From 6ae67fc6dd7fac535d810c926e3bf8ce17100ead Mon Sep 17 00:00:00 2001 From: Hajime Hoshi Date: Sat, 14 Oct 2017 23:58:09 +0900 Subject: [PATCH] graphics: Add ColorM.Apply (#432) --- colorm.go | 9 ++++++ colorm_test.go | 61 ++++++++++++++++++++++++++++++++++++++- internal/affine/colorm.go | 40 +++++++++++++++++++++++++ 3 files changed, 109 insertions(+), 1 deletion(-) diff --git a/colorm.go b/colorm.go index 56d8b028a..9b666c151 100644 --- a/colorm.go +++ b/colorm.go @@ -15,6 +15,8 @@ package ebiten import ( + "image/color" + "github.com/hajimehoshi/ebiten/internal/affine" ) @@ -38,6 +40,13 @@ func (c *ColorM) Reset() { c.impl.Reset() } +// Apply pre-multiplies a vector (r, g, b, a, 1) by the matrix +// where r, g, b, and a are clr's values after un-multiplied alpha. +// In other words, Apply calculates ColorM * (r, g, b, a, 1)^T. +func (c *ColorM) Apply(clr color.Color) color.Color { + return c.impl.Apply(clr) +} + // Concat multiplies a color matrix with the other color matrix. // This is same as muptiplying the matrix other and the matrix c in this order. func (c *ColorM) Concat(other ColorM) { diff --git a/colorm_test.go b/colorm_test.go index efe2c3612..cc8b80af5 100644 --- a/colorm_test.go +++ b/colorm_test.go @@ -15,8 +15,10 @@ package ebiten_test import ( - . "github.com/hajimehoshi/ebiten" + "image/color" "testing" + + . "github.com/hajimehoshi/ebiten" ) func TestColorMInit(t *testing.T) { @@ -165,3 +167,60 @@ func TestColorMConcatSelf(t *testing.T) { } } } + +func abs(x uint32) uint32 { + if x < 0 { + return -x + } + return x +} + +func TestColorMApply(t *testing.T) { + mono := ColorM{} + mono.ChangeHSV(0, 0, 1) + + shiny := ColorM{} + shiny.Translate(1, 1, 1, 0) + + cases := []struct { + ColorM ColorM + In color.Color + Out color.Color + Delta uint32 + }{ + { + ColorM: ColorM{}, + In: color.RGBA{1, 2, 3, 4}, + Out: color.RGBA{1, 2, 3, 4}, + Delta: 0x101, + }, + { + ColorM: mono, + In: color.NRGBA{0xff, 0xff, 0xff, 0}, + Out: color.Transparent, + Delta: 0x101, + }, + { + ColorM: mono, + In: color.RGBA{0xff, 0, 0, 0xff}, + Out: color.RGBA{0x4c, 0x4c, 0x4c, 0xff}, + Delta: 0x101, + }, + { + ColorM: shiny, + In: color.RGBA{0x80, 0x90, 0xa0, 0xb0}, + Out: color.RGBA{0xb0, 0xb0, 0xb0, 0xb0}, + Delta: 1, + }, + } + for _, c := range cases { + out := c.ColorM.Apply(c.In) + r0, g0, b0, a0 := out.RGBA() + r1, g1, b1, a1 := c.Out.RGBA() + if abs(r0-r1) > c.Delta || abs(g0-g1) > c.Delta || + abs(b0-b1) > c.Delta || abs(a0-a1) > c.Delta { + println(r0, r1) + t.Errorf("%v.Apply(%v) = %v, want %v", c.ColorM, c.In, out, c.Out) + } + } +} diff --git a/internal/affine/colorm.go b/internal/affine/colorm.go index 01e51116d..0ceb0ad6d 100644 --- a/internal/affine/colorm.go +++ b/internal/affine/colorm.go @@ -15,6 +15,7 @@ package affine import ( + "image/color" "math" ) @@ -48,6 +49,45 @@ func (c *ColorM) Reset() { c.elements = nil } +func clamp(x float64) float64 { + if x > 1 { + return 1 + } + if x < 0 { + return 0 + } + return x +} + +func (c *ColorM) Apply(clr color.Color) color.Color { + if c.elements == nil { + return clr + } + r, g, b, a := clr.RGBA() + if a == 0 { + return color.Transparent + } + rf := float64(r) / float64(a) + gf := float64(g) / float64(a) + bf := float64(b) / float64(a) + af := float64(a) / 0xffff + e := c.elements + rf2 := e[0]*rf + e[1]*gf + e[2]*bf + e[3]*af + e[4] + gf2 := e[5]*rf + e[6]*gf + e[7]*bf + e[8]*af + e[9] + bf2 := e[10]*rf + e[11]*gf + e[12]*bf + e[13]*af + e[14] + af2 := e[15]*rf + e[16]*gf + e[17]*bf + e[18]*af + e[19] + rf2 = clamp(rf2) + gf2 = clamp(gf2) + bf2 = clamp(bf2) + af2 = clamp(af2) + return color.NRGBA64{ + R: uint16(rf2 * 0xffff), + G: uint16(gf2 * 0xffff), + B: uint16(bf2 * 0xffff), + A: uint16(af2 * 0xffff), + } +} + func (c *ColorM) UnsafeElements() []float64 { if c.elements == nil { c.elements = colorMIdentityElements