~eliasnaur/gio

4bab6fcf322d4b5fc0e2e6fd3132965fea6aefa5 — Elias Naur 3 months ago 679a34b
internal/f32color: add colorspace-correct function for alpha scaling

Package material's ad-hoc mulAlpha didn't take the sRGB color-space
into account, which meant that alpha-scaled colors were subtly wrong.
Introduce f32color.MulAlpha and convert all uses to it.

Thanks to René Post for finding and debugging the issue.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
M internal/f32color/rgba.go => internal/f32color/rgba.go +12 -0
@@ 73,3 73,15 @@ func sRGBToLinear(c float32) float32 {
		return float32(math.Pow(float64((c+0.055)/1.055), 2.4))
	}
}

// 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()
}

M widget/material/button.go => widget/material/button.go +6 -4
@@ 8,6 8,7 @@ import (
	"math"

	"gioui.org/f32"
	"gioui.org/internal/f32color"
	"gioui.org/io/pointer"
	"gioui.org/layout"
	"gioui.org/op"


@@ 131,7 132,7 @@ func (b ButtonLayoutStyle) Layout(gtx layout.Context, w layout.Widget) layout.Di
			}.Add(gtx.Ops)
			background := b.Background
			if gtx.Queue == nil {
				background = mulAlpha(b.Background, 150)
				background = f32color.MulAlpha(b.Background, 150)
			}
			dims := fill(gtx, background)
			for _, c := range b.Button.History() {


@@ 159,7 160,7 @@ func (b IconButtonStyle) Layout(gtx layout.Context) layout.Dimensions {
			}.Add(gtx.Ops)
			background := b.Background
			if gtx.Queue == nil {
				background = mulAlpha(b.Background, 150)
				background = f32color.MulAlpha(b.Background, 150)
			}
			dims := fill(gtx, background)
			for _, c := range b.Button.History() {


@@ 273,9 274,10 @@ func drawInk(gtx layout.Context, c widget.Press) {
	size *= sizeBezier
	alpha := 0.7 * alphaBezier
	const col = 0.8
	ba, bc := byte(alpha*0xff), byte(alpha*col*0xff)
	ba, bc := byte(alpha*0xff), byte(col*0xff)
	defer op.Push(gtx.Ops).Pop()
	ink := paint.ColorOp{Color: color.RGBA{A: ba, R: bc, G: bc, B: bc}}
	rgba := f32color.MulAlpha(color.RGBA{A: 0xff, R: bc, G: bc, B: bc}, ba)
	ink := paint.ColorOp{Color: rgba}
	ink.Add(gtx.Ops)
	rr := size * .5
	op.Offset(c.Position.Add(f32.Point{

M widget/material/checkable.go => widget/material/checkable.go +2 -1
@@ 6,6 6,7 @@ import (
	"image"
	"image/color"

	"gioui.org/internal/f32color"
	"gioui.org/io/pointer"
	"gioui.org/layout"
	"gioui.org/op/paint"


@@ 42,7 43,7 @@ func (c *checkable) layout(gtx layout.Context, checked bool) layout.Dimensions {
					size := gtx.Px(c.Size)
					icon.Color = c.IconColor
					if gtx.Queue == nil {
						icon.Color = mulAlpha(icon.Color, 150)
						icon.Color = f32color.MulAlpha(icon.Color, 150)
					}
					icon.Layout(gtx, unit.Px(float32(size)))
					return layout.Dimensions{

M widget/material/editor.go => widget/material/editor.go +2 -1
@@ 5,6 5,7 @@ package material
import (
	"image/color"

	"gioui.org/internal/f32color"
	"gioui.org/layout"
	"gioui.org/op"
	"gioui.org/op/paint"


@@ 56,7 57,7 @@ func (e EditorStyle) Layout(gtx layout.Context) layout.Dimensions {
	if e.Editor.Len() > 0 {
		textColor := e.Color
		if disabled {
			textColor = mulAlpha(textColor, 150)
			textColor = f32color.MulAlpha(textColor, 150)
		}
		paint.ColorOp{Color: textColor}.Add(gtx.Ops)
		e.Editor.PaintText(gtx)

M widget/material/progressbar.go => widget/material/progressbar.go +3 -13
@@ 7,6 7,7 @@ import (
	"image/color"

	"gioui.org/f32"
	"gioui.org/internal/f32color"
	"gioui.org/layout"
	"gioui.org/op/clip"
	"gioui.org/op/paint"


@@ 58,7 59,7 @@ func (p ProgressBarStyle) Layout(gtx layout.Context) layout.Dimensions {
	return layout.Stack{Alignment: layout.W}.Layout(gtx,
		layout.Stacked(func(gtx layout.Context) layout.Dimensions {
			// Use a transparent equivalent of progress color.
			bgCol := mulAlpha(p.Color, 150)
			bgCol := f32color.MulAlpha(p.Color, 150)

			return shader(progressBarWidth, bgCol)
		}),


@@ 66,20 67,9 @@ func (p ProgressBarStyle) Layout(gtx layout.Context) layout.Dimensions {
			fillWidth := (progressBarWidth / 100) * float32(progress)
			fillColor := p.Color
			if gtx.Queue == nil {
				fillColor = mulAlpha(fillColor, 200)
				fillColor = f32color.MulAlpha(fillColor, 200)
			}
			return shader(fillWidth, fillColor)
		}),
	)
}

// mulAlpha scales all color components by alpha/255.
func mulAlpha(c color.RGBA, alpha uint8) color.RGBA {
	a := uint16(alpha)
	return color.RGBA{
		A: uint8(uint16(c.A) * a / 255),
		R: uint8(uint16(c.R) * a / 255),
		G: uint8(uint16(c.G) * a / 255),
		B: uint8(uint16(c.B) * a / 255),
	}
}

M widget/material/slider.go => widget/material/slider.go +3 -2
@@ 7,6 7,7 @@ import (
	"image/color"

	"gioui.org/f32"
	"gioui.org/internal/f32color"
	"gioui.org/layout"
	"gioui.org/op"
	"gioui.org/op/clip"


@@ 55,7 56,7 @@ func (s SliderStyle) Layout(gtx layout.Context) layout.Dimensions {

	color := s.Color
	if gtx.Queue == nil {
		color = mulAlpha(color, 150)
		color = f32color.MulAlpha(color, 150)
	}

	// Draw track before thumb.


@@ 80,7 81,7 @@ func (s SliderStyle) Layout(gtx layout.Context) layout.Dimensions {
	track.Min.X = thumbPos
	track.Max.X = float32(size.X) - halfWidth
	clip.RRect{Rect: track}.Add(gtx.Ops)
	paint.ColorOp{Color: mulAlpha(color, 96)}.Add(gtx.Ops)
	paint.ColorOp{Color: f32color.MulAlpha(color, 96)}.Add(gtx.Ops)
	paint.PaintOp{Rect: track}.Add(gtx.Ops)
	st.Pop()


M widget/material/switch.go => widget/material/switch.go +3 -2
@@ 7,6 7,7 @@ import (
	"image/color"

	"gioui.org/f32"
	"gioui.org/internal/f32color"
	"gioui.org/io/pointer"
	"gioui.org/layout"
	"gioui.org/op"


@@ 52,9 53,9 @@ func (s SwitchStyle) Layout(gtx layout.Context) layout.Dimensions {
		col = s.Color.Enabled
	}
	if gtx.Queue == nil {
		col = mulAlpha(col, 150)
		col = f32color.MulAlpha(col, 150)
	}
	trackColor := mulAlpha(col, 150)
	trackColor := f32color.MulAlpha(col, 150)
	op.Offset(f32.Point{Y: trackOff}).Add(gtx.Ops)
	clip.RRect{
		Rect: trackRect,