diff --git a/app/headless/backend_test.go b/app/headless/backend_test.go index 4c652e1b..891ad2aa 100644 --- a/app/headless/backend_test.go +++ b/app/headless/backend_test.go @@ -19,15 +19,16 @@ import ( var dumpImages = flag.Bool("saveimages", false, "save test images") -var clearCol = color.RGBA{A: 0xff, R: 0xde, G: 0xad, B: 0xbe} +var clearCol = color.NRGBA{A: 0xff, R: 0xde, G: 0xad, B: 0xbe} +var clearColExpect = f32color.NRGBAToRGBA(clearCol) func TestFramebufferClear(t *testing.T) { b := newBackend(t) sz := image.Point{X: 800, Y: 600} fbo := setupFBO(t, b, sz) img := screenshot(t, fbo, sz) - if got := img.RGBAAt(0, 0); got != clearCol { - t.Errorf("got color %v, expected %v", got, clearCol) + if got := img.RGBAAt(0, 0); got != clearColExpect { + t.Errorf("got color %v, expected %v", got, clearColExpect) } } @@ -43,14 +44,14 @@ func TestSimpleShader(t *testing.T) { b.BindProgram(p) b.DrawArrays(backend.DrawModeTriangles, 0, 3) img := screenshot(t, fbo, sz) - if got := img.RGBAAt(0, 0); got != clearCol { - t.Errorf("got color %v, expected %v", got, clearCol) + if got := img.RGBAAt(0, 0); got != clearColExpect { + t.Errorf("got color %v, expected %v", got, clearColExpect) } // Just off the center to catch inverted triangles. cx, cy := 300, 400 shaderCol := f32color.RGBA{R: .25, G: .55, B: .75, A: 1.0} - if got, exp := img.RGBAAt(cx, cy), shaderCol.SRGB(); got != exp { - t.Errorf("got color %v, expected %v", got, exp) + if got, exp := img.RGBAAt(cx, cy), shaderCol.SRGB(); got != f32color.NRGBAToRGBA(exp) { + t.Errorf("got color %v, expected %v", got, f32color.NRGBAToRGBA(exp)) } } @@ -90,13 +91,13 @@ func TestInputShader(t *testing.T) { b.BindInputLayout(layout) b.DrawArrays(backend.DrawModeTriangles, 0, 3) img := screenshot(t, fbo, sz) - if got := img.RGBAAt(0, 0); got != clearCol { - t.Errorf("got color %v, expected %v", got, clearCol) + if got := img.RGBAAt(0, 0); got != clearColExpect { + t.Errorf("got color %v, expected %v", got, clearColExpect) } cx, cy := 300, 400 shaderCol := f32color.RGBA{R: .25, G: .55, B: .75, A: 1.0} - if got, exp := img.RGBAAt(cx, cy), shaderCol.SRGB(); got != exp { - t.Errorf("got color %v, expected %v", got, exp) + if got, exp := img.RGBAAt(cx, cy), shaderCol.SRGB(); got != f32color.NRGBAToRGBA(exp) { + t.Errorf("got color %v, expected %v", got, f32color.NRGBAToRGBA(exp)) } } @@ -106,21 +107,21 @@ func TestFramebuffers(t *testing.T) { fbo1 := newFBO(t, b, sz) fbo2 := newFBO(t, b, sz) var ( - col1 = color.RGBA{R: 0xad, G: 0xbe, B: 0xef, A: 0xde} - col2 = color.RGBA{R: 0xfe, G: 0xba, B: 0xbe, A: 0xca} + col1 = color.NRGBA{R: 0xad, G: 0xbe, B: 0xef, A: 0xde} + col2 = color.NRGBA{R: 0xfe, G: 0xba, B: 0xbe, A: 0xca} ) - fcol1, fcol2 := f32color.RGBAFromSRGB(col1), f32color.RGBAFromSRGB(col2) + fcol1, fcol2 := f32color.LinearFromSRGB(col1), f32color.LinearFromSRGB(col2) b.BindFramebuffer(fbo1) b.Clear(fcol1.Float32()) b.BindFramebuffer(fbo2) b.Clear(fcol2.Float32()) img := screenshot(t, fbo1, sz) - if got := img.RGBAAt(0, 0); got != col1 { - t.Errorf("got color %v, expected %v", got, col1) + if got := img.RGBAAt(0, 0); got != f32color.NRGBAToRGBA(col1) { + t.Errorf("got color %v, expected %v", got, f32color.NRGBAToRGBA(col1)) } img = screenshot(t, fbo2, sz) - if got := img.RGBAAt(0, 0); got != col2 { - t.Errorf("got color %v, expected %v", got, col2) + if got := img.RGBAAt(0, 0); got != f32color.NRGBAToRGBA(col2) { + t.Errorf("got color %v, expected %v", got, f32color.NRGBAToRGBA(col2)) } } @@ -129,7 +130,7 @@ func setupFBO(t *testing.T, b backend.Device, size image.Point) backend.Framebuf b.BindFramebuffer(fbo) // ClearColor accepts linear RGBA colors, while 8-bit colors // are in the sRGB color space. - col := f32color.RGBAFromSRGB(clearCol) + col := f32color.LinearFromSRGB(clearCol) b.Clear(col.Float32()) b.ClearDepth(0.0) b.Viewport(0, 0, size.X, size.Y) diff --git a/app/headless/headless_test.go b/app/headless/headless_test.go index 294feb83..516ea0b9 100644 --- a/app/headless/headless_test.go +++ b/app/headless/headless_test.go @@ -8,6 +8,7 @@ import ( "testing" "gioui.org/f32" + "gioui.org/internal/f32color" "gioui.org/op" "gioui.org/op/clip" "gioui.org/op/paint" @@ -18,7 +19,7 @@ func TestHeadless(t *testing.T) { defer release() sz := w.size - col := color.RGBA{A: 0xff, R: 0xca, G: 0xfe} + col := color.NRGBA{A: 0xff, R: 0xca, G: 0xfe} var ops op.Ops paint.ColorOp{Color: col}.Add(&ops) // Paint only part of the screen to avoid the glClear optimization. @@ -34,8 +35,8 @@ func TestHeadless(t *testing.T) { if isz := img.Bounds().Size(); isz != sz { t.Errorf("got %v screenshot, expected %v", isz, sz) } - if got := img.RGBAAt(0, 0); got != col { - t.Errorf("got color %v, expected %v", got, col) + if got := img.RGBAAt(0, 0); got != f32color.NRGBAToRGBA(col) { + t.Errorf("got color %v, expected %v", got, f32color.NRGBAToRGBA(col)) } } @@ -43,8 +44,8 @@ func TestClipping(t *testing.T) { w, release := newTestWindow(t) defer release() - col := color.RGBA{A: 0xff, R: 0xca, G: 0xfe} - col2 := color.RGBA{A: 0xff, R: 0x00, G: 0xfe} + col := color.NRGBA{A: 0xff, R: 0xca, G: 0xfe} + col2 := color.NRGBA{A: 0xff, R: 0x00, G: 0xfe} var ops op.Ops paint.ColorOp{Color: col}.Add(&ops) clip.RRect{ @@ -77,10 +78,10 @@ func TestClipping(t *testing.T) { t.Fatal(err) } } - bg := color.RGBA{A: 0xff, R: 0xff, G: 0xff, B: 0xff} + bg := color.NRGBA{A: 0xff, R: 0xff, G: 0xff, B: 0xff} tests := []struct { x, y int - color color.RGBA + color color.NRGBA }{ {120, 120, col}, {130, 130, col2}, @@ -88,8 +89,8 @@ func TestClipping(t *testing.T) { {230, 230, bg}, } for _, test := range tests { - if got := img.RGBAAt(test.x, test.y); got != test.color { - t.Errorf("(%d,%d): got color %v, expected %v", test.x, test.y, got, test.color) + if got := img.RGBAAt(test.x, test.y); got != f32color.NRGBAToRGBA(test.color) { + t.Errorf("(%d,%d): got color %v, expected %v", test.x, test.y, got, f32color.NRGBAToRGBA(test.color)) } } } @@ -99,9 +100,9 @@ func TestDepth(t *testing.T) { defer release() var ops op.Ops - blue := color.RGBA{B: 0xFF, A: 0xFF} + blue := color.NRGBA{B: 0xFF, A: 0xFF} paint.FillShape(&ops, blue, clip.Rect(image.Rect(0, 0, 50, 100)).Op()) - red := color.RGBA{R: 0xFF, A: 0xFF} + red := color.NRGBA{R: 0xFF, A: 0xFF} paint.FillShape(&ops, red, clip.Rect(image.Rect(0, 0, 100, 50)).Op()) if err := w.Frame(&ops); err != nil { t.Fatal(err) @@ -118,15 +119,15 @@ func TestDepth(t *testing.T) { } tests := []struct { x, y int - color color.RGBA + color color.NRGBA }{ {25, 25, red}, {75, 25, red}, {25, 75, blue}, } for _, test := range tests { - if got := img.RGBAAt(test.x, test.y); got != test.color { - t.Errorf("(%d,%d): got color %v, expected %v", test.x, test.y, got, test.color) + if got := img.RGBAAt(test.x, test.y); got != f32color.NRGBAToRGBA(test.color) { + t.Errorf("(%d,%d): got color %v, expected %v", test.x, test.y, got, f32color.NRGBAToRGBA(test.color)) } } } diff --git a/gpu/gpu.go b/gpu/gpu.go index 6637bd64..5eb484bd 100644 --- a/gpu/gpu.go +++ b/gpu/gpu.go @@ -78,13 +78,13 @@ type drawState struct { // Current paint.ImageOp image imageOpData // Current paint.ColorOp, if any. - color color.RGBA + color color.NRGBA // Current paint.LinearGradientOp. stop1 f32.Point stop2 f32.Point - color1 color.RGBA - color2 color.RGBA + color1 color.NRGBA + color2 color.NRGBA } type pathOp struct { @@ -138,9 +138,9 @@ type imageOpData struct { type linearGradientOpData struct { stop1 f32.Point - color1 color.RGBA + color1 color.NRGBA stop2 f32.Point - color2 color.RGBA + color2 color.NRGBA } func (op *clipOp) decode(data []byte) { @@ -183,11 +183,11 @@ func decodeImageOp(data []byte, refs []interface{}) imageOpData { } } -func decodeColorOp(data []byte) color.RGBA { +func decodeColorOp(data []byte) color.NRGBA { if opconst.OpType(data[0]) != opconst.TypeColor { panic("invalid op") } - return color.RGBA{ + return color.NRGBA{ R: data[1], G: data[2], B: data[3], @@ -209,13 +209,13 @@ func decodeLinearGradientOp(data []byte) linearGradientOpData { X: math.Float32frombits(bo.Uint32(data[9:])), Y: math.Float32frombits(bo.Uint32(data[13:])), }, - color1: color.RGBA{ + color1: color.NRGBA{ R: data[17+0], G: data[17+1], B: data[17+2], A: data[17+3], }, - color2: color.RGBA{ + color2: color.NRGBA{ R: data[21+0], G: data[21+1], B: data[21+2], @@ -749,7 +749,7 @@ func (d *drawOps) collect(cache *resourceCache, root *op.Ops, viewport image.Poi state := drawState{ clip: clip, rect: true, - color: color.RGBA{A: 0xff}, + color: color.NRGBA{A: 0xff}, } d.collectOps(&d.reader, state) } @@ -930,13 +930,13 @@ func (d *drawState) materialFor(cache *resourceCache, rect f32.Rectangle, off f3 switch d.matType { case materialColor: m.material = materialColor - m.color = f32color.RGBAFromSRGB(d.color) + m.color = f32color.LinearFromSRGB(d.color) m.opaque = m.color.A == 1.0 case materialLinearGradient: m.material = materialLinearGradient - m.color1 = f32color.RGBAFromSRGB(d.color1) - m.color2 = f32color.RGBAFromSRGB(d.color2) + m.color1 = f32color.LinearFromSRGB(d.color1) + m.color2 = f32color.LinearFromSRGB(d.color2) m.opaque = m.color1.A == 1.0 && m.color2.A == 1.0 m.uvTrans = trans.Mul(gradientSpaceTransform(clip, off, d.stop1, d.stop2)) diff --git a/internal/f32color/rgba.go b/internal/f32color/rgba.go index dcc5d1d0..c72f50ef 100644 --- a/internal/f32color/rgba.go +++ b/internal/f32color/rgba.go @@ -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 } diff --git a/internal/rendertest/bench_test.go b/internal/rendertest/bench_test.go index ca63eff1..b9efb424 100644 --- a/internal/rendertest/bench_test.go +++ b/internal/rendertest/bench_test.go @@ -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) diff --git a/internal/rendertest/clip_test.go b/internal/rendertest/clip_test.go index f7805347..7746156d 100644 --- a/internal/rendertest/clip_test.go +++ b/internal/rendertest/clip_test.go @@ -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) { diff --git a/internal/rendertest/render_test.go b/internal/rendertest/render_test.go index 4c2d95cb..75576150 100644 --- a/internal/rendertest/render_test.go +++ b/internal/rendertest/render_test.go @@ -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) diff --git a/internal/rendertest/transform_test.go b/internal/rendertest/transform_test.go index bbebd9a4..27e49918 100644 --- a/internal/rendertest/transform_test.go +++ b/internal/rendertest/transform_test.go @@ -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) diff --git a/internal/rendertest/util_test.go b/internal/rendertest/util_test.go index d6b9f704..6568aceb 100644 --- a/internal/rendertest/util_test.go +++ b/internal/rendertest/util_test.go @@ -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 diff --git a/op/paint/doc.go b/op/paint/doc.go index ae69faca..79054ab6 100644 --- a/op/paint/doc.go +++ b/op/paint/doc.go @@ -9,6 +9,6 @@ taking the current transformation into account. The current brush is set by either a ColorOp for a constant color, or ImageOp for an image, or LinearGradientOp for gradients. -All color.RGBA values are in the sRGB color space. +All color.NRGBA values are in the sRGB color space. */ package paint diff --git a/op/paint/paint.go b/op/paint/paint.go index 73ff8734..291348f1 100644 --- a/op/paint/paint.go +++ b/op/paint/paint.go @@ -21,7 +21,7 @@ import ( // See NewImageOp for details. type ImageOp struct { uniform bool - color color.RGBA + color color.NRGBA src *image.RGBA // handle is a key to uniquely identify this ImageOp @@ -31,16 +31,16 @@ type ImageOp struct { // ColorOp sets the brush to a constant color. type ColorOp struct { - Color color.RGBA + Color color.NRGBA } // LinearGradientOp sets the brush to a gradient starting at stop1 with color1 and // ending at stop2 with color2. type LinearGradientOp struct { Stop1 f32.Point - Color1 color.RGBA + Color1 color.NRGBA Stop2 f32.Point - Color2 color.RGBA + Color2 color.NRGBA } // PaintOp fills fills the current clip area with the current brush. @@ -58,7 +58,7 @@ type PaintOp struct { func NewImageOp(src image.Image) ImageOp { switch src := src.(type) { case *image.Uniform: - col := color.RGBAModel.Convert(src.C).(color.RGBA) + col := color.NRGBAModel.Convert(src.C).(color.NRGBA) return ImageOp{ uniform: true, color: col, @@ -138,7 +138,7 @@ func (d PaintOp) Add(o *op.Ops) { } // FillShape fills the clip shape with a color. -func FillShape(ops *op.Ops, c color.RGBA, shape clip.Op) { +func FillShape(ops *op.Ops, c color.NRGBA, shape clip.Op) { defer op.Push(ops).Pop() shape.Add(ops) Fill(ops, c) @@ -148,7 +148,7 @@ func FillShape(ops *op.Ops, c color.RGBA, shape clip.Op) { // is intended to be used with a clip.Op already in place to limit // the painted area. Use FillShape unless you need to paint several // times within the same clip.Op. -func Fill(ops *op.Ops, c color.RGBA) { +func Fill(ops *op.Ops, c color.NRGBA) { defer op.Push(ops).Pop() ColorOp{Color: c}.Add(ops) PaintOp{}.Add(ops) diff --git a/widget/border.go b/widget/border.go index e8bae150..da201447 100644 --- a/widget/border.go +++ b/widget/border.go @@ -15,7 +15,7 @@ import ( // Border lays out a widget and draws a border inside it. type Border struct { - Color color.RGBA + Color color.NRGBA CornerRadius unit.Value Width unit.Value } diff --git a/widget/icon.go b/widget/icon.go index 17ef8af9..846c6222 100644 --- a/widget/icon.go +++ b/widget/icon.go @@ -7,6 +7,7 @@ import ( "image/color" "image/draw" + "gioui.org/internal/f32color" "gioui.org/layout" "gioui.org/op/paint" "gioui.org/unit" @@ -14,12 +15,12 @@ import ( ) type Icon struct { - Color color.RGBA + Color color.NRGBA src []byte // Cached values. op paint.ImageOp imgSize int - imgColor color.RGBA + imgColor color.NRGBA } // NewIcon returns a new Icon from IconVG data. @@ -28,7 +29,7 @@ func NewIcon(data []byte) (*Icon, error) { if err != nil { return nil, err } - return &Icon{src: data, Color: color.RGBA{A: 0xff}}, nil + return &Icon{src: data, Color: color.NRGBA{A: 0xff}}, nil } func (ic *Icon) Layout(gtx layout.Context, sz unit.Value) layout.Dimensions { @@ -49,7 +50,7 @@ func (ic *Icon) image(sz int) paint.ImageOp { img := image.NewRGBA(image.Rectangle{Max: image.Point{X: sz, Y: int(float32(sz) * dy / dx)}}) var ico iconvg.Rasterizer ico.SetDstImage(img, img.Bounds(), draw.Src) - m.Palette[0] = ic.Color + m.Palette[0] = f32color.NRGBAToRGBA(ic.Color) iconvg.Decode(&ico, ic.src, &iconvg.DecodeOptions{ Palette: &m.Palette, }) diff --git a/widget/material/button.go b/widget/material/button.go index 18b9c1cd..f5e6dce3 100644 --- a/widget/material/button.go +++ b/widget/material/button.go @@ -22,10 +22,10 @@ import ( type ButtonStyle struct { Text string // Color is the text color. - Color color.RGBA + Color color.NRGBA Font text.Font TextSize unit.Value - Background color.RGBA + Background color.NRGBA CornerRadius unit.Value Inset layout.Inset Button *widget.Clickable @@ -33,15 +33,15 @@ type ButtonStyle struct { } type ButtonLayoutStyle struct { - Background color.RGBA + Background color.NRGBA CornerRadius unit.Value Button *widget.Clickable } type IconButtonStyle struct { - Background color.RGBA + Background color.NRGBA // Color is the icon color. - Color color.RGBA + Color color.NRGBA Icon *widget.Icon // Size is the icon size. Size unit.Value @@ -272,7 +272,7 @@ func drawInk(gtx layout.Context, c widget.Press) { const col = 0.8 ba, bc := byte(alpha*0xff), byte(col*0xff) defer op.Push(gtx.Ops).Pop() - rgba := f32color.MulAlpha(color.RGBA{A: 0xff, R: bc, G: bc, B: bc}, ba) + rgba := f32color.MulAlpha(color.NRGBA{A: 0xff, R: bc, G: bc, B: bc}, ba) ink := paint.ColorOp{Color: rgba} ink.Add(gtx.Ops) rr := size * .5 diff --git a/widget/material/checkable.go b/widget/material/checkable.go index 59033895..617d3201 100644 --- a/widget/material/checkable.go +++ b/widget/material/checkable.go @@ -17,10 +17,10 @@ import ( type checkable struct { Label string - Color color.RGBA + Color color.NRGBA Font text.Font TextSize unit.Value - IconColor color.RGBA + IconColor color.NRGBA Size unit.Value shaper text.Shaper checkedStateIcon *widget.Icon diff --git a/widget/material/doc.go b/widget/material/doc.go index d7679d58..715f5a07 100644 --- a/widget/material/doc.go +++ b/widget/material/doc.go @@ -36,7 +36,7 @@ // Theme-global parameters: For changing the look of all widgets drawn with a // particular theme, adjust the `Theme` fields: // -// theme.Color.Primary = color.RGBA{...} +// theme.Color.Primary = color.NRGBA{...} // // Widget-local parameters: For changing the look of a particular widget, // adjust the widget specific theme object: diff --git a/widget/material/editor.go b/widget/material/editor.go index 7e0d187c..ae6805d5 100644 --- a/widget/material/editor.go +++ b/widget/material/editor.go @@ -18,11 +18,11 @@ type EditorStyle struct { Font text.Font TextSize unit.Value // Color is the text color. - Color color.RGBA + Color color.NRGBA // Hint contains the text displayed when the editor is empty. Hint string // HintColor is the color of hint text. - HintColor color.RGBA + HintColor color.NRGBA Editor *widget.Editor shaper text.Shaper diff --git a/widget/material/label.go b/widget/material/label.go index c475c31d..256dcfd5 100644 --- a/widget/material/label.go +++ b/widget/material/label.go @@ -16,7 +16,7 @@ type LabelStyle struct { // Face defines the text style. Font text.Font // Color is the text color. - Color color.RGBA + Color color.NRGBA // Alignment specify the text alignment. Alignment text.Alignment // MaxLines limits the number of lines. Zero means no limit. diff --git a/widget/material/loader.go b/widget/material/loader.go index f80842f1..48a4b6f2 100644 --- a/widget/material/loader.go +++ b/widget/material/loader.go @@ -17,7 +17,7 @@ import ( ) type LoaderStyle struct { - Color color.RGBA + Color color.NRGBA } func Loader(th *Theme) LoaderStyle { diff --git a/widget/material/progressbar.go b/widget/material/progressbar.go index a5fa9fd8..5cba9baf 100644 --- a/widget/material/progressbar.go +++ b/widget/material/progressbar.go @@ -15,7 +15,7 @@ import ( ) type ProgressBarStyle struct { - Color color.RGBA + Color color.NRGBA Progress int } @@ -27,7 +27,7 @@ func ProgressBar(th *Theme, progress int) ProgressBarStyle { } func (p ProgressBarStyle) Layout(gtx layout.Context) layout.Dimensions { - shader := func(width float32, color color.RGBA) layout.Dimensions { + shader := func(width float32, color color.NRGBA) layout.Dimensions { maxHeight := unit.Dp(4) rr := float32(gtx.Px(unit.Dp(2))) diff --git a/widget/material/slider.go b/widget/material/slider.go index f0239829..4756c23c 100644 --- a/widget/material/slider.go +++ b/widget/material/slider.go @@ -28,7 +28,7 @@ func Slider(th *Theme, float *widget.Float, min, max float32) SliderStyle { type SliderStyle struct { Min, Max float32 - Color color.RGBA + Color color.NRGBA Float *widget.Float } diff --git a/widget/material/switch.go b/widget/material/switch.go index 000091c4..93c22a17 100644 --- a/widget/material/switch.go +++ b/widget/material/switch.go @@ -19,8 +19,8 @@ import ( type SwitchStyle struct { Color struct { - Enabled color.RGBA - Disabled color.RGBA + Enabled color.NRGBA + Disabled color.NRGBA } Switch *widget.Bool } @@ -124,7 +124,7 @@ func (s SwitchStyle) Layout(gtx layout.Context) layout.Dimensions { return layout.Dimensions{Size: dims} } -func drawDisc(ops *op.Ops, sz float32, col color.RGBA) { +func drawDisc(ops *op.Ops, sz float32, col color.NRGBA) { defer op.Push(ops).Pop() rr := sz / 2 r := f32.Rectangle{Max: f32.Point{X: sz, Y: sz}} diff --git a/widget/material/theme.go b/widget/material/theme.go index 40412ab1..b55af125 100644 --- a/widget/material/theme.go +++ b/widget/material/theme.go @@ -14,10 +14,10 @@ import ( type Theme struct { Shaper text.Shaper Color struct { - Primary color.RGBA - Text color.RGBA - Hint color.RGBA - InvText color.RGBA + Primary color.NRGBA + Text color.NRGBA + Hint color.NRGBA + InvText color.NRGBA } TextSize unit.Value Icon struct { @@ -53,10 +53,10 @@ func mustIcon(ic *widget.Icon, err error) *widget.Icon { return ic } -func rgb(c uint32) color.RGBA { +func rgb(c uint32) color.NRGBA { return argb(0xff000000 | c) } -func argb(c uint32) color.RGBA { - return color.RGBA{A: uint8(c >> 24), R: uint8(c >> 16), G: uint8(c >> 8), B: uint8(c)} +func argb(c uint32) color.NRGBA { + return color.NRGBA{A: uint8(c >> 24), R: uint8(c >> 16), G: uint8(c >> 8), B: uint8(c)} }