diff --git a/backend/goglbackend/gogl.go b/backend/gogl/gogl.go similarity index 100% rename from backend/goglbackend/gogl.go rename to backend/gogl/gogl.go diff --git a/backend/goglbackend/rects.go b/backend/gogl/rects.go similarity index 100% rename from backend/goglbackend/rects.go rename to backend/gogl/rects.go diff --git a/backend/goglbackend/shader.go b/backend/gogl/shader.go similarity index 100% rename from backend/goglbackend/shader.go rename to backend/gogl/shader.go diff --git a/backend/goglbackend/shaders.go b/backend/gogl/shaders.go similarity index 100% rename from backend/goglbackend/shaders.go rename to backend/gogl/shaders.go diff --git a/canvas.go b/canvas.go index a04a472..bf23cc7 100644 --- a/canvas.go +++ b/canvas.go @@ -4,6 +4,7 @@ package canvas import ( "fmt" + "image/color" "os" "github.com/golang/freetype/truetype" @@ -35,8 +36,8 @@ type Canvas struct { type drawState struct { transform mat - fill drawStyle - stroke drawStyle + fill DrawStyle + stroke DrawStyle font *Font fontSize float64 fontMetrics font.Metrics @@ -72,11 +73,11 @@ type drawState struct { */ } -type drawStyle struct { - color glColor - radialGradient *RadialGradient - linearGradient *LinearGradient - image *Image +type DrawStyle struct { + Color color.RGBA + RadialGradient *RadialGradient + LinearGradient *LinearGradient + Image *Image } type scissor struct { @@ -146,8 +147,8 @@ func New(backend Backend, x, y, w, h int) *Canvas { cv.state.lineAlpha = 1 cv.state.miterLimitSqr = 100 cv.state.globalAlpha = 1 - cv.state.fill.color = glColor{a: 1} - cv.state.stroke.color = glColor{a: 1} + cv.state.fill.Color = color.RGBA{A: 255} + cv.state.stroke.Color = color.RGBA{A: 255} cv.state.transform = matIdentity() return cv } @@ -454,45 +455,45 @@ func (cv *Canvas) SetStrokeStyle(value ...interface{}) { cv.state.stroke = parseStyle(value...) } -func parseStyle(value ...interface{}) drawStyle { - var style drawStyle +func parseStyle(value ...interface{}) DrawStyle { + var style DrawStyle if len(value) == 1 { switch v := value[0].(type) { case *LinearGradient: - style.linearGradient = v + style.LinearGradient = v return style case *RadialGradient: - style.radialGradient = v + style.RadialGradient = v return style } } c, ok := parseColor(value...) if ok { - style.color = c + style.Color = c } else if len(value) == 1 { switch v := value[0].(type) { case *Image, string: - style.image = getImage(v) + style.Image = getImage(v) } } return style } -func (s *drawStyle) isOpaque() bool { - if lg := s.linearGradient; lg != nil { +func (s *DrawStyle) isOpaque() bool { + if lg := s.LinearGradient; lg != nil { return lg.opaque } - if rg := s.radialGradient; rg != nil { + if rg := s.RadialGradient; rg != nil { return rg.opaque } - if img := s.image; img != nil { + if img := s.Image; img != nil { return img.opaque } - return s.color.a >= 1 + return s.Color.A >= 255 } -func (cv *Canvas) useShader(style *drawStyle) (vertexLoc uint32) { - if lg := style.linearGradient; lg != nil { +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) @@ -510,7 +511,7 @@ func (cv *Canvas) useShader(style *drawStyle) (vertexLoc uint32) { gli.Uniform1f(lgr.globalAlpha, float32(cv.state.globalAlpha)) return lgr.vertex } - if rg := style.radialGradient; rg != nil { + if rg := style.RadialGradient; rg != nil { rg.load() gli.ActiveTexture(gl_TEXTURE0) gli.BindTexture(gl_TEXTURE_2D, rg.tex) @@ -531,7 +532,7 @@ func (cv *Canvas) useShader(style *drawStyle) (vertexLoc uint32) { gli.Uniform1f(rgr.globalAlpha, float32(cv.state.globalAlpha)) return rgr.vertex } - if img := style.image; img != nil { + if img := style.Image; img != nil { gli.UseProgram(ipr.id) gli.ActiveTexture(gl_TEXTURE0) gli.BindTexture(gl_TEXTURE_2D, img.tex) @@ -544,14 +545,14 @@ func (cv *Canvas) useShader(style *drawStyle) (vertexLoc uint32) { gli.UseProgram(sr.id) gli.Uniform2f(sr.canvasSize, float32(cv.fw), float32(cv.fh)) - c := style.color + c := colorGoToGL(style.Color) gli.Uniform4f(sr.color, float32(c.r), float32(c.g), float32(c.b), float32(c.a)) gli.Uniform1f(sr.globalAlpha, float32(cv.state.globalAlpha)) return sr.vertex } -func (cv *Canvas) useAlphaShader(style *drawStyle, alphaTexSlot int32) (vertexLoc, alphaTexCoordLoc uint32) { - if lg := style.linearGradient; lg != nil { +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) @@ -570,7 +571,7 @@ func (cv *Canvas) useAlphaShader(style *drawStyle, alphaTexSlot int32) (vertexLo gli.Uniform1f(lgar.globalAlpha, float32(cv.state.globalAlpha)) return lgar.vertex, lgar.alphaTexCoord } - if rg := style.radialGradient; rg != nil { + if rg := style.RadialGradient; rg != nil { rg.load() gli.ActiveTexture(gl_TEXTURE0) gli.BindTexture(gl_TEXTURE_2D, rg.tex) @@ -592,7 +593,7 @@ func (cv *Canvas) useAlphaShader(style *drawStyle, alphaTexSlot int32) (vertexLo gli.Uniform1f(rgar.globalAlpha, float32(cv.state.globalAlpha)) return rgar.vertex, rgar.alphaTexCoord } - if img := style.image; img != nil { + if img := style.Image; img != nil { gli.UseProgram(ipar.id) gli.ActiveTexture(gl_TEXTURE0) gli.BindTexture(gl_TEXTURE_2D, img.tex) @@ -606,7 +607,7 @@ func (cv *Canvas) useAlphaShader(style *drawStyle, alphaTexSlot int32) (vertexLo gli.UseProgram(sar.id) gli.Uniform2f(sar.canvasSize, float32(cv.fw), float32(cv.fh)) - c := style.color + c := colorGoToGL(style.Color) gli.Uniform4f(sar.color, float32(c.r), float32(c.g), float32(c.b), float32(c.a)) gli.Uniform1i(sar.alphaTex, alphaTexSlot) gli.Uniform1f(sar.globalAlpha, float32(cv.state.globalAlpha)) @@ -834,7 +835,7 @@ func (cv *Canvas) SetTransform(a, b, c, d, e, f float64) { // then no shadow is drawn func (cv *Canvas) SetShadowColor(color ...interface{}) { if c, ok := parseColor(color...); ok { - cv.state.shadowColor = c + cv.state.shadowColor = colorGoToGL(c) } } diff --git a/canvas_test.go b/canvas_test.go index e34bcae..0832901 100644 --- a/canvas_test.go +++ b/canvas_test.go @@ -2,6 +2,7 @@ package canvas_test import ( "fmt" + "image" "image/png" "math" "os" @@ -23,7 +24,7 @@ func run(t *testing.T, fn func(cv *canvas.Canvas)) { } defer wnd.Destroy() - cv := canvas.NewOffscreen(100, 100, false) + cv := canvas.NewOffscreen(wnd.Backend, 100, 100, false) gl.Disable(gl.MULTISAMPLE) @@ -58,15 +59,9 @@ func run(t *testing.T, fn func(cv *canvas.Canvas)) { } if os.IsNotExist(err) { - f, err := os.OpenFile(fileName, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0777) + err = writeImage(img, fileName) if err != nil { - t.Fatalf("Failed to create file \"%s\"", fileName) - } - defer f.Close() - - err = png.Encode(f, img) - if err != nil { - t.Fatalf("Failed to encode PNG") + t.Fatal(err) } return } @@ -95,15 +90,31 @@ func run(t *testing.T, fn func(cv *canvas.Canvas)) { r2, g2, b2, a2 := img2.At(x, y).RGBA() r3, g3, b3, a3 := refImg.At(x, y).RGBA() if r1 != r3 || g1 != g3 || b1 != b3 || a1 != a3 { + writeImage(img, fmt.Sprintf("testdata/%s_fail.png", callerFuncName)) t.FailNow() } if r2 != r3 || g2 != g3 || b2 != b3 || a2 != a3 { + writeImage(img2, fmt.Sprintf("testdata/%s_fail.png", callerFuncName)) t.FailNow() } } } } +func writeImage(img *image.RGBA, fileName string) error { + f, err := os.OpenFile(fileName, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0777) + if err != nil { + return fmt.Errorf("Failed to create file \"%s\"", fileName) + } + defer f.Close() + + err = png.Encode(f, img) + if err != nil { + return fmt.Errorf("Failed to encode PNG") + } + return nil +} + func TestFillRect(t *testing.T) { run(t, func(cv *canvas.Canvas) { cv.SetFillStyle("#F00") diff --git a/color.go b/color.go index da2537d..af8f1b9 100644 --- a/color.go +++ b/color.go @@ -3,6 +3,7 @@ package canvas import ( "fmt" "image/color" + "math" "strconv" "strings" ) @@ -21,7 +22,7 @@ func colorGoToGL(color color.Color) glColor { return c } -func colorGLToGo(c glColor) color.Color { +func colorGLToGo(c glColor) color.RGBA { if c.r < 0 { c.r = 0 } else if c.r > 1 { @@ -74,18 +75,18 @@ func parseHexRunePair(rn1, rn2 rune) (int, bool) { return i1*16 + i2, true } -func parseColorComponent(value interface{}) (float64, bool) { +func parseColorComponent(value interface{}) (uint8, bool) { switch v := value.(type) { case float32: - return float64(v), true + return uint8(math.Floor(float64(v) * 255)), true case float64: - return v, true + return uint8(math.Floor(v * 255)), true case int: - return float64(v) / 255, true + return uint8(v), true case uint: - return float64(v) / 255, true + return uint8(v), true case uint8: - return float64(v) / 255, true + return v, true case string: if len(v) == 0 { return 0, false @@ -99,7 +100,7 @@ func parseColorComponent(value interface{}) (float64, bool) { if err != nil { return 0, false } - return float64(conv) / 255, true + return uint8(conv), true } else if strings.ContainsRune(v, '.') { conv, err := strconv.ParseFloat(v, 32) if err != nil { @@ -110,45 +111,78 @@ func parseColorComponent(value interface{}) (float64, bool) { } else if conv > 1 { conv = 1 } - return float64(conv), true + return uint8(conv), true } else { conv, err := strconv.ParseUint(v, 10, 8) if err != nil { return 0, false } - return float64(conv) / 255, true + return uint8(conv), true } } return 0, false } -func parseColor(value ...interface{}) (c glColor, ok bool) { +func parseColor(value ...interface{}) (c color.RGBA, ok bool) { if len(value) == 1 { switch v := value[0].(type) { case color.Color: - c = colorGoToGL(v) + r, g, b, a := v.RGBA() + c = color.RGBA{R: uint8(r >> 8), G: uint8(g >> 8), B: uint8(b >> 8), A: uint8(a >> 8)} ok = true return case [3]float32: - return glColor{r: float64(v[0]), g: float64(v[1]), b: float64(v[2]), a: 1}, true + return color.RGBA{ + R: uint8(math.Floor(float64(v[0] * 255))), + G: uint8(math.Floor(float64(v[1] * 255))), + B: uint8(math.Floor(float64(v[2] * 255))), + A: 255}, true case [4]float32: - return glColor{r: float64(v[0]), g: float64(v[1]), b: float64(v[2]), a: float64(v[3])}, true + return color.RGBA{ + R: uint8(math.Floor(float64(v[0] * 255))), + G: uint8(math.Floor(float64(v[1] * 255))), + B: uint8(math.Floor(float64(v[2] * 255))), + A: uint8(math.Floor(float64(v[3] * 255)))}, true case [3]float64: - return glColor{r: v[0], g: v[1], b: v[2], a: 1}, true + return color.RGBA{ + R: uint8(math.Floor(v[0] * 255)), + G: uint8(math.Floor(v[1] * 255)), + B: uint8(math.Floor(v[2] * 255)), + A: 255}, true case [4]float64: - return glColor{r: v[0], g: v[1], b: v[2], a: v[3]}, true + return color.RGBA{ + R: uint8(math.Floor(v[0] * 255)), + G: uint8(math.Floor(v[1] * 255)), + B: uint8(math.Floor(v[2] * 255)), + A: uint8(math.Floor(v[3] * 255))}, true case [3]int: - return glColor{r: float64(v[0]) / 255, g: float64(v[1]) / 255, b: float64(v[2]) / 255, a: 1}, true + return color.RGBA{ + R: uint8(v[0]), + G: uint8(v[1]), + B: uint8(v[2]), + A: 255}, true case [4]int: - return glColor{r: float64(v[0]) / 255, g: float64(v[1]) / 255, b: float64(v[2]) / 255, a: float64(v[3]) / 255}, true + return color.RGBA{ + R: uint8(v[0]), + G: uint8(v[1]), + B: uint8(v[2]), + A: uint8(v[3])}, true case [3]uint: - return glColor{r: float64(v[0]) / 255, g: float64(v[1]) / 255, b: float64(v[2]) / 255, a: 1}, true + return color.RGBA{ + R: uint8(v[0]), + G: uint8(v[1]), + B: uint8(v[2]), + A: 255}, true case [4]uint: - return glColor{r: float64(v[0]) / 255, g: float64(v[1]) / 255, b: float64(v[2]) / 255, a: float64(v[3]) / 255}, true + return color.RGBA{ + R: uint8(v[0]), + G: uint8(v[1]), + B: uint8(v[2]), + A: uint8(v[3])}, true case [3]uint8: - return glColor{r: float64(v[0]) / 255, g: float64(v[1]) / 255, b: float64(v[2]) / 255, a: 1}, true + return color.RGBA{R: v[0], G: v[1], B: v[2], A: 255}, true case [4]uint8: - return glColor{r: float64(v[0]) / 255, g: float64(v[1]) / 255, b: float64(v[2]) / 255, a: float64(v[3]) / 255}, true + return color.RGBA{R: v[0], G: v[1], B: v[2], A: v[3]}, true case string: if len(v) == 0 { return @@ -180,7 +214,7 @@ func parseColor(value ...interface{}) (c glColor, ok bool) { } ia = ia*16 + ia } - return glColor{r: float64(ir) / 255, g: float64(ig) / 255, b: float64(ib) / 255, a: float64(ia) / 255}, true + return color.RGBA{R: uint8(ir), G: uint8(ig), B: uint8(ib), A: uint8(ia)}, true } else if len(str) == 6 || len(str) == 8 { var ir, ig, ib int ia := 255 @@ -202,7 +236,7 @@ func parseColor(value ...interface{}) (c glColor, ok bool) { return } } - return glColor{r: float64(ir) / 255, g: float64(ig) / 255, b: float64(ib) / 255, a: float64(ia) / 255}, true + return color.RGBA{R: uint8(ir), G: uint8(ig), B: uint8(ib), A: uint8(ia)}, true } else { return } @@ -211,16 +245,16 @@ func parseColor(value ...interface{}) (c glColor, ok bool) { var ir, ig, ib, ia int n, err := fmt.Sscanf(v, "rgb(%d,%d,%d)", &ir, &ig, &ib) if err == nil && n == 3 { - return glColor{r: float64(ir) / 255, g: float64(ig) / 255, b: float64(ib) / 255, a: 1}, true + return color.RGBA{R: uint8(ir), G: uint8(ig), B: uint8(ib), A: 255}, true } n, err = fmt.Sscanf(v, "rgba(%d,%d,%d,%d)", &ir, &ig, &ib, &ia) if err == nil && n == 4 { - return glColor{r: float64(ir) / 255, g: float64(ig) / 255, b: float64(ib) / 255, a: float64(ia) / 255}, true + return color.RGBA{R: uint8(ir), G: uint8(ig), B: uint8(ib), A: uint8(ia)}, true } } } } else if len(value) == 3 || len(value) == 4 { - var pr, pg, pb, pa float64 + var pr, pg, pb, pa uint8 pr, ok = parseColorComponent(value[0]) if !ok { return @@ -239,10 +273,10 @@ func parseColor(value ...interface{}) (c glColor, ok bool) { return } } else { - pa = 1 + pa = 255 } - return glColor{r: pr, g: pg, b: pb, a: pa}, true + return color.RGBA{R: pr, G: pg, B: pb, A: pa}, true } - return glColor{r: 0, g: 0, b: 0, a: 1}, false + return color.RGBA{A: 255}, false } diff --git a/gradients.go b/gradients.go index 5d19b19..abd7aa7 100644 --- a/gradients.go +++ b/gradients.go @@ -143,7 +143,8 @@ func (g *gradient) colorAt(pos float64) glColor { // don't have to be added in order, they are sorted into the // right place func (g *gradient) AddColorStop(pos float64, color ...interface{}) { - c, _ := parseColor(color...) + gc, _ := parseColor(color...) + c := colorGoToGL(gc) if c.a < 1 { g.opaque = false } diff --git a/sdlcanvas/sdlcanvas.go b/sdlcanvas/sdlcanvas.go index c7f044d..18b3229 100644 --- a/sdlcanvas/sdlcanvas.go +++ b/sdlcanvas/sdlcanvas.go @@ -5,12 +5,14 @@ import ( _ "image/gif" // Imported here so that applications based on this package support these formats by default _ "image/jpeg" _ "image/png" + "log" "runtime" "time" "unicode/utf8" "github.com/go-gl/gl/v3.2-core/gl" "github.com/tfriedel6/canvas" + "github.com/tfriedel6/canvas/backend/gogl" "github.com/tfriedel6/canvas/glimpl/gogl" "github.com/veandco/go-sdl2/sdl" ) @@ -21,6 +23,7 @@ type Window struct { Window *sdl.Window WindowID uint32 GLContext sdl.GLContext + Backend canvas.Backend canvas *canvas.Canvas frameTimes [10]time.Time frameIndex int @@ -86,6 +89,11 @@ func CreateWindow(w, h int, title string) (*Window, *canvas.Canvas, error) { return nil, nil, fmt.Errorf("Error initializing GL: %v", err) } + backend, err := goglbackend.New(0, 0, 1280, 720) + if err != nil { + log.Fatalf("Error loading GoGL backend: %v", err) + } + sdl.GLSetSwapInterval(1) gl.Enable(gl.MULTISAMPLE) @@ -94,11 +102,12 @@ func CreateWindow(w, h int, title string) (*Window, *canvas.Canvas, error) { return nil, nil, fmt.Errorf("Error loading canvas GL assets: %v", err) } - cv := canvas.New(0, 0, w, h) + cv := canvas.New(backend, 0, 0, w, h) wnd := &Window{ Window: window, WindowID: windowID, GLContext: glContext, + Backend: backend, canvas: cv, events: make([]sdl.Event, 0, 100), } diff --git a/shadows.go b/shadows.go index eebba96..eb3898b 100644 --- a/shadows.go +++ b/shadows.go @@ -47,8 +47,8 @@ func (cv *Canvas) drawShadow(tris []float32) { gli.StencilFunc(gl_EQUAL, 1, 0xFF) - var style drawStyle - style.color = cv.state.shadowColor + var style DrawStyle + style.Color = colorGLToGo(cv.state.shadowColor) vertex := cv.useShader(&style) gli.EnableVertexAttribArray(vertex) @@ -82,8 +82,8 @@ func (cv *Canvas) drawTextShadow(offset image.Point, strWidth, strHeight int, x, gli.BindBuffer(gl_ARRAY_BUFFER, buf) - var style drawStyle - style.color = cv.state.shadowColor + var style DrawStyle + style.Color = colorGLToGo(cv.state.shadowColor) vertex, alphaTexCoord := cv.useAlphaShader(&style, 1)