Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Color refactor #93

Merged
merged 1 commit into from
Aug 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 9 additions & 13 deletions backends/opengl/canvas.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
})
Expand Down
26 changes: 26 additions & 0 deletions backends/opengl/color.go
Original file line number Diff line number Diff line change
@@ -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,
}
}
8 changes: 4 additions & 4 deletions backends/opengl/glframe.go
Original file line number Diff line number Diff line change
Expand Up @@ -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],
}
}

Expand Down
8 changes: 4 additions & 4 deletions backends/opengl/glpicture.go
Original file line number Diff line number Diff line change
Expand Up @@ -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],
}
}
26 changes: 11 additions & 15 deletions backends/opengl/gltriangles.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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.
Expand Down
96 changes: 51 additions & 45 deletions color.go
Original file line number Diff line number Diff line change
@@ -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))
}
}

Expand Down
54 changes: 54 additions & 0 deletions color_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
33 changes: 21 additions & 12 deletions compose.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package pixel

import "errors"
import (
"errors"
"image/color"
)

// ComposeTarget is a BasicTarget capable of Porter-Duff composition.
type ComposeTarget interface {
Expand Down Expand Up @@ -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:
Expand All @@ -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)))
}
Loading