~eliasnaur/gio

21ef492cc9dfd9161e9fc57df25684865cd1c847 — Egon Elbre 11 months ago 9469d18
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>
M app/headless/backend_test.go => app/headless/backend_test.go +20 -19
@@ 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)

M app/headless/headless_test.go => app/headless/headless_test.go +15 -14
@@ 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))
		}
	}
}

M gpu/gpu.go => gpu/gpu.go +13 -13
@@ 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))

M internal/f32color/rgba.go => internal/f32color/rgba.go +53 -23
@@ 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
}

M internal/rendertest/bench_test.go => internal/rendertest/bench_test.go +3 -3
@@ 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)

M internal/rendertest/clip_test.go => internal/rendertest/clip_test.go +14 -14
@@ 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) {

M internal/rendertest/render_test.go => internal/rendertest/render_test.go +24 -24
@@ 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)

M internal/rendertest/transform_test.go => internal/rendertest/transform_test.go +8 -8
@@ 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)

M internal/rendertest/util_test.go => internal/rendertest/util_test.go +10 -0
@@ 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

M op/paint/doc.go => op/paint/doc.go +1 -1
@@ 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

M op/paint/paint.go => op/paint/paint.go +7 -7
@@ 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)

M widget/border.go => widget/border.go +1 -1
@@ 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
}

M widget/icon.go => widget/icon.go +5 -4
@@ 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,
	})

M widget/material/button.go => widget/material/button.go +6 -6
@@ 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

M widget/material/checkable.go => widget/material/checkable.go +2 -2
@@ 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

M widget/material/doc.go => widget/material/doc.go +1 -1
@@ 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:

M widget/material/editor.go => widget/material/editor.go +2 -2
@@ 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

M widget/material/label.go => widget/material/label.go +1 -1
@@ 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.

M widget/material/loader.go => widget/material/loader.go +1 -1
@@ 17,7 17,7 @@ import (
)

type LoaderStyle struct {
	Color color.RGBA
	Color color.NRGBA
}

func Loader(th *Theme) LoaderStyle {

M widget/material/progressbar.go => widget/material/progressbar.go +2 -2
@@ 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)))


M widget/material/slider.go => widget/material/slider.go +1 -1
@@ 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
}


M widget/material/switch.go => widget/material/switch.go +3 -3
@@ 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}}

M widget/material/theme.go => widget/material/theme.go +7 -7
@@ 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)}
}