package helper import ( "fmt" "image/color" "math" "testing" ) func TestLMSToXYZ(t *testing.T) { for c := range Enum(false, true) { want := collect3(LRGBtoXYZ(RGBtoLRGB(uint32(c.R), uint32(c.G), uint32(c.B)))) if got := collect3(LMStoXYZ(XYZtoLMS(want[0], want[1], want[2]))); !EqFloat64SliceFuzzy(want[:], got[:]) { t.Errorf("LMStoXYZ(XYZtoLMS(%v)) = %v, want unchanged", want, got) return } } } func TestLMSToLRGB(t *testing.T) { for c := range Enum(false, true) { want := collect3(RGBtoLRGB(uint32(c.R), uint32(c.G), uint32(c.B))) l, m, s := LRGBtoLMS(want[0], want[1], want[2]) // test via the optimized LMStoLRGB function. if got := collect3(LMStoLRGB(l, m, s)); !EqFloat64SliceFuzzy(want[:], got[:]) { t.Errorf("LMStoLRGB(LRGBtoLMS(%v)) = %v, want unchanged", want, got) return } // make sure this is equivalent to going through the XYZ colourspace. if got := collect3(XYZtoLRGB(LMStoXYZ(l, m, s))); !EqFloat64SliceFuzzy(want[:], got[:]) { t.Errorf("XYZtoLRGB(LMStoXYZ(LRGBtoLMS(%v))) = %v, want unchanged", want, got) return } } } func TestOKLabToLMS(t *testing.T) { for c := range Enum(false, true) { want := collect3(LRGBtoLMS(RGBtoLRGB(uint32(c.R), uint32(c.G), uint32(c.B)))) if got := collect3(OkLabToLMS(LMStoOkLab(want[0], want[1], want[2]))); !EqFloat64SliceFuzzy(want[:], got[:]) { t.Errorf("OkLabToLMS(LMStoOKLab(%v)) = %v, want unchanged", want, got) return } } } func TestOkLabExamplePairs(t *testing.T) { // The page https://bottosson.github.io/posts/oklab/ lists example XYZ and OkLab pairs, // with the results rounded to three decimal places. examples := []struct{ xyz, lab [3]float64 }{ {[3]float64{0.950, 1.000, 1.089}, [3]float64{1.000, 0.000, 0.000}}, {[3]float64{1.000, 0.000, 0.000}, [3]float64{0.450, 1.236, -0.019}}, {[3]float64{0.000, 1.000, 0.000}, [3]float64{0.922, -0.671, 0.263}}, {[3]float64{0.000, 0.000, 1.000}, [3]float64{0.153, -1.415, -0.449}}, } round := func(x float64) float64 { return math.Round(x*1000) / 1000 } round3 := func(a, b, c float64) [3]float64 { return [3]float64{round(a), round(b), round(c)} } for i, e := range examples { if gotLab := round3(LMStoOkLab(XYZtoLMS(e.xyz[0], e.xyz[1], e.xyz[2]))); gotLab != e.lab { t.Errorf("pair %d: computed lab=%v, want=%v", i+1, gotLab, e.lab) } // note that the example table isn't suitable fo testing OkLab to XYZ conversion due to // the errors introduced by rounding. // // we are depending the round trip conversions being correct, which is verified by TestLMSToXYZ and TestOKLabToLMS. } } type testNXYZAColor [4]float64 func (c testNXYZAColor) RGBA() (_, _, _, _ uint32) { panic("should not be called") } func (c testNXYZAColor) NXYZA() (_, _, _, _ float64) { return c[0], c[1], c[2], c[3] } type testNOkLabAColor [4]float64 func (c testNOkLabAColor) RGBA() (_, _, _, _ uint32) { panic("should not be called") } func (c testNOkLabAColor) NOkLabA() (_, _, _, _ float64) { return c[0], c[1], c[2], c[3] } func TestColorToNOkLabA(t *testing.T) { tests := []struct { input color.Color want [4]float64 }{ { // test special NRGBA handling. color.NRGBA{0x01, 0x23, 0x45, 0x67}, [4]float64{0.25462381167525894, -0.02293028913883799, -0.07098467472369072, float64(0x6767) / 0xffff}, }, { // test special NRGBA64 handling. color.NRGBA64{0x0123, 0x4567, 0x89ab, 0}, [4]float64{0.39601873251000413, -0.03369278598612779, -0.12401844116020128, 0}, }, { // test a colour that can return its linear NRGBA values directly. testNLRGBA{color.NRGBA64{0x0123, 0x4567, 0x89ab, 0xcdef}}, [4]float64{0.39601873251000413, -0.03369278598612779, -0.12401844116020128, float64(0xcdef) / 0xffff}, }, { // test conversion of the values from a a colour that can return NXYZA values directly. testNXYZAColor{0.95, 1., 1.089, .5}, // these were from the canonical test pairs, these values are 1, 0, 0 when rounded to the nearest thousandth. [4]float64{0.9999686754143632, -0.0002580058168537569, -0.00011499756458199784, .5}, }, { // test that we get the values from a colour that can return NOkLabA directly. testNOkLabAColor{math.Inf(1), math.NaN(), math.Inf(-1), -1}, [4]float64{math.Inf(1), math.NaN(), math.Inf(-1), -1}, }, } for _, tt := range tests { t.Run(fmt.Sprintf("%#+v", tt.input), func(t *testing.T) { if got := collect4(ColorToNOkLabA(tt.input)); !EqFloat64SliceFuzzy(got[:], tt.want[:]) { t.Errorf("ColorToNOkLabA(%#+v) = %v, want %v", tt.input, got, tt.want) } }) } }