238 lines
6.6 KiB
Go
238 lines
6.6 KiB
Go
package oklab
|
|
|
|
import (
|
|
"image/color"
|
|
"math"
|
|
"reflect"
|
|
"testing"
|
|
)
|
|
|
|
func TestColor_NRGBA(t *testing.T) {
|
|
// Not checking the whole space because that would be slow, and alpha is given less resolution in this test.
|
|
// The increments were chosen because they're factors of 0xffff, so we'll end on the maximum value.
|
|
for a := uint32(0); a < 0x10000; a += 4369 {
|
|
for b := uint32(0); b < 0x10000; b += 1285 {
|
|
for g := uint32(0); g < 0x10000; g += 1285 {
|
|
for r := uint32(0); r < 0x10000; r += 1285 {
|
|
_r, _g, _b, _a := FromNRGBA(r, g, b, a).NRGBA()
|
|
|
|
if r != _r || g != _g || b != _b || a != _a {
|
|
t.Errorf("NRGBA(0x%04x,0x%04x,0x%04x,0x%04x) -> OkLab -> NRGBA(0x%04x,0x%04x,0x%04x,0x%04x)", r, g, b, a, _r, _g, _b, _a)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestColor_RGBA(t *testing.T) {
|
|
// This is similar to the NRGBA test, except the components step by a/17 instead of a fixed 1285.
|
|
// a/17 was chosen because 17 is a factor of 4369, so the components will end equalling a.
|
|
for a := uint32(0); a < 0x10000; a += 4369 {
|
|
step := max(1, a/17)
|
|
for b := uint32(0); b <= a; b += step {
|
|
for g := uint32(0); g <= a; g += step {
|
|
for r := uint32(0); r <= a; r += step {
|
|
_r, _g, _b, _a := FromRGBA(r, g, b, a).RGBA()
|
|
|
|
if r != _r || g != _g || b != _b || a != _a {
|
|
t.Errorf("RGBA(0x%04x,0x%04x,0x%04x,0x%04x) -> OkLab -> RGBA(0x%04x,0x%04x,0x%04x,0x%04x)", r, g, b, a, _r, _g, _b, _a)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func Test_delinearize(t *testing.T) {
|
|
for c := uint32(0); c < 0x10000; c++ {
|
|
got := delinearize(linearize(c))
|
|
if got != c {
|
|
t.Errorf("delinearize(linearize(0x%04x)) != 0x%04x", c, got)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// The NRGBA and NRGBA64 models don't have sensible ways to recover transparent colors from types it
|
|
// doesn't know about, so I'm going to help them out.
|
|
func fixedNRGBAModel(c color.Color) color.Color {
|
|
if c, ok := c.(NRGBAColor); ok {
|
|
r, g, b, a := c.NRGBA()
|
|
return color.NRGBA{R: uint8(r >> 8), G: uint8(g >> 8), B: uint8(b >> 8), A: uint8(a >> 8)}
|
|
}
|
|
|
|
return color.NRGBAModel.Convert(c)
|
|
}
|
|
|
|
func fixedNRGBA64Model(c color.Color) color.Color {
|
|
if c, ok := c.(NRGBAColor); ok {
|
|
r, g, b, a := c.NRGBA()
|
|
return color.NRGBA64{R: uint16(r), G: uint16(g), B: uint16(b), A: uint16(a)}
|
|
}
|
|
|
|
return color.NRGBA64Model.Convert(c)
|
|
}
|
|
|
|
type testNRGBAColor struct {
|
|
color.NRGBA64
|
|
}
|
|
|
|
func (c testNRGBAColor) NRGBA() (r, g, b, a uint32) {
|
|
return uint32(c.R), uint32(c.G), uint32(c.B), uint32(c.A)
|
|
}
|
|
|
|
func testNRGBAModel(c color.Color) color.Color {
|
|
return testNRGBAColor{fixedNRGBA64Model(c).(color.NRGBA64)}
|
|
}
|
|
|
|
func Test_Model(t *testing.T) {
|
|
// test to make sure we can reproduce some test colors.
|
|
// In particular, I want to make sure the colors can be recovered from transparent NRGBA and NRGBA64.
|
|
for _, tt := range [...]struct {
|
|
name string
|
|
color color.Color
|
|
model color.Model
|
|
}{
|
|
{
|
|
"OkLab: nop",
|
|
Color{math.Inf(1), math.Inf(-1), math.MaxFloat64, 0},
|
|
Model,
|
|
}, {
|
|
"NRGBA: opaque Sapphire",
|
|
color.NRGBA{R: 0x0f, G: 0x52, B: 0xba, A: 0xff},
|
|
color.ModelFunc(fixedNRGBAModel),
|
|
}, {
|
|
"NRGBA: translucent Orchid",
|
|
color.NRGBA{R: 0xda, G: 0x70, B: 0xd6, A: 0x7f},
|
|
color.ModelFunc(fixedNRGBAModel),
|
|
}, {
|
|
"NRGBA: invisible Floral White",
|
|
color.NRGBA{R: 0xff, G: 0xfa, B: 0xf0, A: 0x00},
|
|
color.ModelFunc(fixedNRGBAModel),
|
|
}, {
|
|
"NRGBA64: opaque Amethyst",
|
|
color.NRGBA64{R: 0x9999, G: 0x6666, B: 0xcccc, A: 0xffff},
|
|
color.ModelFunc(fixedNRGBA64Model),
|
|
}, {
|
|
"NRGBA64: translucent Smoke",
|
|
color.NRGBA64{R: 0xf5f5, G: 0xf5f5, B: 0xf5f5, A: 0x7fff},
|
|
color.ModelFunc(fixedNRGBA64Model),
|
|
}, {
|
|
"NRGBA64: invisible Emerald",
|
|
color.NRGBA64{R: 0x5050, G: 0xC8C8, B: 0x7878, A: 0x0000},
|
|
color.ModelFunc(fixedNRGBA64Model),
|
|
}, {
|
|
"RGBA64: opaque Chartreuse",
|
|
color.RGBA64{R: 0xb2b2, G: 0xd6d6, B: 0x3f3f, A: 0xffff},
|
|
color.RGBA64Model,
|
|
}, {
|
|
"RGBA64: transparent Mauveine",
|
|
color.RGBA64{R: 0x577c, G: 0x013e, B: 0x602b, A: 0x9e37},
|
|
color.RGBA64Model,
|
|
}, {
|
|
"RGBA64: invisible nothing",
|
|
color.RGBA64{R: 0x0, G: 0x0, B: 0x0, A: 0x0},
|
|
color.RGBA64Model,
|
|
}, {
|
|
"RGBA: opaque Chartreuse",
|
|
color.RGBA{R: 0xb2, G: 0xd6, B: 0x3f, A: 0xff},
|
|
color.RGBAModel,
|
|
}, {
|
|
"RGBA: transparent Mauveine",
|
|
color.RGBA{R: 0x57, G: 0x01, B: 0x60, A: 0x9e},
|
|
color.RGBAModel,
|
|
}, {
|
|
"RGBA: invisible nothing",
|
|
color.RGBA{R: 0x0, G: 0x0, B: 0x0, A: 0x0},
|
|
color.RGBAModel,
|
|
}, {
|
|
// test the handling of the NRGBAColor interface; as
|
|
// all the types that support it have special handling
|
|
// that would take precedence.
|
|
"testNRGBAColor: invisible Floral White",
|
|
testNRGBAColor{color.NRGBA64{R: 0xff, G: 0xfa, B: 0xf0, A: 0x00}},
|
|
color.ModelFunc(testNRGBAModel),
|
|
},
|
|
} {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
c := Model.Convert(tt.color).(Color)
|
|
|
|
if got := tt.model.Convert(c); !reflect.DeepEqual(got, tt.color) {
|
|
t.Errorf("%#+v -> %#+v -> %#+v, want %#+v", tt.color, c, got, tt.color)
|
|
return
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func testDistance(t *testing.T, c0, c1 Color) {
|
|
|
|
d := Distance(c0, c1)
|
|
|
|
if math.IsNaN(d) || math.IsInf(d, 0) {
|
|
t.Errorf("Distance(%v, %v) = %f, want finite", c0, c1, d)
|
|
return
|
|
}
|
|
|
|
if d < 0 {
|
|
t.Errorf("Distance(%v, %v) = %f, want %f >= 0", c0, c1, d, d)
|
|
return
|
|
}
|
|
|
|
if d2 := Distance(c1, c0); d2 != d {
|
|
t.Errorf("Distance(%v, %v) != Distance(%v, %v), want %f == %f", c1, c0, c0, c1, d, d2)
|
|
return
|
|
}
|
|
|
|
if c0 == c1 || (c0.A == 0 && c1.A == 0) {
|
|
// if they're the same color, or both are completely transparent,
|
|
// they should be perceived as identical.
|
|
|
|
if d != 0 {
|
|
t.Errorf("Distance(%v, %v) = %f, want %f == 0", c0, c1, d, d)
|
|
return
|
|
}
|
|
} else {
|
|
// otherwise, there should be some kind of difference between them.
|
|
if d <= 0 {
|
|
t.Errorf("Distance(%v, %v) = %f, want %f > 0", c0, c1, d, d)
|
|
return
|
|
}
|
|
}
|
|
|
|
mid := Color{
|
|
Lightness: (c0.Lightness + c1.Lightness) * 0.5,
|
|
ChromaA: (c0.ChromaA + c1.ChromaA) * 0.5,
|
|
ChromaB: (c0.ChromaB + c1.ChromaB) * 0.5,
|
|
A: (c0.A + c1.A) * 0.5,
|
|
}
|
|
|
|
// traveling from c0 to c1 via mid can't possibly be
|
|
// shorter than traveling from c0 to c1 directly.
|
|
d2 := Distance(c0, mid) + Distance(mid, c1)
|
|
if d2 < d {
|
|
t.Errorf("Distance(%v, %v)+Distance(%v, %v) < Distance(%v, %v), want %f >= %f", c0, mid, mid, c1, c0, c1, d2, d)
|
|
return
|
|
}
|
|
}
|
|
|
|
func TestDistance(t *testing.T) {
|
|
colours := []Color{
|
|
FromNRGBA(0xffff, 0xffff, 0xffff, 0xffff),
|
|
FromNRGBA(0xffff, 0xffff, 0xffff, 0x7fff),
|
|
FromNRGBA(0xffff, 0xffff, 0xffff, 0x0000),
|
|
FromNRGBA(0x0000, 0x0000, 0x0000, 0xffff),
|
|
FromNRGBA(0x0000, 0x0000, 0x0000, 0x7fff),
|
|
FromNRGBA(0x0000, 0x0000, 0x0000, 0x0000),
|
|
}
|
|
|
|
for _, c0 := range colours {
|
|
for _, c1 := range colours {
|
|
testDistance(t, c0, c1)
|
|
}
|
|
}
|
|
}
|