From 7ddf13330bca2429267acf32bd54735fd4cc5c83 Mon Sep 17 00:00:00 2001 From: Allen Ray Date: Mon, 12 Aug 2024 20:04:22 -0400 Subject: [PATCH] Refactor out pixel.RGBA Stuff Revert "Refactor out pixel.RGBA" This reverts commit d029ce8204ffaaf4f351d9de392870d5add46758. Reworked to keep pixel.RGBA around, but an alias working on color more color work --- backends/opengl/canvas.go | 22 ++++---- backends/opengl/color.go | 26 +++++++++ backends/opengl/glframe.go | 8 +-- backends/opengl/glpicture.go | 8 +-- backends/opengl/gltriangles.go | 26 ++++----- color.go | 96 ++++++++++++++++++---------------- color_test.go | 54 +++++++++++++++++++ compose.go | 33 +++++++----- ext/imdraw/imdraw.go | 7 +-- go.mod | 13 ++--- go.sum | 26 ++++----- sprite.go | 4 +- 12 files changed, 205 insertions(+), 118 deletions(-) create mode 100644 backends/opengl/color.go create mode 100644 color_test.go diff --git a/backends/opengl/canvas.go b/backends/opengl/canvas.go index 34d452c..9a5ca7e 100644 --- a/backends/opengl/canvas.go +++ b/backends/opengl/canvas.go @@ -193,24 +193,20 @@ func setBlendFunc(cmp pixel.ComposeMethod) { func (c *Canvas) Clear(color color.Color) { c.gf.Dirty() - rgba := pixel.ToRGBA(color) - - // color masking - rgba = rgba.Mul(pixel.RGBA{ - R: float64(c.col[0]), - G: float64(c.col[1]), - B: float64(c.col[2]), - A: float64(c.col[3]), - }) + r, g, b, a := pixel.ColorToFloats[float32]( + pixel.ToRGBA(color).Mul( + pixel.FloatsToColor(c.col[0], c.col[1], c.col[2], c.col[3]), + ), + ) mainthread.CallNonBlock(func() { c.setGlhfBounds() c.gf.Frame().Begin() glhf.Clear( - float32(rgba.R), - float32(rgba.G), - float32(rgba.B), - float32(rgba.A), + r, + g, + b, + a, ) c.gf.Frame().End() }) diff --git a/backends/opengl/color.go b/backends/opengl/color.go new file mode 100644 index 0000000..22dde74 --- /dev/null +++ b/backends/opengl/color.go @@ -0,0 +1,26 @@ +package opengl + +import ( + "image/color" + + "github.com/go-gl/mathgl/mgl32" +) + +type GLColor mgl32.Vec4 + +func (c GLColor) RGBA() (r, g, b, a uint32) { + return uint32(c[0] * 0xffff), uint32(c[1] * 0xffff), uint32(c[2] * 0xffff), uint32(c[3] * 0xffff) +} + +func ToGLColor(col color.Color) GLColor { + if c, ok := col.(GLColor); ok { + return c + } + r, g, b, a := col.RGBA() + return GLColor{ + float32(r) / 0xffff, + float32(g) / 0xffff, + float32(b) / 0xffff, + float32(a) / 0xffff, + } +} diff --git a/backends/opengl/glframe.go b/backends/opengl/glframe.go index f943ea1..6b80dc1 100644 --- a/backends/opengl/glframe.go +++ b/backends/opengl/glframe.go @@ -79,10 +79,10 @@ func (gf *GLFrame) Color(at pixel.Vec) pixel.RGBA { x, y := int(at.X)-bx, int(at.Y)-by off := y*bw + x return pixel.RGBA{ - R: float64(gf.pixels[off*4+0]) / 255, - G: float64(gf.pixels[off*4+1]) / 255, - B: float64(gf.pixels[off*4+2]) / 255, - A: float64(gf.pixels[off*4+3]) / 255, + R: gf.pixels[off*4+0], + G: gf.pixels[off*4+1], + B: gf.pixels[off*4+2], + A: gf.pixels[off*4+3], } } diff --git a/backends/opengl/glpicture.go b/backends/opengl/glpicture.go index 3f16504..4065702 100644 --- a/backends/opengl/glpicture.go +++ b/backends/opengl/glpicture.go @@ -90,9 +90,9 @@ func (gp *glPicture) Color(at pixel.Vec) pixel.RGBA { x, y := int(at.X)-bx, int(at.Y)-by off := y*bw + x return pixel.RGBA{ - R: float64(gp.pixels[off*4+0]) / 255, - G: float64(gp.pixels[off*4+1]) / 255, - B: float64(gp.pixels[off*4+2]) / 255, - A: float64(gp.pixels[off*4+3]) / 255, + R: gp.pixels[off*4+0], + G: gp.pixels[off*4+1], + B: gp.pixels[off*4+2], + A: gp.pixels[off*4+3], } } diff --git a/backends/opengl/gltriangles.go b/backends/opengl/gltriangles.go index 7220d4a..2375921 100644 --- a/backends/opengl/gltriangles.go +++ b/backends/opengl/gltriangles.go @@ -127,19 +127,20 @@ func (gt *GLTriangles) updateData(t pixel.Triangles) { if t, ok := t.(*pixel.TrianglesData); ok { for i := 0; i < length; i++ { var ( - px, py = (*t)[i].Position.XY() - col = (*t)[i].Color - tx, ty = (*t)[i].Picture.XY() - in = (*t)[i].Intensity - rec = (*t)[i].ClipRect + px, py = (*t)[i].Position.XY() + col = (*t)[i].Color + tx, ty = (*t)[i].Picture.XY() + in = (*t)[i].Intensity + rec = (*t)[i].ClipRect + r, g, b, a = pixel.ColorToFloats[float32](col) ) d := gt.data[i*stride : i*stride+trisAttrLen] d[triPosX] = float32(px) d[triPosY] = float32(py) - d[triColorR] = float32(col.R) - d[triColorG] = float32(col.G) - d[triColorB] = float32(col.B) - d[triColorA] = float32(col.A) + d[triColorR] = r + d[triColorG] = g + d[triColorB] = b + d[triColorA] = a d[triPicX] = float32(tx) d[triPicY] = float32(ty) d[triIntensity] = float32(in) @@ -251,12 +252,7 @@ func (gt *GLTriangles) Color(i int) pixel.RGBA { g := gt.data[gt.index(i, triColorG)] b := gt.data[gt.index(i, triColorB)] a := gt.data[gt.index(i, triColorA)] - return pixel.RGBA{ - R: float64(r), - G: float64(g), - B: float64(b), - A: float64(a), - } + return pixel.FloatsToColor(r, g, b, a) } // SetColor sets the color property of the i-th vertex. diff --git a/color.go b/color.go index 4b41af3..5611096 100644 --- a/color.go +++ b/color.go @@ -1,91 +1,97 @@ package pixel -import "image/color" +import ( + "image/color" + + "golang.org/x/exp/constraints" +) // RGBA represents an alpha-premultiplied RGBA color with components within range [0, 1]. // // The difference between color.RGBA is that the value range is [0, 1] and the values are floats. -type RGBA struct { - R, G, B, A float64 -} +type RGBA color.RGBA // RGB returns a fully opaque RGBA color with the given RGB values. // // A common way to construct a transparent color is to create one with RGB constructor, then // multiply it by a color obtained from the Alpha constructor. func RGB(r, g, b float64) RGBA { - return RGBA{r, g, b, 1} + return RGBA{R: uint8(r * 255), G: uint8(g * 255), B: uint8(b * 255), A: 255} } // Alpha returns a white RGBA color with the given alpha component. func Alpha(a float64) RGBA { - return RGBA{a, a, a, a} + A := uint8(a * 255) + return RGBA{A, A, A, A} } // Add adds color d to color c component-wise and returns the result (the components are not // clamped). -func (c RGBA) Add(d RGBA) RGBA { +func (c RGBA) Add(d color.Color) RGBA { + rgba := ToRGBA(d) return RGBA{ - R: c.R + d.R, - G: c.G + d.G, - B: c.B + d.B, - A: c.A + d.A, + R: c.R + rgba.R, + G: c.G + rgba.G, + B: c.B + rgba.B, + A: c.A + rgba.A, } } // Sub subtracts color d from color c component-wise and returns the result (the components // are not clamped). -func (c RGBA) Sub(d RGBA) RGBA { +func (c RGBA) Sub(d color.Color) RGBA { + rgba := ToRGBA(d) return RGBA{ - R: c.R - d.R, - G: c.G - d.G, - B: c.B - d.B, - A: c.A - d.A, + R: c.R - rgba.R, + G: c.G - rgba.G, + B: c.B - rgba.B, + A: c.A - rgba.A, } } // Mul multiplies color c by color d component-wise (the components are not clamped). -func (c RGBA) Mul(d RGBA) RGBA { - return RGBA{ - R: c.R * d.R, - G: c.G * d.G, - B: c.B * d.B, - A: c.A * d.A, - } +func (c RGBA) Mul(d color.Color) RGBA { + r1, g1, b1, a1 := ColorToFloats[float64](c) + r2, g2, b2, a2 := ColorToFloats[float64](d) + return FloatsToColor(r1*r2, g1*g2, b1*b2, a1*a2) } -// Scaled multiplies each component of color c by scale and returns the result (the components -// are not clamped). +// Scaled multiplies each component of color c by scale and returns the result. func (c RGBA) Scaled(scale float64) RGBA { + r, g, b, a := ColorToFloats[float64](c) return RGBA{ - R: c.R * scale, - G: c.G * scale, - B: c.B * scale, - A: c.A * scale, + R: uint8(r * scale), + G: uint8(g * scale), + B: uint8(b * scale), + A: uint8(a * scale), } } -// RGBA returns alpha-premultiplied red, green, blue and alpha components of the RGBA color. +// RGBA returns components of the color. func (c RGBA) RGBA() (r, g, b, a uint32) { - r = uint32(0xffff * c.R) - g = uint32(0xffff * c.G) - b = uint32(0xffff * c.B) - a = uint32(0xffff * c.A) - return + return color.RGBA(c).RGBA() } -// ToRGBA converts a color to RGBA format. Using this function is preferred to using RGBAModel, for -// performance (using RGBAModel introduces additional unnecessary allocations). +// ColorToFloats converts a color to float32 or float64 components. +func ColorToFloats[F constraints.Float](c color.Color) (r, g, b, a F) { + r1, g1, b1, a1 := c.RGBA() + return F(r1) / 0xffff, F(g1) / 0xffff, F(b1) / 0xffff, F(a1) / 0xffff +} + +// FloatsToColor converts float32 or float64 components to a color. +func FloatsToColor[F constraints.Float](r, g, b, a F) RGBA { + return RGBA{uint8(r * F(255)), uint8(g * F(255)), uint8(b * F(255)), uint8(a * F(255))} +} + +// ToRGBA converts a color to RGBA format. func ToRGBA(c color.Color) RGBA { - if c, ok := c.(RGBA); ok { + switch c := c.(type) { + case RGBA: return c - } - r, g, b, a := c.RGBA() - return RGBA{ - float64(r) / 0xffff, - float64(g) / 0xffff, - float64(b) / 0xffff, - float64(a) / 0xffff, + case color.RGBA: + return RGBA(c) + default: + return RGBA(color.RGBAModel.Convert(c).(color.RGBA)) } } diff --git a/color_test.go b/color_test.go new file mode 100644 index 0000000..d9b3040 --- /dev/null +++ b/color_test.go @@ -0,0 +1,54 @@ +package pixel + +import ( + "image/color" + "reflect" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestColorConversions(t *testing.T) { + c0 := RGBA{64, 127, 128, 255} + c1 := FloatsToColor(ColorToFloats[float32](c0)) + require.Equal(t, c0, c1) +} + +func TestToRGBA(t *testing.T) { + type args struct { + c color.Color + } + tests := []struct { + name string + args args + want RGBA + }{ + { + name: "pixel.rgba", + args: args{c: RGBA{64, 127, 128, 255}}, + want: RGBA{64, 127, 128, 255}, + }, + { + name: "color.rgba", + args: args{c: color.RGBA{64, 127, 128, 255}}, + want: RGBA{64, 127, 128, 255}, + }, + { + name: "color.nrgba", + args: args{c: color.NRGBA{64, 127, 128, 255}}, + want: RGBA{64, 127, 128, 255}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := ToRGBA(tt.args.c); !reflect.DeepEqual(got, tt.want) { + t.Errorf("ToRGBA() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestRGB(t *testing.T) { + rgba := RGB(0.25, 0.5, 0.75) + require.Equal(t, RGBA{63, 127, 191, 255}, rgba) +} diff --git a/compose.go b/compose.go index a4a8783..9b1f630 100644 --- a/compose.go +++ b/compose.go @@ -1,6 +1,9 @@ package pixel -import "errors" +import ( + "errors" + "image/color" +) // ComposeTarget is a BasicTarget capable of Porter-Duff composition. type ComposeTarget interface { @@ -31,28 +34,34 @@ const ( // Compose composes two colors together according to the ComposeMethod. A is the foreground, B is // the background. -func (cm ComposeMethod) Compose(a, b RGBA) RGBA { +func (cm ComposeMethod) Compose(a, b color.Color) RGBA { var fa, fb float64 + ac := ToRGBA(a) + bc := ToRGBA(b) + + aa := float64(ac.A) / 255 + ba := float64(bc.A) / 255 + switch cm { case ComposeOver: - fa, fb = 1, 1-a.A + fa, fb = 1, 1-aa case ComposeIn: - fa, fb = b.A, 0 + fa, fb = ba, 0 case ComposeOut: - fa, fb = 1-b.A, 0 + fa, fb = 1-ba, 0 case ComposeAtop: - fa, fb = b.A, 1-a.A + fa, fb = ba, 1-aa case ComposeRover: - fa, fb = 1-b.A, 1 + fa, fb = 1-ba, 1 case ComposeRin: - fa, fb = 0, a.A + fa, fb = 0, aa case ComposeRout: - fa, fb = 0, 1-a.A + fa, fb = 0, 1-aa case ComposeRatop: - fa, fb = 1-b.A, a.A + fa, fb = 1-ba, aa case ComposeXor: - fa, fb = 1-b.A, 1-a.A + fa, fb = 1-ba, 1-aa case ComposePlus: fa, fb = 1, 1 case ComposeCopy: @@ -61,5 +70,5 @@ func (cm ComposeMethod) Compose(a, b RGBA) RGBA { panic(errors.New("Compose: invalid ComposeMethod")) } - return a.Mul(Alpha(fa)).Add(b.Mul(Alpha(fb))) + return ac.Mul(Alpha(fa)).Add(bc.Mul(Alpha(fb))) } diff --git a/ext/imdraw/imdraw.go b/ext/imdraw/imdraw.go index d216836..50e8e0b 100644 --- a/ext/imdraw/imdraw.go +++ b/ext/imdraw/imdraw.go @@ -128,13 +128,8 @@ func (imd *IMDraw) Draw(t pixel.Target) { // Push adds some points to the IM queue. All Pushed points will have the same properties except for // the position. func (imd *IMDraw) Push(pts ...pixel.Vec) { - // Assert that Color is of type pixel.RGBA, - if _, ok := imd.Color.(pixel.RGBA); !ok { - // otherwise cast it - imd.Color = pixel.ToRGBA(imd.Color) - } opts := point{ - col: imd.Color.(pixel.RGBA), + col: pixel.ToRGBA(imd.Color), pic: imd.Picture, in: imd.Intensity, precision: imd.Precision, diff --git a/go.mod b/go.mod index 1966237..bed8e46 100644 --- a/go.mod +++ b/go.mod @@ -3,15 +3,16 @@ module github.com/gopxl/pixel/v2 go 1.21 require ( - github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 - github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b + github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 + github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a github.com/go-gl/mathgl v1.1.0 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 - github.com/gopxl/glhf/v2 v2.0.0 - github.com/gopxl/mainthread/v2 v2.0.0 + github.com/gopxl/glhf/v2 v2.1.0 + github.com/gopxl/mainthread/v2 v2.1.1 github.com/pkg/errors v0.9.1 - github.com/stretchr/testify v1.8.4 - golang.org/x/image v0.13.0 + github.com/stretchr/testify v1.9.0 + golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa + golang.org/x/image v0.19.0 ) require ( diff --git a/go.sum b/go.sum index b86c224..22c04d0 100644 --- a/go.sum +++ b/go.sum @@ -1,26 +1,28 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 h1:zDw5v7qm4yH7N8C8uWd+8Ii9rROdgWxQuGoJ9WDXxfk= -github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b h1:GgabKamyOYguHqHjSkDACcgoPIz3w0Dis/zJ1wyHHHU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 h1:5BVwOaUSBTlVZowGO6VZGw2H/zl9nrd3eCZfYV+NfQA= +github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a h1:vxnBhFDDT+xzxf1jTJKMKZw3H0swfWk9RpWbBbDK5+0= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/mathgl v1.1.0 h1:0lzZ+rntPX3/oGrDzYGdowSLC2ky8Osirvf5uAwfIEA= github.com/go-gl/mathgl v1.1.0/go.mod h1:yhpkQzEiH9yPyxDUGzkmgScbaBVlhC06qodikEM0ZwQ= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= -github.com/gopxl/glhf/v2 v2.0.0 h1:SJtNy+TXuTBRjMersNx722VDJ0XHIooMH2+7+99LPIc= -github.com/gopxl/glhf/v2 v2.0.0/go.mod h1:InKwj5OoVdOAkpzsS0ILwpB+RrWBLw1i7aFefiGmrp8= -github.com/gopxl/mainthread/v2 v2.0.0 h1:jRbeWFzX6/UyhRab00xS3xIVYywBgc0DgwPgwS6EVYw= -github.com/gopxl/mainthread/v2 v2.0.0/go.mod h1:/uFQhUiSP53SSU/RQ5w0FFkljRArJlaQkDPza3zE2V8= +github.com/gopxl/glhf/v2 v2.1.0 h1:IUydEPsxFVsfEu7mIWJBc9KSy/B5aXOXW/uHyRKUiAg= +github.com/gopxl/glhf/v2 v2.1.0/go.mod h1:InKwj5OoVdOAkpzsS0ILwpB+RrWBLw1i7aFefiGmrp8= +github.com/gopxl/mainthread/v2 v2.1.1 h1:S7jIvQZth9s2k8qFePOxtEgtZLzW/Yjykum2mscGr0o= +github.com/gopxl/mainthread/v2 v2.1.1/go.mod h1:RLdqSRamocAGPzK9P4HsZf+WXL5bfHHtX78O6GkKaUw= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI= +golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= golang.org/x/image v0.0.0-20190321063152-3fc05d484e9f/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.13.0 h1:3cge/F/QTkNLauhf2QoE9zp+7sr+ZcL4HnoZmdwg9sg= -golang.org/x/image v0.13.0/go.mod h1:6mmbMOeV28HuMTgA6OSRkdXKYw/t5W9Uwn2Yv1r3Yxk= +golang.org/x/image v0.19.0 h1:D9FX4QWkLfkeqaC62SonffIIuYdOk/UE2XKUBgRIBIQ= +golang.org/x/image v0.19.0/go.mod h1:y0zrRqlQRWQ5PXaYCOMLTW2fpsxZ8Qh9I/ohnInJEys= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/sprite.go b/sprite.go index 7adc845..a7e2019 100644 --- a/sprite.go +++ b/sprite.go @@ -1,6 +1,8 @@ package pixel -import "image/color" +import ( + "image/color" +) // Sprite is a drawable frame of a Picture. It's anchored by the center of it's Picture's frame. //