all: use color.NRGBA in public API

color.RGBA has two problems with regards to using it.

First the color values need to be premultiplied, whereas most APIs
have non-premultiplied values. This is mainly to preserve color components
with low alpha values.

Second there are two ways to premultiply with sRGB. One is to premultiply
after sRGB conversion, the other is before. This makes using the API more
confusing.

Using color.NRGBA in sRGB makes it align with CSS.e

Signed-off-by: Egon Elbre <egonelbre@gmail.com>
This commit is contained in:
Egon Elbre
2020-11-18 20:21:26 +02:00
committed by Elias Naur
parent 9469d18907
commit 21ef492cc9
23 changed files with 200 additions and 157 deletions
+53 -23
View File
@@ -7,7 +7,7 @@ import (
"math"
)
// RGBA is a 32 bit floating point linear space color.
// RGBA is a 32 bit floating point linear premultiplied color space.
type RGBA struct {
R, G, B, A float32
}
@@ -23,11 +23,14 @@ func (col RGBA) Float32() (r, g, b, a float32) {
}
// SRGBA converts from linear to sRGB color space.
func (col RGBA) SRGB() color.RGBA {
return color.RGBA{
R: uint8(linearTosRGB(col.R)*255 + .5),
G: uint8(linearTosRGB(col.G)*255 + .5),
B: uint8(linearTosRGB(col.B)*255 + .5),
func (col RGBA) SRGB() color.NRGBA {
if col.A == 0 {
return color.NRGBA{}
}
return color.NRGBA{
R: uint8(linearTosRGB(col.R/col.A)*255 + .5),
G: uint8(linearTosRGB(col.G/col.A)*255 + .5),
B: uint8(linearTosRGB(col.B/col.A)*255 + .5),
A: uint8(col.A*255 + .5),
}
}
@@ -38,17 +41,50 @@ func (col RGBA) Opaque() RGBA {
return col
}
// RGBAFromSRGB converts from SRGBA to RGBA.
func RGBAFromSRGB(col color.RGBA) RGBA {
r, g, b, a := col.RGBA()
// LinearFromSRGB converts from SRGBA to RGBA.
func LinearFromSRGB(col color.NRGBA) RGBA {
af := float32(col.A) / 0xFF
return RGBA{
R: sRGBToLinear(float32(r) / 0xffff),
G: sRGBToLinear(float32(g) / 0xffff),
B: sRGBToLinear(float32(b) / 0xffff),
A: float32(a) / 0xFFFF,
R: sRGBToLinear(float32(col.R)/0xff) * af,
G: sRGBToLinear(float32(col.G)/0xff) * af,
B: sRGBToLinear(float32(col.B)/0xff) * af,
A: af,
}
}
// NRGBAToRGBA converts from non-premultiplied sRGB color to premultiplied sRGB color.
//
// Each component in the result is `sRGBToLinear(c * alpha)`, where `c`
// is the linear color.
func NRGBAToRGBA(col color.NRGBA) color.RGBA {
if col.A == 0xFF {
return color.RGBA(col)
}
c := LinearFromSRGB(col)
return color.RGBA{
R: uint8(linearTosRGB(c.R)*255 + .5),
G: uint8(linearTosRGB(c.G)*255 + .5),
B: uint8(linearTosRGB(c.B)*255 + .5),
A: col.A,
}
}
// RGBAToNRGBA converts from premultiplied sRGB color to non-premultiplied sRGB color.
func RGBAToNRGBA(col color.RGBA) color.NRGBA {
if col.A == 0xFF {
return color.NRGBA(col)
}
linear := RGBA{
R: sRGBToLinear(float32(col.R) / 0xff),
G: sRGBToLinear(float32(col.G) / 0xff),
B: sRGBToLinear(float32(col.B) / 0xff),
A: float32(col.A) / 0xff,
}
return linear.SRGB()
}
// linearTosRGB transforms color value from linear to sRGB.
func linearTosRGB(c float32) float32 {
// Formula from EXT_sRGB.
@@ -74,14 +110,8 @@ func sRGBToLinear(c float32) float32 {
}
}
// MulAlpha scales all color components by alpha/255.
func MulAlpha(c color.RGBA, alpha uint8) color.RGBA {
// TODO: Optimize. This is pretty slow.
a := float32(alpha) / 255.
rgba := RGBAFromSRGB(c)
rgba.A *= a
rgba.R *= a
rgba.G *= a
rgba.B *= a
return rgba.SRGB()
// MulAlpha applies the alpha to the color.
func MulAlpha(c color.NRGBA, alpha uint8) color.NRGBA {
c.A = uint8(uint32(c.A) * uint32(alpha) / 0xFF)
return c
}
+3 -3
View File
@@ -157,7 +157,7 @@ func draw1000Circles(gtx layout.Context) {
op.Offset(f32.Pt(float32(x*10), 0)).Add(ops)
for y := 0; y < 10; y++ {
paint.FillShape(ops,
color.RGBA{R: 100 + uint8(x), G: 100 + uint8(y), B: 100, A: 120},
color.NRGBA{R: 100 + uint8(x), G: 100 + uint8(y), B: 100, A: 120},
clip.RRect{Rect: f32.Rect(0, 0, 10, 10), NE: 5, SE: 5, SW: 5, NW: 5}.Op(ops),
)
op.Offset(f32.Pt(0, float32(100))).Add(ops)
@@ -179,7 +179,7 @@ func draw1000CirclesInstanced(gtx layout.Context) {
op.Offset(f32.Pt(float32(x*10), 0)).Add(ops)
for y := 0; y < 10; y++ {
pi := op.Push(ops)
paint.ColorOp{Color: color.RGBA{R: 100 + uint8(x), G: 100 + uint8(y), B: 100, A: 120}}.Add(ops)
paint.ColorOp{Color: color.NRGBA{R: 100 + uint8(x), G: 100 + uint8(y), B: 100, A: 120}}.Add(ops)
c.Add(ops)
pi.Pop()
op.Offset(f32.Pt(0, float32(100))).Add(ops)
@@ -208,7 +208,7 @@ func drawIndividualShapes(gtx layout.Context, th *material.Theme) chan op.CallOp
op.Offset(f32.Pt(float32(x*50), 0)).Add(ops)
for y := 0; y < 9; y++ {
paint.FillShape(ops,
color.RGBA{R: 100 + uint8(x), G: 100 + uint8(y), B: 100, A: 120},
color.NRGBA{R: 100 + uint8(x), G: 100 + uint8(y), B: 100, A: 120},
clip.RRect{Rect: f32.Rect(0, 0, 25, 25), NE: 10, SE: 10, SW: 10, NW: 10}.Op(ops),
)
op.Offset(f32.Pt(0, float32(50))).Add(ops)
+14 -14
View File
@@ -14,7 +14,7 @@ import (
func TestPaintRect(t *testing.T) {
run(t, func(o *op.Ops) {
paint.FillShape(o, colornames.Red, clip.Rect(image.Rect(0, 0, 50, 50)).Op())
paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 50, 50)).Op())
}, func(r result) {
r.expect(0, 0, colornames.Red)
r.expect(49, 0, colornames.Red)
@@ -26,7 +26,7 @@ func TestPaintRect(t *testing.T) {
func TestPaintClippedRect(t *testing.T) {
run(t, func(o *op.Ops) {
clip.RRect{Rect: f32.Rect(25, 25, 60, 60)}.Add(o)
paint.FillShape(o, colornames.Red, clip.Rect(image.Rect(0, 0, 50, 50)).Op())
paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 50, 50)).Op())
}, func(r result) {
r.expect(0, 0, colornames.White)
r.expect(24, 35, colornames.White)
@@ -41,7 +41,7 @@ func TestPaintClippedCirle(t *testing.T) {
r := float32(10)
clip.RRect{Rect: f32.Rect(20, 20, 40, 40), SE: r, SW: r, NW: r, NE: r}.Add(o)
clip.Rect(image.Rect(0, 0, 30, 50)).Add(o)
paint.Fill(o, colornames.Red)
paint.Fill(o, red)
}, func(r result) {
r.expect(21, 21, colornames.White)
r.expect(25, 30, colornames.Red)
@@ -70,7 +70,7 @@ func TestPaintArc(t *testing.T) {
p.Line(f32.Pt(-50, 0))
p.Outline().Add(o)
paint.FillShape(o, colornames.Red, clip.Rect(image.Rect(0, 0, 128, 128)).Op())
paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 128, 128)).Op())
}, func(r result) {
r.expect(0, 0, colornames.White)
r.expect(0, 25, colornames.Red)
@@ -123,7 +123,7 @@ func TestStrokedPathBevelFlat(t *testing.T) {
p.Quad(f32.Pt(-10, -10), f32.Pt(-30, 30))
p.Stroke(width, sty).Add(o)
paint.Fill(o, colornames.Red)
paint.Fill(o, red)
}, func(r result) {
r.expect(0, 0, colornames.White)
r.expect(10, 50, colornames.Red)
@@ -150,7 +150,7 @@ func TestStrokedPathBevelRound(t *testing.T) {
p.Quad(f32.Pt(-10, -10), f32.Pt(-30, 30))
p.Stroke(width, sty).Add(o)
paint.Fill(o, colornames.Red)
paint.Fill(o, red)
}, func(r result) {
r.expect(0, 0, colornames.White)
r.expect(10, 50, colornames.Red)
@@ -177,7 +177,7 @@ func TestStrokedPathBevelSquare(t *testing.T) {
p.Quad(f32.Pt(-10, -10), f32.Pt(-30, 30))
p.Stroke(width, sty).Add(o)
paint.Fill(o, colornames.Red)
paint.Fill(o, red)
}, func(r result) {
r.expect(0, 0, colornames.White)
r.expect(10, 50, colornames.Red)
@@ -204,7 +204,7 @@ func TestStrokedPathRoundRound(t *testing.T) {
p.Quad(f32.Pt(-10, -10), f32.Pt(-30, 30))
p.Stroke(width, sty).Add(o)
paint.Fill(o, colornames.Red)
paint.Fill(o, red)
}, func(r result) {
r.expect(0, 0, colornames.White)
r.expect(10, 50, colornames.Red)
@@ -232,7 +232,7 @@ func TestStrokedPathFlatMiter(t *testing.T) {
p.Line(f32.Pt(50, 0))
p.Stroke(width, sty).Add(o)
paint.Fill(o, colornames.Red)
paint.Fill(o, red)
}
{
@@ -246,7 +246,7 @@ func TestStrokedPathFlatMiter(t *testing.T) {
p.Line(f32.Pt(50, 0))
p.Stroke(2, clip.StrokeStyle{}).Add(o)
paint.Fill(o, colornames.Black)
paint.Fill(o, black)
}
}, func(r result) {
@@ -277,7 +277,7 @@ func TestStrokedPathFlatMiterInf(t *testing.T) {
p.Line(f32.Pt(50, 0))
p.Stroke(width, sty).Add(o)
paint.Fill(o, colornames.Red)
paint.Fill(o, red)
}
{
@@ -291,7 +291,7 @@ func TestStrokedPathFlatMiterInf(t *testing.T) {
p.Line(f32.Pt(50, 0))
p.Stroke(2, clip.StrokeStyle{}).Add(o)
paint.Fill(o, colornames.Black)
paint.Fill(o, black)
}
}, func(r result) {
@@ -312,7 +312,7 @@ func TestStrokedPathZeroWidth(t *testing.T) {
p.Line(f32.Pt(50, 0))
p.Stroke(width, sty).Add(o)
paint.Fill(o, colornames.Black)
paint.Fill(o, black)
}
{
@@ -322,7 +322,7 @@ func TestStrokedPathZeroWidth(t *testing.T) {
p.Line(f32.Pt(30, 0))
p.Stroke(0, sty).Add(o) // width=0, disable stroke
paint.Fill(o, colornames.Red)
paint.Fill(o, red)
}
}, func(r result) {
+24 -24
View File
@@ -25,12 +25,12 @@ func TestTransformMacro(t *testing.T) {
// render the first Stacked item
m1 := op.Record(o)
dr := image.Rect(0, 0, 128, 50)
paint.FillShape(o, colornames.Black, clip.Rect(dr).Op())
paint.FillShape(o, black, clip.Rect(dr).Op())
c1 := m1.Stop()
// Render the second stacked item
m2 := op.Record(o)
paint.ColorOp{Color: colornames.Red}.Add(o)
paint.ColorOp{Color: red}.Add(o)
// Simulate a draw text call
stack := op.Push(o)
op.Offset(f32.Pt(0, 10)).Add(o)
@@ -62,7 +62,7 @@ func TestTransformMacro(t *testing.T) {
func TestRepeatedPaintsZ(t *testing.T) {
run(t, func(o *op.Ops) {
// Draw a rectangle
paint.FillShape(o, colornames.Black, clip.Rect(image.Rect(0, 0, 128, 50)).Op())
paint.FillShape(o, black, clip.Rect(image.Rect(0, 0, 128, 50)).Op())
builder := clip.Path{}
builder.Begin(o)
@@ -72,7 +72,7 @@ func TestRepeatedPaintsZ(t *testing.T) {
builder.Line(f32.Pt(-10, 0))
builder.Line(f32.Pt(0, -10))
builder.Outline().Add(o)
paint.Fill(o, colornames.Red)
paint.Fill(o, red)
}, func(r result) {
r.expect(5, 5, colornames.Red)
r.expect(11, 15, colornames.Black)
@@ -86,11 +86,11 @@ func TestNoClipFromPaint(t *testing.T) {
run(t, func(o *op.Ops) {
a := f32.Affine2D{}.Rotate(f32.Pt(20, 20), math.Pi/4)
op.Affine(a).Add(o)
paint.FillShape(o, colornames.Red, clip.Rect(image.Rect(10, 10, 30, 30)).Op())
paint.FillShape(o, red, clip.Rect(image.Rect(10, 10, 30, 30)).Op())
a = f32.Affine2D{}.Rotate(f32.Pt(20, 20), -math.Pi/4)
op.Affine(a).Add(o)
paint.FillShape(o, colornames.Black, clip.Rect(image.Rect(0, 0, 50, 50)).Op())
paint.FillShape(o, black, clip.Rect(image.Rect(0, 0, 50, 50)).Op())
}, func(r result) {
r.expect(1, 1, colornames.Black)
r.expect(20, 20, colornames.Black)
@@ -195,16 +195,16 @@ func TestNegativeOverlaps(t *testing.T) {
}
type Gradient struct {
From, To color.RGBA
From, To color.NRGBA
}
var gradients = []Gradient{
{From: color.RGBA{R: 0x00, G: 0x00, B: 0x00, A: 0xFF}, To: color.RGBA{R: 0xFF, G: 0xFF, B: 0xFF, A: 0xFF}},
{From: color.RGBA{R: 0x19, G: 0xFF, B: 0x19, A: 0xFF}, To: color.RGBA{R: 0xFF, G: 0x19, B: 0x19, A: 0xFF}},
{From: color.RGBA{R: 0xFF, G: 0x19, B: 0x19, A: 0xFF}, To: color.RGBA{R: 0x19, G: 0x19, B: 0xFF, A: 0xFF}},
{From: color.RGBA{R: 0x19, G: 0x19, B: 0xFF, A: 0xFF}, To: color.RGBA{R: 0x19, G: 0xFF, B: 0x19, A: 0xFF}},
{From: color.RGBA{R: 0x19, G: 0xFF, B: 0xFF, A: 0xFF}, To: color.RGBA{R: 0xFF, G: 0x19, B: 0x19, A: 0xFF}},
{From: color.RGBA{R: 0xFF, G: 0xFF, B: 0x19, A: 0xFF}, To: color.RGBA{R: 0x19, G: 0x19, B: 0xFF, A: 0xFF}},
{From: color.NRGBA{R: 0x00, G: 0x00, B: 0x00, A: 0xFF}, To: color.NRGBA{R: 0xFF, G: 0xFF, B: 0xFF, A: 0xFF}},
{From: color.NRGBA{R: 0x19, G: 0xFF, B: 0x19, A: 0xFF}, To: color.NRGBA{R: 0xFF, G: 0x19, B: 0x19, A: 0xFF}},
{From: color.NRGBA{R: 0xFF, G: 0x19, B: 0x19, A: 0xFF}, To: color.NRGBA{R: 0x19, G: 0x19, B: 0xFF, A: 0xFF}},
{From: color.NRGBA{R: 0x19, G: 0x19, B: 0xFF, A: 0xFF}, To: color.NRGBA{R: 0x19, G: 0xFF, B: 0x19, A: 0xFF}},
{From: color.NRGBA{R: 0x19, G: 0xFF, B: 0xFF, A: 0xFF}, To: color.NRGBA{R: 0xFF, G: 0x19, B: 0x19, A: 0xFF}},
{From: color.NRGBA{R: 0xFF, G: 0xFF, B: 0x19, A: 0xFF}, To: color.NRGBA{R: 0x19, G: 0x19, B: 0xFF, A: 0xFF}},
}
func TestLinearGradient(t *testing.T) {
@@ -236,11 +236,11 @@ func TestLinearGradient(t *testing.T) {
}, func(r result) {
gr := pixelAligned
for _, g := range gradients {
from := f32color.RGBAFromSRGB(g.From)
to := f32color.RGBAFromSRGB(g.To)
from := f32color.LinearFromSRGB(g.From)
to := f32color.LinearFromSRGB(g.To)
for _, p := range samples {
exp := lerp(from, to, float32(p)/float32(r.img.Bounds().Dx()-1))
r.expect(p, int(gr.Min.Y+gradienth/2), exp.SRGB())
r.expect(p, int(gr.Min.Y+gradienth/2), f32color.NRGBAToRGBA(exp.SRGB()))
}
gr = gr.Add(f32.Pt(0, gradienth))
}
@@ -251,9 +251,9 @@ func TestLinearGradientAngled(t *testing.T) {
run(t, func(ops *op.Ops) {
paint.LinearGradientOp{
Stop1: f32.Pt(64, 64),
Color1: colornames.Black,
Color1: black,
Stop2: f32.Pt(0, 0),
Color2: colornames.Red,
Color2: red,
}.Add(ops)
st := op.Push(ops)
clip.Rect(image.Rect(0, 0, 64, 64)).Add(ops)
@@ -262,9 +262,9 @@ func TestLinearGradientAngled(t *testing.T) {
paint.LinearGradientOp{
Stop1: f32.Pt(64, 64),
Color1: colornames.White,
Color1: white,
Stop2: f32.Pt(128, 0),
Color2: colornames.Green,
Color2: green,
}.Add(ops)
st = op.Push(ops)
clip.Rect(image.Rect(64, 0, 128, 64)).Add(ops)
@@ -273,9 +273,9 @@ func TestLinearGradientAngled(t *testing.T) {
paint.LinearGradientOp{
Stop1: f32.Pt(64, 64),
Color1: colornames.Black,
Color1: black,
Stop2: f32.Pt(128, 128),
Color2: colornames.Blue,
Color2: blue,
}.Add(ops)
st = op.Push(ops)
clip.Rect(image.Rect(64, 64, 128, 128)).Add(ops)
@@ -284,9 +284,9 @@ func TestLinearGradientAngled(t *testing.T) {
paint.LinearGradientOp{
Stop1: f32.Pt(64, 64),
Color1: colornames.White,
Color1: white,
Stop2: f32.Pt(0, 128),
Color2: colornames.Magenta,
Color2: magenta,
}.Add(ops)
st = op.Push(ops)
clip.Rect(image.Rect(0, 64, 64, 128)).Add(ops)
+8 -8
View File
@@ -15,7 +15,7 @@ import (
func TestPaintOffset(t *testing.T) {
run(t, func(o *op.Ops) {
op.Offset(f32.Pt(10, 20)).Add(o)
paint.FillShape(o, colornames.Red, clip.Rect(image.Rect(0, 0, 50, 50)).Op())
paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 50, 50)).Op())
}, func(r result) {
r.expect(0, 0, colornames.White)
r.expect(59, 30, colornames.Red)
@@ -28,7 +28,7 @@ func TestPaintRotate(t *testing.T) {
run(t, func(o *op.Ops) {
a := f32.Affine2D{}.Rotate(f32.Pt(40, 40), -math.Pi/8)
op.Affine(a).Add(o)
paint.FillShape(o, colornames.Red, clip.Rect(image.Rect(20, 20, 60, 60)).Op())
paint.FillShape(o, red, clip.Rect(image.Rect(20, 20, 60, 60)).Op())
}, func(r result) {
r.expect(40, 40, colornames.Red)
r.expect(50, 19, colornames.Red)
@@ -41,7 +41,7 @@ func TestPaintShear(t *testing.T) {
run(t, func(o *op.Ops) {
a := f32.Affine2D{}.Shear(f32.Point{}, math.Pi/4, 0)
op.Affine(a).Add(o)
paint.FillShape(o, colornames.Red, clip.Rect(image.Rect(0, 0, 40, 40)).Op())
paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 40, 40)).Op())
}, func(r result) {
r.expect(10, 30, colornames.White)
})
@@ -51,7 +51,7 @@ func TestClipPaintOffset(t *testing.T) {
run(t, func(o *op.Ops) {
clip.RRect{Rect: f32.Rect(10, 10, 30, 30)}.Add(o)
op.Offset(f32.Pt(20, 20)).Add(o)
paint.FillShape(o, colornames.Red, clip.Rect(image.Rect(0, 0, 100, 100)).Op())
paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 100, 100)).Op())
}, func(r result) {
r.expect(0, 0, colornames.White)
r.expect(19, 19, colornames.White)
@@ -64,7 +64,7 @@ func TestClipOffset(t *testing.T) {
run(t, func(o *op.Ops) {
op.Offset(f32.Pt(20, 20)).Add(o)
clip.RRect{Rect: f32.Rect(10, 10, 30, 30)}.Add(o)
paint.FillShape(o, colornames.Red, clip.Rect(image.Rect(0, 0, 100, 100)).Op())
paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 100, 100)).Op())
}, func(r result) {
r.expect(0, 0, colornames.White)
r.expect(29, 29, colornames.White)
@@ -79,7 +79,7 @@ func TestClipScale(t *testing.T) {
a := f32.Affine2D{}.Scale(f32.Point{}, f32.Pt(2, 2)).Offset(f32.Pt(10, 10))
op.Affine(a).Add(o)
clip.RRect{Rect: f32.Rect(10, 10, 20, 20)}.Add(o)
paint.FillShape(o, colornames.Red, clip.Rect(image.Rect(0, 0, 1000, 1000)).Op())
paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 1000, 1000)).Op())
}, func(r result) {
r.expect(19+10, 19+10, colornames.White)
r.expect(20+10, 20+10, colornames.Red)
@@ -92,7 +92,7 @@ func TestClipRotate(t *testing.T) {
run(t, func(o *op.Ops) {
op.Affine(f32.Affine2D{}.Rotate(f32.Pt(40, 40), -math.Pi/4)).Add(o)
clip.RRect{Rect: f32.Rect(30, 30, 50, 50)}.Add(o)
paint.FillShape(o, colornames.Red, clip.Rect(image.Rect(0, 40, 100, 100)).Op())
paint.FillShape(o, red, clip.Rect(image.Rect(0, 40, 100, 100)).Op())
}, func(r result) {
r.expect(39, 39, colornames.White)
r.expect(41, 41, colornames.Red)
@@ -188,7 +188,7 @@ func TestTransformOrder(t *testing.T) {
c := f32.Affine2D{}.Offset(f32.Pt(-10, -10)).Scale(f32.Point{}, f32.Pt(0.5, 0.5))
op.Affine(c).Add(o)
paint.FillShape(o, colornames.Red, clip.Rect(image.Rect(0, 0, 20, 20)).Op())
paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 20, 20)).Op())
}, func(r result) {
// centered and with radius 40
r.expect(64-41, 64, colornames.White)
+10
View File
@@ -15,6 +15,7 @@ import (
"gioui.org/app/headless"
"gioui.org/f32"
"gioui.org/internal/f32color"
"gioui.org/op"
"gioui.org/op/paint"
"golang.org/x/image/colornames"
@@ -25,6 +26,15 @@ var (
squares paint.ImageOp
)
var (
red = f32color.RGBAToNRGBA(colornames.Red)
green = f32color.RGBAToNRGBA(colornames.Green)
blue = f32color.RGBAToNRGBA(colornames.Blue)
magenta = f32color.RGBAToNRGBA(colornames.Magenta)
black = f32color.RGBAToNRGBA(colornames.Black)
white = f32color.RGBAToNRGBA(colornames.White)
)
func init() {
// build the texture we use for testing
size := 512