moved gradients to backend

This commit is contained in:
Thomas Friedel 2019-02-21 13:41:54 +01:00
parent a354e192b6
commit c5c13de2a4
5 changed files with 472 additions and 273 deletions

View file

@ -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

View file

@ -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
View 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
View file

@ -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)

View file

@ -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
}