diff --git a/canvas.go b/canvas.go index a94684f..83f4fa9 100644 --- a/canvas.go +++ b/canvas.go @@ -36,7 +36,8 @@ type pathPoint struct { type drawState struct { transform lm.Mat3x3 fill struct { - color glColor + color glColor + linearGradient *LinearGradient } stroke struct { color glColor @@ -137,6 +138,7 @@ var ( buf uint32 sr *solidShader tr *textureShader + lgr *linearGradientShader glChan = make(chan func()) ) @@ -163,6 +165,15 @@ func LoadGL(glimpl GL) (err error) { return } + lgr, err = loadLinearGradientShader() + if err != nil { + return + } + err = glError() + if err != nil { + return + } + gli.GenBuffers(1, &buf) err = glError() if err != nil { @@ -211,6 +222,30 @@ void main() { gl_FragColor = texture2D(image, v_texCoord); }` +var linearGradientVS = ` +attribute vec2 vertex; +uniform vec2 canvasSize; +varying vec2 v_cp; +void main() { + v_cp = vertex; + vec2 glp = vertex * 2.0 / canvasSize - 1.0; + gl_Position = vec4(glp.x, -glp.y, 0.0, 1.0); +}` +var linearGradientFS = ` +#ifdef GL_ES +precision mediump float; +#endif +varying vec2 v_cp; +uniform sampler1D gradient; +uniform vec2 from, dir; +uniform float length; +void main() { + vec2 v = v_cp - from; + float r = dot(v, dir) / length; + r = clamp(r, 0.0, 1.0); + gl_FragColor = texture1D(gradient, r); +}` + func glError() error { glErr := gli.GetError() if glErr != gl_NO_ERROR { @@ -219,8 +254,17 @@ func glError() error { return nil } -// SetFillColor sets the color for any fill calls -func (cv *Canvas) SetFillColor(value ...interface{}) { +// SetFillStyle sets the color or gradient for any fill calls +func (cv *Canvas) SetFillStyle(value ...interface{}) { + cv.state.fill.color = glColor{} + cv.state.fill.linearGradient = nil + if len(value) == 1 { + switch v := value[0].(type) { + case *LinearGradient: + cv.state.fill.linearGradient = v + return + } + } c, ok := parseColor(value...) if ok { cv.state.fill.color = c @@ -316,25 +360,56 @@ func (cv *Canvas) SetTransform(a, b, c, d, e, f float32) { cv.state.transform = lm.Mat3x3{a, b, 0, c, d, 0, e, f, 1} } -// FillRect fills a rectangle with the active color +// FillRect fills a rectangle with the active fill style func (cv *Canvas) FillRect(x, y, w, h float32) { cv.activate() - gli.UseProgram(sr.id) + if lg := cv.state.fill.linearGradient; lg != nil { + p0 := cv.tf(lm.Vec2{x, y}) + p1 := cv.tf(lm.Vec2{x, y + h}) + p2 := cv.tf(lm.Vec2{x + w, y + h}) + p3 := cv.tf(lm.Vec2{x + w, y}) - x0f, y0f := cv.tfToGL(x, y) - x1f, y1f := cv.tfToGL(x, y+h) - x2f, y2f := cv.tfToGL(x+w, y+h) - x3f, y3f := cv.tfToGL(x+w, y) + gli.BindBuffer(gl_ARRAY_BUFFER, buf) + data := [8]float32{p0[0], p0[1], p1[0], p1[1], p2[0], p2[1], p3[0], p3[1]} + gli.BufferData(gl_ARRAY_BUFFER, len(data)*4, unsafe.Pointer(&data[0]), gl_STREAM_DRAW) - gli.BindBuffer(gl_ARRAY_BUFFER, buf) - data := [8]float32{x0f, y0f, x1f, y1f, x2f, y2f, x3f, y3f} - gli.BufferData(gl_ARRAY_BUFFER, len(data)*4, unsafe.Pointer(&data[0]), gl_STREAM_DRAW) + lg.load() + gli.UseProgram(lgr.id) + gli.VertexAttribPointer(lgr.vertex, 2, gl_FLOAT, false, 0, nil) + gli.ActiveTexture(gl_TEXTURE0) + gli.BindTexture(gl_TEXTURE_1D, lg.tex) - gli.VertexAttribPointer(sr.vertex, 2, gl_FLOAT, false, 0, nil) - c := cv.state.fill.color - gli.Uniform4f(sr.color, c.r, c.g, c.b, c.a) - gli.EnableVertexAttribArray(sr.vertex) - gli.DrawArrays(gl_TRIANGLE_FAN, 0, 4) - gli.DisableVertexAttribArray(sr.vertex) + gli.Uniform2f(lgr.canvasSize, cv.fw, cv.fh) + from := cv.tf(lg.from) + to := cv.tf(lg.to) + dir := to.Sub(from) + length := dir.Len() + dir = dir.DivF(length) + gli.Uniform2f(lgr.from, from[0], from[1]) + gli.Uniform2f(lgr.dir, dir[0], dir[1]) + gli.Uniform1f(lgr.length, length) + + gli.Uniform1i(lgr.gradient, 0) + gli.EnableVertexAttribArray(lgr.vertex) + gli.DrawArrays(gl_TRIANGLE_FAN, 0, 4) + gli.DisableVertexAttribArray(lgr.vertex) + } else { + x0f, y0f := cv.tfToGL(x, y) + x1f, y1f := cv.tfToGL(x, y+h) + x2f, y2f := cv.tfToGL(x+w, y+h) + x3f, y3f := cv.tfToGL(x+w, y) + + gli.BindBuffer(gl_ARRAY_BUFFER, buf) + data := [8]float32{x0f, y0f, x1f, y1f, x2f, y2f, x3f, y3f} + gli.BufferData(gl_ARRAY_BUFFER, len(data)*4, unsafe.Pointer(&data[0]), gl_STREAM_DRAW) + + gli.UseProgram(sr.id) + gli.VertexAttribPointer(sr.vertex, 2, gl_FLOAT, false, 0, nil) + c := cv.state.fill.color + gli.Uniform4f(sr.color, c.r, c.g, c.b, c.a) + gli.EnableVertexAttribArray(lgr.vertex) + gli.DrawArrays(gl_TRIANGLE_FAN, 0, 4) + gli.DisableVertexAttribArray(lgr.vertex) + } } diff --git a/gradients.go b/gradients.go new file mode 100644 index 0000000..1b7aac1 --- /dev/null +++ b/gradients.go @@ -0,0 +1,108 @@ +package canvas + +import ( + "runtime" + + "github.com/barnex/fmath" + "github.com/tfriedel6/lm" +) + +type LinearGradient struct { + from, to lm.Vec2 + stops []gradientStop + tex uint32 + loaded bool + deleted bool +} + +type gradientStop struct { + pos float32 + color glColor +} + +func NewLinearGradient(x0, y0, x1, y1 float32) *LinearGradient { + lg := &LinearGradient{from: lm.Vec2{x0, y0}, to: lm.Vec2{x1, y1}} + gli.GenTextures(1, &lg.tex) + gli.ActiveTexture(gl_TEXTURE0) + gli.BindTexture(gl_TEXTURE_1D, lg.tex) + gli.TexParameteri(gl_TEXTURE_1D, gl_TEXTURE_MIN_FILTER, gl_LINEAR) + gli.TexParameteri(gl_TEXTURE_1D, gl_TEXTURE_MAG_FILTER, gl_LINEAR) + gli.TexParameteri(gl_TEXTURE_1D, gl_TEXTURE_WRAP_S, gl_CLAMP_TO_EDGE) + runtime.SetFinalizer(lg, func(lg *LinearGradient) { + glChan <- func() { + gli.DeleteTextures(1, &lg.tex) + } + }) + return lg +} + +func (lg *LinearGradient) Delete() { + gli.DeleteTextures(1, &lg.tex) +} + +func (lg *LinearGradient) load() { + if lg.loaded { + return + } + + gli.ActiveTexture(gl_TEXTURE0) + gli.BindTexture(gl_TEXTURE_1D, lg.tex) + var pixels [2048 * 4]byte + pp := 0 + for i := 0; i < 2048; i++ { + c := lg.colorAt(float32(i) / 2047) + pixels[pp] = byte(fmath.Floor(c.r*255 + 0.5)) + pixels[pp+1] = byte(fmath.Floor(c.g*255 + 0.5)) + pixels[pp+2] = byte(fmath.Floor(c.b*255 + 0.5)) + pixels[pp+3] = byte(fmath.Floor(c.a*255 + 0.5)) + pp += 4 + } + gli.TexImage1D(gl_TEXTURE_1D, 0, gl_RGBA, 2048, 0, gl_RGBA, gl_UNSIGNED_BYTE, gli.Ptr(&pixels[0])) + lg.loaded = true +} + +func (lg *LinearGradient) colorAt(pos float32) glColor { + if len(lg.stops) == 0 { + return glColor{} + } else if len(lg.stops) == 1 { + return lg.stops[0].color + } + beforeIdx, afterIdx := -1, -1 + for i, stop := range lg.stops { + if stop.pos > pos { + afterIdx = i + break + } + beforeIdx = i + } + if beforeIdx == -1 { + return lg.stops[0].color + } else if afterIdx == -1 { + return lg.stops[len(lg.stops)-1].color + } + before, after := lg.stops[beforeIdx], lg.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 +} + +func (lg *LinearGradient) AddColorStop(pos float32, color ...interface{}) { + c, _ := parseColor(color...) + insert := len(lg.stops) + for i, stop := range lg.stops { + if stop.pos > pos { + insert = i + break + } + } + lg.stops = append(lg.stops, gradientStop{}) + if insert < len(lg.stops)-1 { + copy(lg.stops[insert+1:], lg.stops[insert:len(lg.stops)-1]) + } + lg.stops[insert] = gradientStop{pos: pos, color: c} + lg.loaded = false +} diff --git a/made_shaders.go b/made_shaders.go index 78ce9ec..943f181 100755 --- a/made_shaders.go +++ b/made_shaders.go @@ -6,6 +6,111 @@ import ( "strings" ) +type linearGradientShader struct { + id uint32 + vertex uint32 + canvasSize int32 + gradient int32 + from int32 + dir int32 + length int32 +} + +func loadLinearGradientShader() (*linearGradientShader, error) { + var vs, fs, program uint32 + + { + csource, freeFunc := gli.Strs(linearGradientVS + "\x00") + defer freeFunc() + + vs = gli.CreateShader(gl_VERTEX_SHADER) + gli.ShaderSource(vs, 1, csource, nil) + gli.CompileShader(vs) + + var logLength int32 + gli.GetShaderiv(vs, gl_INFO_LOG_LENGTH, &logLength) + if logLength > 0 { + shLog := strings.Repeat("\x00", int(logLength+1)) + gli.GetShaderInfoLog(vs, logLength, nil, gli.Str(shLog)) + fmt.Printf("VERTEX_SHADER compilation log:\n\n%s\n", shLog) + } + + var status int32 + gli.GetShaderiv(vs, gl_COMPILE_STATUS, &status) + if status != gl_TRUE { + gli.DeleteShader(vs) + return nil, errors.New("Error compiling GL_VERTEX_SHADER shader part") + } + if glErr := gli.GetError(); glErr != gl_NO_ERROR { + return nil, errors.New("error compiling shader part, glError: " + fmt.Sprint(glErr)) + } + } + + { + csource, freeFunc := gli.Strs(linearGradientFS + "\x00") + defer freeFunc() + + fs = gli.CreateShader(gl_FRAGMENT_SHADER) + gli.ShaderSource(fs, 1, csource, nil) + gli.CompileShader(fs) + + var logLength int32 + gli.GetShaderiv(fs, gl_INFO_LOG_LENGTH, &logLength) + if logLength > 0 { + shLog := strings.Repeat("\x00", int(logLength+1)) + gli.GetShaderInfoLog(fs, logLength, nil, gli.Str(shLog)) + fmt.Printf("FRAGMENT_SHADER compilation log:\n\n%s\n", shLog) + } + + var status int32 + gli.GetShaderiv(fs, gl_COMPILE_STATUS, &status) + if status != gl_TRUE { + gli.DeleteShader(fs) + return nil, errors.New("Error compiling GL_FRAGMENT_SHADER shader part") + } + if glErr := gli.GetError(); glErr != gl_NO_ERROR { + return nil, errors.New("error compiling shader part, glError: " + fmt.Sprint(glErr)) + } + } + + { + program = gli.CreateProgram() + gli.AttachShader(program, vs) + gli.AttachShader(program, fs) + gli.LinkProgram(program) + + var logLength int32 + gli.GetProgramiv(program, gl_INFO_LOG_LENGTH, &logLength) + if logLength > 0 { + shLog := strings.Repeat("\x00", int(logLength+1)) + gli.GetProgramInfoLog(program, logLength, nil, gli.Str(shLog)) + fmt.Printf("Shader link log:\n\n%s\n", shLog) + } + + var status int32 + gli.GetProgramiv(program, gl_LINK_STATUS, &status) + if status != gl_TRUE { + gli.DeleteShader(vs) + gli.DeleteShader(fs) + return nil, errors.New("error linking shader") + } + if glErr := gli.GetError(); glErr != gl_NO_ERROR { + return nil, errors.New("error linking shader, glError: " + fmt.Sprint(glErr)) + } + } + + result := &linearGradientShader{} + result.id = program + result.vertex = uint32(gli.GetAttribLocation(program, gli.Str("vertex\x00"))) + result.canvasSize = gli.GetUniformLocation(program, gli.Str("canvasSize\x00")) + result.gradient = gli.GetUniformLocation(program, gli.Str("gradient\x00")) + result.from = gli.GetUniformLocation(program, gli.Str("from\x00")) + result.dir = gli.GetUniformLocation(program, gli.Str("dir\x00")) + result.length = gli.GetUniformLocation(program, gli.Str("length\x00")) + + return result, nil +} + type solidShader struct { id uint32 vertex uint32