moved gradients to backend
This commit is contained in:
parent
a354e192b6
commit
c5c13de2a4
5 changed files with 472 additions and 273 deletions
|
@ -3,16 +3,20 @@ 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)
|
||||
}
|
||||
|
||||
|
@ -20,12 +24,81 @@ type Backend interface {
|
|||
type FillStyle struct {
|
||||
Color color.RGBA
|
||||
Blur float64
|
||||
// radialGradient *RadialGradient
|
||||
// linearGradient *LinearGradient
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
Width() int
|
||||
Height() int
|
||||
|
|
|
@ -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}
|
||||
}
|
||||
|
|
109
backend/gogl/gradients.go
Normal file
109
backend/gogl/gradients.go
Normal file
|
@ -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
|
||||
}
|
152
canvas.go
152
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)
|
||||
|
|
198
gradients.go
198
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
|
||||
cv *Canvas
|
||||
created bool
|
||||
loaded bool
|
||||
deleted bool
|
||||
opaque bool
|
||||
}
|
||||
|
||||
type gradientStop struct {
|
||||
pos float64
|
||||
color glColor
|
||||
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
|
||||
|
||||
if !rg.created {
|
||||
rg.grad = rg.cv.b.LoadRadialGradient(&rg.data)
|
||||
} else {
|
||||
rg.grad.Replace(&rg.data)
|
||||
}
|
||||
beforeIdx = i
|
||||
}
|
||||
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
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue