From c5c13de2a44606769d0b00f8e9278abfb7f45a46 Mon Sep 17 00:00:00 2001 From: Thomas Friedel Date: Thu, 21 Feb 2019 13:41:54 +0100 Subject: [PATCH] moved gradients to backend --- backend/backendbase/base.go | 85 +++++++++++++-- backend/gogl/gogl.go | 195 ++++++++++++++++++---------------- backend/gogl/gradients.go | 109 +++++++++++++++++++ canvas.go | 152 +++++++++++++++------------ gradients.go | 204 +++++++++++++++++------------------- 5 files changed, 472 insertions(+), 273 deletions(-) create mode 100644 backend/gogl/gradients.go diff --git a/backend/backendbase/base.go b/backend/backendbase/base.go index 82cf5ad..c0e224c 100644 --- a/backend/backendbase/base.go +++ b/backend/backendbase/base.go @@ -3,27 +3,100 @@ package backendbase import ( "image" "image/color" + "math" ) // Backend is used by the canvas to actually do the final // drawing. This enables the backend to be implemented by // various methods (OpenGL, but also other APIs or software) type Backend interface { + LoadImage(img image.Image) (Image, error) + LoadLinearGradient(data *LinearGradientData) LinearGradient + LoadRadialGradient(data *RadialGradientData) RadialGradient + ClearRect(x, y, w, h int) Clear(pts [4][2]float64) Fill(style *FillStyle, pts [][2]float64) - LoadImage(img image.Image) (Image, error) DrawImage(dimg Image, sx, sy, sw, sh, dx, dy, dw, dh float64, alpha float64) } // FillStyle is the color and other details on how to fill type FillStyle struct { + Color color.RGBA + Blur float64 + LinearGradient LinearGradient + RadialGradient RadialGradient + Image Image + FillMatrix [9]float64 +} + +type LinearGradientData struct { + X0, Y0 float64 + X1, Y1 float64 + Stops Gradient +} + +type RadialGradientData struct { + X0, Y0 float64 + X1, Y1 float64 + RadFrom float64 + RadTo float64 + Stops Gradient +} + +type Gradient []GradientStop + +func (g Gradient) ColorAt(pos float64) color.RGBA { + if len(g) == 0 { + return color.RGBA{} + } else if len(g) == 1 { + return g[0].Color + } + beforeIdx, afterIdx := -1, -1 + for i, stop := range g { + if stop.Pos > pos { + afterIdx = i + break + } + beforeIdx = i + } + if beforeIdx == -1 { + return g[0].Color + } else if afterIdx == -1 { + return g[len(g)-1].Color + } + before, after := g[beforeIdx], g[afterIdx] + p := (pos - before.Pos) / (after.Pos - before.Pos) + var c [4]float64 + c[0] = (float64(after.Color.R)-float64(before.Color.R))*p + float64(before.Color.R) + c[1] = (float64(after.Color.G)-float64(before.Color.G))*p + float64(before.Color.G) + c[2] = (float64(after.Color.B)-float64(before.Color.B))*p + float64(before.Color.B) + c[3] = (float64(after.Color.A)-float64(before.Color.A))*p + float64(before.Color.A) + return color.RGBA{ + R: uint8(math.Round(c[0])), + G: uint8(math.Round(c[1])), + B: uint8(math.Round(c[2])), + A: uint8(math.Round(c[3])), + } +} + +type GradientStop struct { + Pos float64 Color color.RGBA - Blur float64 - // radialGradient *RadialGradient - // linearGradient *LinearGradient - Image Image - FillMatrix [9]float64 +} + +type LinearGradient interface { + Delete() + IsDeleted() bool + IsOpaque() bool + Replace(data *LinearGradientData) +} + +type RadialGradient interface { + Delete() + IsDeleted() bool + IsOpaque() bool + Replace(data *RadialGradientData) } type Image interface { diff --git a/backend/gogl/gogl.go b/backend/gogl/gogl.go index dd50769..579ef81 100644 --- a/backend/gogl/gogl.go +++ b/backend/gogl/gogl.go @@ -3,6 +3,7 @@ package goglbackend import ( "fmt" "image/color" + "math" "github.com/go-gl/gl/v3.2-core/gl" "github.com/tfriedel6/canvas/backend/backendbase" @@ -249,49 +250,49 @@ func colorGoToGL(c color.RGBA) glColor { } func (b *GoGLBackend) useShader(style *backendbase.FillStyle) (vertexLoc uint32) { - // if lg := style.LinearGradient; lg != nil { - // lg.load() - // gl.ActiveTexture(gl.TEXTURE0) - // gl.BindTexture(gl.TEXTURE_2D, lg.tex) - // gl.UseProgram(lgr.id) - // from := cv.tf(lg.from) - // to := cv.tf(lg.to) - // dir := to.sub(from) - // length := dir.len() - // dir = dir.divf(length) - // gl.Uniform2f(lgr.canvasSize, float32(cv.fw), float32(cv.fh)) - // inv := cv.state.transform.invert().f32() - // gl.UniformMatrix3fv(lgr.invmat, 1, false, &inv[0]) - // gl.Uniform2f(lgr.from, float32(from[0]), float32(from[1])) - // gl.Uniform2f(lgr.dir, float32(dir[0]), float32(dir[1])) - // gl.Uniform1f(lgr.len, float32(length)) - // gl.Uniform1i(lgr.gradient, 0) - // gl.Uniform1f(lgr.globalAlpha, float32(cv.state.globalAlpha)) - // return lgr.vertex - // } - // if rg := style.RadialGradient; rg != nil { - // rg.load() - // gl.ActiveTexture(gl.TEXTURE0) - // gl.BindTexture(gl.TEXTURE_2D, rg.tex) - // gl.UseProgram(rgr.id) - // from := cv.tf(rg.from) - // to := cv.tf(rg.to) - // dir := to.sub(from) - // length := dir.len() - // dir = dir.divf(length) - // gl.Uniform2f(rgr.canvasSize, float32(cv.fw), float32(cv.fh)) - // inv := cv.state.transform.invert().f32() - // gl.UniformMatrix3fv(rgr.invmat, 1, false, &inv[0]) - // gl.Uniform2f(rgr.from, float32(from[0]), float32(from[1])) - // gl.Uniform2f(rgr.to, float32(to[0]), float32(to[1])) - // gl.Uniform2f(rgr.dir, float32(dir[0]), float32(dir[1])) - // gl.Uniform1f(rgr.radFrom, float32(rg.radFrom)) - // gl.Uniform1f(rgr.radTo, float32(rg.radTo)) - // gl.Uniform1f(rgr.len, float32(length)) - // gl.Uniform1i(rgr.gradient, 0) - // gl.Uniform1f(rgr.globalAlpha, float32(cv.state.globalAlpha)) - // return rgr.vertex - // } + if lg := style.LinearGradient; lg != nil { + lg := lg.(*LinearGradient) + gl.ActiveTexture(gl.TEXTURE0) + gl.BindTexture(gl.TEXTURE_2D, lg.tex) + gl.UseProgram(b.lgr.ID) + from := mat(style.FillMatrix).mul(lg.from) + to := mat(style.FillMatrix).mul(lg.to) + dir := to.sub(from) + length := dir.len() + dir = dir.scale(1 / length) + gl.Uniform2f(b.lgr.CanvasSize, float32(b.fw), float32(b.fh)) + inv := mat(style.FillMatrix).invert().f32() + gl.UniformMatrix3fv(b.lgr.Invmat, 1, false, &inv[0]) + gl.Uniform2f(b.lgr.From, float32(from[0]), float32(from[1])) + gl.Uniform2f(b.lgr.Dir, float32(dir[0]), float32(dir[1])) + gl.Uniform1f(b.lgr.Len, float32(length)) + gl.Uniform1i(b.lgr.Gradient, 0) + gl.Uniform1f(b.lgr.GlobalAlpha, float32(style.Color.A)/255) + return b.lgr.Vertex + } + if rg := style.RadialGradient; rg != nil { + rg := rg.(*RadialGradient) + gl.ActiveTexture(gl.TEXTURE0) + gl.BindTexture(gl.TEXTURE_2D, rg.tex) + gl.UseProgram(b.rgr.ID) + from := mat(style.FillMatrix).mul(rg.from) + to := mat(style.FillMatrix).mul(rg.to) + dir := to.sub(from) + length := dir.len() + dir = dir.scale(1 / length) + gl.Uniform2f(b.rgr.CanvasSize, float32(b.fw), float32(b.fh)) + inv := mat(style.FillMatrix).invert().f32() + gl.UniformMatrix3fv(b.rgr.Invmat, 1, false, &inv[0]) + gl.Uniform2f(b.rgr.From, float32(from[0]), float32(from[1])) + gl.Uniform2f(b.rgr.To, float32(to[0]), float32(to[1])) + gl.Uniform2f(b.rgr.Dir, float32(dir[0]), float32(dir[1])) + gl.Uniform1f(b.rgr.RadFrom, float32(rg.radFrom)) + gl.Uniform1f(b.rgr.RadTo, float32(rg.radTo)) + gl.Uniform1f(b.rgr.Len, float32(length)) + gl.Uniform1i(b.rgr.Gradient, 0) + gl.Uniform1f(b.rgr.GlobalAlpha, float32(style.Color.A)/255) + return b.rgr.Vertex + } if img := style.Image; img != nil { img := img.(*Image) gl.UseProgram(b.ipr.ID) @@ -315,51 +316,51 @@ func (b *GoGLBackend) useShader(style *backendbase.FillStyle) (vertexLoc uint32) } func (b *GoGLBackend) useAlphaShader(style *backendbase.FillStyle, alphaTexSlot int32) (vertexLoc, alphaTexCoordLoc uint32) { - // if lg := style.LinearGradient; lg != nil { - // lg.load() - // gl.ActiveTexture(gl.TEXTURE0) - // gl.BindTexture(gl.TEXTURE_2D, lg.tex) - // gl.UseProgram(lgar.id) - // from := cv.tf(lg.from) - // to := cv.tf(lg.to) - // dir := to.sub(from) - // length := dir.len() - // dir = dir.divf(length) - // gl.Uniform2f(lgar.canvasSize, float32(cv.fw), float32(cv.fh)) - // inv := cv.state.transform.invert().f32() - // gl.UniformMatrix3fv(lgar.invmat, 1, false, &inv[0]) - // gl.Uniform2f(lgar.from, float32(from[0]), float32(from[1])) - // gl.Uniform2f(lgar.dir, float32(dir[0]), float32(dir[1])) - // gl.Uniform1f(lgar.len, float32(length)) - // gl.Uniform1i(lgar.gradient, 0) - // gl.Uniform1i(lgar.alphaTex, alphaTexSlot) - // gl.Uniform1f(lgar.globalAlpha, float32(cv.state.globalAlpha)) - // return lgar.vertex, lgar.alphaTexCoord - // } - // if rg := style.RadialGradient; rg != nil { - // rg.load() - // gl.ActiveTexture(gl.TEXTURE0) - // gl.BindTexture(gl.TEXTURE_2D, rg.tex) - // gl.UseProgram(rgar.id) - // from := cv.tf(rg.from) - // to := cv.tf(rg.to) - // dir := to.sub(from) - // length := dir.len() - // dir = dir.divf(length) - // gl.Uniform2f(rgar.canvasSize, float32(cv.fw), float32(cv.fh)) - // inv := cv.state.transform.invert().f32() - // gl.UniformMatrix3fv(rgar.invmat, 1, false, &inv[0]) - // gl.Uniform2f(rgar.from, float32(from[0]), float32(from[1])) - // gl.Uniform2f(rgar.to, float32(to[0]), float32(to[1])) - // gl.Uniform2f(rgar.dir, float32(dir[0]), float32(dir[1])) - // gl.Uniform1f(rgar.radFrom, float32(rg.radFrom)) - // gl.Uniform1f(rgar.radTo, float32(rg.radTo)) - // gl.Uniform1f(rgar.len, float32(length)) - // gl.Uniform1i(rgar.gradient, 0) - // gl.Uniform1i(rgar.alphaTex, alphaTexSlot) - // gl.Uniform1f(rgar.globalAlpha, float32(cv.state.globalAlpha)) - // return rgar.vertex, rgar.alphaTexCoord - // } + if lg := style.LinearGradient; lg != nil { + lg := lg.(*LinearGradient) + gl.ActiveTexture(gl.TEXTURE0) + gl.BindTexture(gl.TEXTURE_2D, lg.tex) + gl.UseProgram(b.lgar.ID) + from := mat(style.FillMatrix).mul(lg.from) + to := mat(style.FillMatrix).mul(lg.to) + dir := to.sub(from) + length := dir.len() + dir = dir.scale(1 / length) + gl.Uniform2f(b.lgar.CanvasSize, float32(b.fw), float32(b.fh)) + inv := mat(style.FillMatrix).invert().f32() + gl.UniformMatrix3fv(b.lgar.Invmat, 1, false, &inv[0]) + gl.Uniform2f(b.lgar.From, float32(from[0]), float32(from[1])) + gl.Uniform2f(b.lgar.Dir, float32(dir[0]), float32(dir[1])) + gl.Uniform1f(b.lgar.Len, float32(length)) + gl.Uniform1i(b.lgar.Gradient, 0) + gl.Uniform1i(b.lgar.AlphaTex, alphaTexSlot) + gl.Uniform1f(b.lgar.GlobalAlpha, float32(style.Color.A)/255) + return b.lgar.Vertex, b.lgar.AlphaTexCoord + } + if rg := style.RadialGradient; rg != nil { + rg := rg.(*RadialGradient) + gl.ActiveTexture(gl.TEXTURE0) + gl.BindTexture(gl.TEXTURE_2D, rg.tex) + gl.UseProgram(b.rgar.ID) + from := mat(style.FillMatrix).mul(rg.from) + to := mat(style.FillMatrix).mul(rg.to) + dir := to.sub(from) + length := dir.len() + dir = dir.scale(1 / length) + gl.Uniform2f(b.rgar.CanvasSize, float32(b.fw), float32(b.fh)) + inv := mat(style.FillMatrix).invert().f32() + gl.UniformMatrix3fv(b.rgar.Invmat, 1, false, &inv[0]) + gl.Uniform2f(b.rgar.From, float32(from[0]), float32(from[1])) + gl.Uniform2f(b.rgar.To, float32(to[0]), float32(to[1])) + gl.Uniform2f(b.rgar.Dir, float32(dir[0]), float32(dir[1])) + gl.Uniform1f(b.rgar.RadFrom, float32(rg.radFrom)) + gl.Uniform1f(b.rgar.RadTo, float32(rg.radTo)) + gl.Uniform1f(b.rgar.Len, float32(length)) + gl.Uniform1i(b.rgar.Gradient, 0) + gl.Uniform1i(b.rgar.AlphaTex, alphaTexSlot) + gl.Uniform1f(b.rgar.GlobalAlpha, float32(style.Color.A)/255) + return b.rgar.Vertex, b.rgar.AlphaTexCoord + } if img := style.Image; img != nil { img := img.(*Image) gl.UseProgram(b.ipar.ID) @@ -458,3 +459,21 @@ func (m mat) f32() [9]float32 { float32(m[3]), float32(m[4]), float32(m[5]), float32(m[6]), float32(m[7]), float32(m[8])} } + +func (m mat) mul(v vec) vec { + return vec{m[0]*v[0] + m[3]*v[1] + m[6], m[1]*v[0] + m[4]*v[1] + m[7]} +} + +type vec [2]float64 + +func (v1 vec) sub(v2 vec) vec { + return vec{v1[0] - v2[0], v1[1] - v2[1]} +} + +func (v vec) len() float64 { + return math.Sqrt(v[0]*v[0] + v[1]*v[1]) +} + +func (v vec) scale(f float64) vec { + return vec{v[0] * f, v[1] * f} +} diff --git a/backend/gogl/gradients.go b/backend/gogl/gradients.go new file mode 100644 index 0000000..d9d6a96 --- /dev/null +++ b/backend/gogl/gradients.go @@ -0,0 +1,109 @@ +package goglbackend + +import ( + "runtime" + + "github.com/go-gl/gl/v3.2-core/gl" + "github.com/tfriedel6/canvas/backend/backendbase" +) + +// LinearGradient is a gradient with any number of +// stops and any number of colors. The gradient will +// be drawn such that each point on the gradient +// will correspond to a straight line +type LinearGradient struct { + gradient +} + +// RadialGradient is a gradient with any number of +// stops and any number of colors. The gradient will +// be drawn such that each point on the gradient +// will correspond to a circle +type RadialGradient struct { + gradient + radFrom, radTo float64 +} + +type gradient struct { + from, to vec + tex uint32 + loaded bool + deleted bool + opaque bool +} + +func (b *GoGLBackend) LoadLinearGradient(data *backendbase.LinearGradientData) backendbase.LinearGradient { + lg := &LinearGradient{ + gradient: gradient{from: vec{data.X0, data.Y0}, to: vec{data.X1, data.Y1}, opaque: true}, + } + gl.GenTextures(1, &lg.tex) + gl.ActiveTexture(gl.TEXTURE0) + gl.BindTexture(gl.TEXTURE_2D, lg.tex) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE) + lg.load(data.Stops) + runtime.SetFinalizer(lg, func(lg *LinearGradient) { + b.glChan <- func() { + gl.DeleteTextures(1, &lg.tex) + } + }) + return lg +} + +func (b *GoGLBackend) LoadRadialGradient(data *backendbase.RadialGradientData) backendbase.RadialGradient { + rg := &RadialGradient{ + gradient: gradient{from: vec{data.X0, data.Y0}, to: vec{data.X1, data.Y1}, opaque: true}, + radFrom: data.RadFrom, + radTo: data.RadTo, + } + gl.GenTextures(1, &rg.tex) + gl.ActiveTexture(gl.TEXTURE0) + gl.BindTexture(gl.TEXTURE_2D, rg.tex) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE) + rg.load(data.Stops) + runtime.SetFinalizer(rg, func(rg *RadialGradient) { + b.glChan <- func() { + gl.DeleteTextures(1, &rg.tex) + } + }) + return rg +} + +// Delete explicitly deletes the gradient +func (g *gradient) Delete() { + gl.DeleteTextures(1, &g.tex) + g.deleted = true +} + +func (g *gradient) IsDeleted() bool { return g.deleted } +func (g *gradient) IsOpaque() bool { return g.opaque } + +func (lg *LinearGradient) Replace(data *backendbase.LinearGradientData) { lg.load(data.Stops) } +func (rg *RadialGradient) Replace(data *backendbase.RadialGradientData) { rg.load(data.Stops) } + +func (g *gradient) load(stops backendbase.Gradient) { + if g.loaded { + return + } + + gl.ActiveTexture(gl.TEXTURE0) + gl.BindTexture(gl.TEXTURE_2D, g.tex) + var pixels [2048 * 4]byte + pp := 0 + for i := 0; i < 2048; i++ { + c := stops.ColorAt(float64(i) / 2047) + pixels[pp] = c.R + pixels[pp+1] = c.G + pixels[pp+2] = c.B + pixels[pp+3] = c.A + pp += 4 + } + + gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 2048, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, gl.Ptr(&pixels[0])) + g.loaded = true +} diff --git a/canvas.go b/canvas.go index 27547d5..897ff46 100644 --- a/canvas.go +++ b/canvas.go @@ -504,7 +504,13 @@ func (s *drawStyle) isOpaque() bool { func (cv *Canvas) backendFillStyle(s *drawStyle, alpha float64) backendbase.FillStyle { stl := backendbase.FillStyle{Color: s.color, FillMatrix: cv.state.transform} alpha *= cv.state.globalAlpha - if img := cv.state.fill.image; img != nil { + if lg := s.linearGradient; lg != nil { + lg.load() + stl.LinearGradient = lg.grad + } else if rg := s.radialGradient; rg != nil { + rg.load() + stl.RadialGradient = rg.grad + } else if img := s.image; img != nil { stl.Image = img.img } else { alpha *= float64(s.color.A) / 255 @@ -516,42 +522,46 @@ func (cv *Canvas) backendFillStyle(s *drawStyle, alpha float64) backendbase.Fill func (cv *Canvas) useShader(style *drawStyle) (vertexLoc uint32) { if lg := style.linearGradient; lg != nil { lg.load() - gli.ActiveTexture(gl_TEXTURE0) - gli.BindTexture(gl_TEXTURE_2D, lg.tex) - gli.UseProgram(lgr.id) - from := cv.tf(lg.from) - to := cv.tf(lg.to) - dir := to.sub(from) - length := dir.len() - dir = dir.divf(length) - gli.Uniform2f(lgr.canvasSize, float32(cv.fw), float32(cv.fh)) - gli.Uniform2f(lgr.from, float32(from[0]), float32(from[1])) - gli.Uniform2f(lgr.dir, float32(dir[0]), float32(dir[1])) - gli.Uniform1f(lgr.len, float32(length)) - gli.Uniform1i(lgr.gradient, 0) - gli.Uniform1f(lgr.globalAlpha, float32(cv.state.globalAlpha)) - return lgr.vertex + // gli.ActiveTexture(gl_TEXTURE0) + // gli.BindTexture(gl_TEXTURE_2D, lg.tex) + // gli.UseProgram(lgr.id) + // from := cv.tf(lg.from) + // to := cv.tf(lg.to) + // dir := to.sub(from) + // length := dir.len() + // dir = dir.divf(length) + // gli.Uniform2f(lgr.canvasSize, float32(cv.fw), float32(cv.fh)) + // inv := cv.state.transform.invert().f32() + // gli.UniformMatrix3fv(lgr.invmat, 1, false, &inv[0]) + // gli.Uniform2f(lgr.from, float32(from[0]), float32(from[1])) + // gli.Uniform2f(lgr.dir, float32(dir[0]), float32(dir[1])) + // gli.Uniform1f(lgr.len, float32(length)) + // gli.Uniform1i(lgr.gradient, 0) + // gli.Uniform1f(lgr.globalAlpha, float32(cv.state.globalAlpha)) + // return lgr.vertex } if rg := style.radialGradient; rg != nil { rg.load() - gli.ActiveTexture(gl_TEXTURE0) - gli.BindTexture(gl_TEXTURE_2D, rg.tex) - gli.UseProgram(rgr.id) - from := cv.tf(rg.from) - to := cv.tf(rg.to) - dir := to.sub(from) - length := dir.len() - dir = dir.divf(length) - gli.Uniform2f(rgr.canvasSize, float32(cv.fw), float32(cv.fh)) - gli.Uniform2f(rgr.from, float32(from[0]), float32(from[1])) - gli.Uniform2f(rgr.to, float32(to[0]), float32(to[1])) - gli.Uniform2f(rgr.dir, float32(dir[0]), float32(dir[1])) - gli.Uniform1f(rgr.radFrom, float32(rg.radFrom)) - gli.Uniform1f(rgr.radTo, float32(rg.radTo)) - gli.Uniform1f(rgr.len, float32(length)) - gli.Uniform1i(rgr.gradient, 0) - gli.Uniform1f(rgr.globalAlpha, float32(cv.state.globalAlpha)) - return rgr.vertex + // gli.ActiveTexture(gl_TEXTURE0) + // gli.BindTexture(gl_TEXTURE_2D, rg.tex) + // gli.UseProgram(rgr.id) + // from := cv.tf(rg.from) + // to := cv.tf(rg.to) + // dir := to.sub(from) + // length := dir.len() + // dir = dir.divf(length) + // gli.Uniform2f(rgr.canvasSize, float32(cv.fw), float32(cv.fh)) + // inv := cv.state.transform.invert().f32() + // gli.UniformMatrix3fv(rgr.invmat, 1, false, &inv[0]) + // gli.Uniform2f(rgr.from, float32(from[0]), float32(from[1])) + // gli.Uniform2f(rgr.to, float32(to[0]), float32(to[1])) + // gli.Uniform2f(rgr.dir, float32(dir[0]), float32(dir[1])) + // gli.Uniform1f(rgr.radFrom, float32(rg.radFrom)) + // gli.Uniform1f(rgr.radTo, float32(rg.radTo)) + // gli.Uniform1f(rgr.len, float32(length)) + // gli.Uniform1i(rgr.gradient, 0) + // gli.Uniform1f(rgr.globalAlpha, float32(cv.state.globalAlpha)) + // return rgr.vertex } // if img := style.image; img != nil { // gli.UseProgram(ipr.id) @@ -577,44 +587,48 @@ func (cv *Canvas) useShader(style *drawStyle) (vertexLoc uint32) { func (cv *Canvas) useAlphaShader(style *drawStyle, alphaTexSlot int32) (vertexLoc, alphaTexCoordLoc uint32) { if lg := style.linearGradient; lg != nil { lg.load() - gli.ActiveTexture(gl_TEXTURE0) - gli.BindTexture(gl_TEXTURE_2D, lg.tex) - gli.UseProgram(lgar.id) - from := cv.tf(lg.from) - to := cv.tf(lg.to) - dir := to.sub(from) - length := dir.len() - dir = dir.divf(length) - gli.Uniform2f(lgar.canvasSize, float32(cv.fw), float32(cv.fh)) - gli.Uniform2f(lgar.from, float32(from[0]), float32(from[1])) - gli.Uniform2f(lgar.dir, float32(dir[0]), float32(dir[1])) - gli.Uniform1f(lgar.len, float32(length)) - gli.Uniform1i(lgar.gradient, 0) - gli.Uniform1i(lgar.alphaTex, alphaTexSlot) - gli.Uniform1f(lgar.globalAlpha, float32(cv.state.globalAlpha)) - return lgar.vertex, lgar.alphaTexCoord + // gli.ActiveTexture(gl_TEXTURE0) + // gli.BindTexture(gl_TEXTURE_2D, lg.tex) + // gli.UseProgram(lgar.id) + // from := cv.tf(lg.from) + // to := cv.tf(lg.to) + // dir := to.sub(from) + // length := dir.len() + // dir = dir.divf(length) + // gli.Uniform2f(lgar.canvasSize, float32(cv.fw), float32(cv.fh)) + // inv := cv.state.transform.invert().f32() + // gli.UniformMatrix3fv(lgar.invmat, 1, false, &inv[0]) + // gli.Uniform2f(lgar.from, float32(from[0]), float32(from[1])) + // gli.Uniform2f(lgar.dir, float32(dir[0]), float32(dir[1])) + // gli.Uniform1f(lgar.len, float32(length)) + // gli.Uniform1i(lgar.gradient, 0) + // gli.Uniform1i(lgar.alphaTex, alphaTexSlot) + // gli.Uniform1f(lgar.globalAlpha, float32(cv.state.globalAlpha)) + // return lgar.vertex, lgar.alphaTexCoord } if rg := style.radialGradient; rg != nil { rg.load() - gli.ActiveTexture(gl_TEXTURE0) - gli.BindTexture(gl_TEXTURE_2D, rg.tex) - gli.UseProgram(rgar.id) - from := cv.tf(rg.from) - to := cv.tf(rg.to) - dir := to.sub(from) - length := dir.len() - dir = dir.divf(length) - gli.Uniform2f(rgar.canvasSize, float32(cv.fw), float32(cv.fh)) - gli.Uniform2f(rgar.from, float32(from[0]), float32(from[1])) - gli.Uniform2f(rgar.to, float32(to[0]), float32(to[1])) - gli.Uniform2f(rgar.dir, float32(dir[0]), float32(dir[1])) - gli.Uniform1f(rgar.radFrom, float32(rg.radFrom)) - gli.Uniform1f(rgar.radTo, float32(rg.radTo)) - gli.Uniform1f(rgar.len, float32(length)) - gli.Uniform1i(rgar.gradient, 0) - gli.Uniform1i(rgar.alphaTex, alphaTexSlot) - gli.Uniform1f(rgar.globalAlpha, float32(cv.state.globalAlpha)) - return rgar.vertex, rgar.alphaTexCoord + // gli.ActiveTexture(gl_TEXTURE0) + // gli.BindTexture(gl_TEXTURE_2D, rg.tex) + // gli.UseProgram(rgar.id) + // from := cv.tf(rg.from) + // to := cv.tf(rg.to) + // dir := to.sub(from) + // length := dir.len() + // dir = dir.divf(length) + // gli.Uniform2f(rgar.canvasSize, float32(cv.fw), float32(cv.fh)) + // inv := cv.state.transform.invert().f32() + // gli.UniformMatrix3fv(rgar.invmat, 1, false, &inv[0]) + // gli.Uniform2f(rgar.from, float32(from[0]), float32(from[1])) + // gli.Uniform2f(rgar.to, float32(to[0]), float32(to[1])) + // gli.Uniform2f(rgar.dir, float32(dir[0]), float32(dir[1])) + // gli.Uniform1f(rgar.radFrom, float32(rg.radFrom)) + // gli.Uniform1f(rgar.radTo, float32(rg.radTo)) + // gli.Uniform1f(rgar.len, float32(length)) + // gli.Uniform1i(rgar.gradient, 0) + // gli.Uniform1i(rgar.alphaTex, alphaTexSlot) + // gli.Uniform1f(rgar.globalAlpha, float32(cv.state.globalAlpha)) + // return rgar.vertex, rgar.alphaTexCoord } // if img := style.image; img != nil { // gli.UseProgram(ipar.id) diff --git a/gradients.go b/gradients.go index abd7aa7..48b4911 100644 --- a/gradients.go +++ b/gradients.go @@ -1,8 +1,9 @@ package canvas import ( - "math" - "runtime" + "image/color" + + "github.com/tfriedel6/canvas/backend/backendbase" ) // LinearGradient is a gradient with any number of @@ -10,7 +11,13 @@ import ( // be drawn such that each point on the gradient // will correspond to a straight line type LinearGradient struct { - gradient + cv *Canvas + created bool + loaded bool + deleted bool + opaque bool + grad backendbase.LinearGradient + data backendbase.LinearGradientData } // RadialGradient is a gradient with any number of @@ -18,147 +25,124 @@ type LinearGradient struct { // be drawn such that each point on the gradient // will correspond to a circle type RadialGradient struct { - gradient - radFrom, radTo float64 -} - -type gradient struct { - from, to vec - stops []gradientStop - tex uint32 - loaded bool - deleted bool - opaque bool -} - -type gradientStop struct { - pos float64 - color glColor + cv *Canvas + created bool + loaded bool + deleted bool + opaque bool + grad backendbase.RadialGradient + data backendbase.RadialGradientData } // NewLinearGradient creates a new linear gradient with // the coordinates from where to where the gradient // will apply on the canvas -func NewLinearGradient(x0, y0, x1, y1 float64) *LinearGradient { - if gli == nil { - panic("LoadGL must be called before gradients can be created") +func (cv *Canvas) NewLinearGradient(x0, y0, x1, y1 float64) *LinearGradient { + return &LinearGradient{ + cv: cv, + opaque: true, + data: backendbase.LinearGradientData{ + X0: x0, + Y0: y0, + X1: x1, + Y1: y1, + Stops: make(backendbase.Gradient, 0, 20), + }, } - lg := &LinearGradient{gradient: gradient{from: vec{x0, y0}, to: vec{x1, y1}, opaque: true}} - gli.GenTextures(1, &lg.tex) - gli.ActiveTexture(gl_TEXTURE0) - gli.BindTexture(gl_TEXTURE_2D, lg.tex) - gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, gl_LINEAR) - gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, gl_LINEAR) - gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_S, gl_CLAMP_TO_EDGE) - gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_T, gl_CLAMP_TO_EDGE) - runtime.SetFinalizer(lg, func(lg *LinearGradient) { - glChan <- func() { - gli.DeleteTextures(1, &lg.tex) - } - }) - return lg } // NewRadialGradient creates a new linear gradient with // the coordinates and the radii for two circles. The // gradient will apply from the first to the second // circle -func NewRadialGradient(x0, y0, r0, x1, y1, r1 float64) *RadialGradient { - if gli == nil { - panic("LoadGL must be called before gradients can be created") +func (cv *Canvas) NewRadialGradient(x0, y0, r0, x1, y1, r1 float64) *RadialGradient { + return &RadialGradient{ + cv: cv, + opaque: true, + data: backendbase.RadialGradientData{ + X0: x0, + Y0: y0, + X1: x1, + Y1: y1, + RadFrom: r0, + RadTo: r1, + Stops: make(backendbase.Gradient, 0, 20), + }, } - rg := &RadialGradient{gradient: gradient{from: vec{x0, y0}, to: vec{x1, y1}, opaque: true}, radFrom: r0, radTo: r1} - gli.GenTextures(1, &rg.tex) - gli.ActiveTexture(gl_TEXTURE0) - gli.BindTexture(gl_TEXTURE_2D, rg.tex) - gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, gl_LINEAR) - gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, gl_LINEAR) - gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_S, gl_CLAMP_TO_EDGE) - gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_T, gl_CLAMP_TO_EDGE) - runtime.SetFinalizer(rg, func(rg *RadialGradient) { - glChan <- func() { - gli.DeleteTextures(1, &rg.tex) - } - }) - return rg + } // Delete explicitly deletes the gradient -func (g *gradient) Delete() { - gli.DeleteTextures(1, &g.tex) - g.deleted = true -} +func (lg *LinearGradient) Delete() { lg.grad.Delete() } -func (g *gradient) load() { - if g.loaded { +// Delete explicitly deletes the gradient +func (rg *RadialGradient) Delete() { rg.grad.Delete() } + +func (lg *LinearGradient) load() { + if lg.loaded || len(lg.data.Stops) < 1 { return } - gli.ActiveTexture(gl_TEXTURE0) - gli.BindTexture(gl_TEXTURE_2D, g.tex) - var pixels [2048 * 4]byte - pp := 0 - for i := 0; i < 2048; i++ { - c := g.colorAt(float64(i) / 2047) - pixels[pp] = byte(math.Floor(c.r*255 + 0.5)) - pixels[pp+1] = byte(math.Floor(c.g*255 + 0.5)) - pixels[pp+2] = byte(math.Floor(c.b*255 + 0.5)) - pixels[pp+3] = byte(math.Floor(c.a*255 + 0.5)) - pp += 4 + if !lg.created { + lg.grad = lg.cv.b.LoadLinearGradient(&lg.data) + } else { + lg.grad.Replace(&lg.data) } - gli.TexImage2D(gl_TEXTURE_2D, 0, gl_RGBA, 2048, 1, 0, gl_RGBA, gl_UNSIGNED_BYTE, gli.Ptr(&pixels[0])) - g.loaded = true + lg.created = true + lg.loaded = true } -func (g *gradient) colorAt(pos float64) glColor { - if len(g.stops) == 0 { - return glColor{} - } else if len(g.stops) == 1 { - return g.stops[0].color +func (rg *RadialGradient) load() { + if rg.loaded || len(rg.data.Stops) < 1 { + return } - beforeIdx, afterIdx := -1, -1 - for i, stop := range g.stops { - if stop.pos > pos { - afterIdx = i - break - } - beforeIdx = i + + if !rg.created { + rg.grad = rg.cv.b.LoadRadialGradient(&rg.data) + } else { + rg.grad.Replace(&rg.data) } - if beforeIdx == -1 { - return g.stops[0].color - } else if afterIdx == -1 { - return g.stops[len(g.stops)-1].color - } - before, after := g.stops[beforeIdx], g.stops[afterIdx] - p := (pos - before.pos) / (after.pos - before.pos) - var c glColor - c.r = (after.color.r-before.color.r)*p + before.color.r - c.g = (after.color.g-before.color.g)*p + before.color.g - c.b = (after.color.b-before.color.b)*p + before.color.b - c.a = (after.color.a-before.color.a)*p + before.color.a - return c + rg.created = true + rg.loaded = true } // AddColorStop adds a color stop to the gradient. The stops // don't have to be added in order, they are sorted into the // right place -func (g *gradient) AddColorStop(pos float64, color ...interface{}) { - gc, _ := parseColor(color...) - c := colorGoToGL(gc) - if c.a < 1 { - g.opaque = false +func (lg *LinearGradient) AddColorStop(pos float64, stopColor ...interface{}) { + var c color.RGBA + lg.data.Stops, c = addColorStop(lg.data.Stops, pos, stopColor...) + if c.A < 255 { + lg.opaque = false } - insert := len(g.stops) - for i, stop := range g.stops { - if stop.pos > pos { + lg.loaded = false +} + +// AddColorStop adds a color stop to the gradient. The stops +// don't have to be added in order, they are sorted into the +// right place +func (rg *RadialGradient) AddColorStop(pos float64, stopColor ...interface{}) { + var c color.RGBA + rg.data.Stops, c = addColorStop(rg.data.Stops, pos, stopColor...) + if c.A < 255 { + rg.opaque = false + } + rg.loaded = false +} + +func addColorStop(stops backendbase.Gradient, pos float64, stopColor ...interface{}) (backendbase.Gradient, color.RGBA) { + c, _ := parseColor(stopColor...) + insert := len(stops) + for i, stop := range stops { + if stop.Pos > pos { insert = i break } } - g.stops = append(g.stops, gradientStop{}) - if insert < len(g.stops)-1 { - copy(g.stops[insert+1:], g.stops[insert:len(g.stops)-1]) + stops = append(stops, backendbase.GradientStop{}) + if insert < len(stops)-1 { + copy(stops[insert+1:], stops[insert:len(stops)-1]) } - g.stops[insert] = gradientStop{pos: pos, color: c} - g.loaded = false + stops[insert] = backendbase.GradientStop{Pos: pos, Color: c} + return stops, c }