~eliasnaur/gio

f114acdb022e8c5d42f460616c379395f8a6ea37 — Egon Elbre 5 months ago 0e3e446
widget: use correct color in Icon

iconvg seems to expect a linear premultiplied color.

Fixes gio#132

Signed-off-by: Egon Elbre <egonelbre@gmail.com>
4 files changed, 103 insertions(+), 1 deletions(-)

M internal/f32color/rgba.go
A internal/f32color/rgba_test.go
M widget/icon.go
A widget/icon_test.go
M internal/f32color/rgba.go => internal/f32color/rgba.go +34 -0
@@ 77,6 77,40 @@ func NRGBAToRGBA(col color.NRGBA) color.RGBA {
	}
}

// NRGBAToLinearRGBA converts from non-premultiplied sRGB color to premultiplied linear RGBA color.
//
// Each component in the result is `c * alpha`, where `c` is the linear color.
func NRGBAToLinearRGBA(col color.NRGBA) color.RGBA {
	if col.A == 0xFF {
		return color.RGBA(col)
	}
	c := LinearFromSRGB(col)
	return color.RGBA{
		R: uint8(c.R*255 + .5),
		G: uint8(c.G*255 + .5),
		B: uint8(c.B*255 + .5),
		A: col.A,
	}
}

// NRGBAToRGBA_PostAlpha 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_PostAlpha(col color.NRGBA) color.RGBA {
	if col.A == 0xFF {
		return color.RGBA(col)
	} else if col.A == 0x00 {
		return color.RGBA{}
	}
	return color.RGBA{
		R: uint8(uint32(col.R) * uint32(col.A) / 0xFF),
		G: uint8(uint32(col.G) * uint32(col.A) / 0xFF),
		B: uint8(uint32(col.B) * uint32(col.A) / 0xFF),
		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 {

A internal/f32color/rgba_test.go => internal/f32color/rgba_test.go +38 -0
@@ 0,0 1,38 @@
// SPDX-License-Identifier: Unlicense OR MIT

package f32color

import (
	"image/color"
	"testing"
)

func TestNRGBAToRGBA_PostAlpha_Boundary(t *testing.T) {
	for col := 0; col <= 0xFF; col++ {
		for alpha := 0; alpha <= 0xFF; alpha++ {
			in := color.NRGBA{R: uint8(col), A: uint8(alpha)}
			premul := NRGBAToRGBA_PostAlpha(in)
			if premul.A != uint8(alpha) {
				t.Errorf("%v: got %v expected %v", in, premul.A, alpha)
			}
			if premul.R > premul.A {
				t.Errorf("%v: R=%v > A=%v", in, premul.R, premul.A)
			}
		}
	}
}

func TestNRGBAToLinearRGBA_Boundary(t *testing.T) {
	for col := 0; col <= 0xFF; col++ {
		for alpha := 0; alpha <= 0xFF; alpha++ {
			in := color.NRGBA{R: uint8(col), A: uint8(alpha)}
			premul := NRGBAToLinearRGBA(in)
			if premul.A != uint8(alpha) {
				t.Errorf("%v: got %v expected %v", in, premul.A, alpha)
			}
			if premul.R > premul.A {
				t.Errorf("%v: R=%v > A=%v", in, premul.R, premul.A)
			}
		}
	}
}

M widget/icon.go => widget/icon.go +1 -1
@@ 51,7 51,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] = f32color.NRGBAToRGBA(ic.Color)
	m.Palette[0] = f32color.NRGBAToLinearRGBA(ic.Color)
	iconvg.Decode(&ico, ic.src, &iconvg.DecodeOptions{
		Palette: &m.Palette,
	})

A widget/icon_test.go => widget/icon_test.go +30 -0
@@ 0,0 1,30 @@
// SPDX-License-Identifier: Unlicense OR MIT

package widget

import (
	"image"
	"image/color"
	"testing"

	"gioui.org/layout"
	"gioui.org/op"
	"gioui.org/unit"
	"golang.org/x/exp/shiny/materialdesign/icons"
)

func TestIcon_Alpha(t *testing.T) {
	icon, err := NewIcon(icons.ToggleCheckBox)
	if err != nil {
		t.Fatal(err)
	}

	icon.Color = color.NRGBA{B: 0xff, A: 0x40}

	gtx := layout.Context{
		Ops:         new(op.Ops),
		Constraints: layout.Exact(image.Pt(100, 100)),
	}

	_ = icon.Layout(gtx, unit.Sp(18))
}