made DrawStyle public for use by backends; changed color to be color.RGBA

This commit is contained in:
Thomas Friedel 2019-02-14 12:38:27 +01:00
parent 1e5180dd65
commit 7dbcd9b420
10 changed files with 132 additions and 76 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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