1package colorful 2 3import ( 4 "image/color" 5 "math" 6 "math/rand" 7 "strings" 8 "testing" 9) 10 11var bench_result float64 // Dummy for benchmarks to avoid optimization 12 13// Checks whether the relative error is below eps 14func almosteq_eps(v1, v2, eps float64) bool { 15 if math.Abs(v1) > delta { 16 return math.Abs((v1-v2)/v1) < eps 17 } 18 return true 19} 20 21// Checks whether the relative error is below the 8bit RGB delta, which should be good enough. 22const delta = 1.0 / 256.0 23 24func almosteq(v1, v2 float64) bool { 25 return almosteq_eps(v1, v2, delta) 26} 27 28// Note: the XYZ, L*a*b*, etc. are using D65 white and D50 white if postfixed by "50". 29// See http://www.brucelindbloom.com/index.html?ColorCalcHelp.html 30// For d50 white, no "adaptation" and the sRGB model are used in colorful 31// HCL values form http://www.easyrgb.com/index.php?X=CALC and missing ones hand-computed from lab ones 32var vals = []struct { 33 c Color 34 hsl [3]float64 35 hsv [3]float64 36 hex string 37 xyz [3]float64 38 xyy [3]float64 39 lab [3]float64 40 lab50 [3]float64 41 luv [3]float64 42 luv50 [3]float64 43 hcl [3]float64 44 hcl50 [3]float64 45 rgba [4]uint32 46 rgb255 [3]uint8 47}{ 48 {Color{1.0, 1.0, 1.0}, [3]float64{ 0.0, 0.0, 1.00}, [3]float64{ 0.0, 0.0, 1.0}, "#ffffff", [3]float64{0.950470, 1.000000, 1.088830}, [3]float64{0.312727, 0.329023, 1.000000}, [3]float64{1.000000, 0.000000, 0.000000}, [3]float64{1.000000,-0.023881,-0.193622}, [3]float64{1.00000, 0.00000, 0.00000}, [3]float64{1.00000,-0.14716,-0.25658}, [3]float64{ 0.0000, 0.000000, 1.000000}, [3]float64{262.9688, 0.195089, 1.000000}, [4]uint32{65535, 65535, 65535, 65535}, [3]uint8{255, 255, 255}}, 49 {Color{0.5, 1.0, 1.0}, [3]float64{180.0, 1.0, 0.75}, [3]float64{180.0, 0.5, 1.0}, "#80ffff", [3]float64{0.626296, 0.832848, 1.073634}, [3]float64{0.247276, 0.328828, 0.832848}, [3]float64{0.931390,-0.353319,-0.108946}, [3]float64{0.931390,-0.374100,-0.301663}, [3]float64{0.93139,-0.53909,-0.11630}, [3]float64{0.93139,-0.67615,-0.35528}, [3]float64{197.1371, 0.369735, 0.931390}, [3]float64{218.8817, 0.480574, 0.931390}, [4]uint32{32768, 65535, 65535, 65535}, [3]uint8{128, 255, 255}}, 50 {Color{1.0, 0.5, 1.0}, [3]float64{300.0, 1.0, 0.75}, [3]float64{300.0, 0.5, 1.0}, "#ff80ff", [3]float64{0.669430, 0.437920, 0.995150}, [3]float64{0.318397, 0.208285, 0.437920}, [3]float64{0.720892, 0.651673,-0.422133}, [3]float64{0.720892, 0.630425,-0.610035}, [3]float64{0.72089, 0.60047,-0.77626}, [3]float64{0.72089, 0.49438,-0.96123}, [3]float64{327.0661, 0.776450, 0.720892}, [3]float64{315.9417, 0.877257, 0.720892}, [4]uint32{65535, 32768, 65535, 65535}, [3]uint8{255, 128, 255}}, 51 {Color{1.0, 1.0, 0.5}, [3]float64{ 60.0, 1.0, 0.75}, [3]float64{ 60.0, 0.5, 1.0}, "#ffff80", [3]float64{0.808654, 0.943273, 0.341930}, [3]float64{0.386203, 0.450496, 0.943273}, [3]float64{0.977637,-0.165795, 0.602017}, [3]float64{0.977637,-0.188424, 0.470410}, [3]float64{0.97764, 0.05759, 0.79816}, [3]float64{0.97764,-0.08628, 0.54731}, [3]float64{105.3975, 0.624430, 0.977637}, [3]float64{111.8287, 0.506743, 0.977637}, [4]uint32{65535, 65535, 32768, 65535}, [3]uint8{255, 255, 128}}, 52 {Color{0.5, 0.5, 1.0}, [3]float64{240.0, 1.0, 0.75}, [3]float64{240.0, 0.5, 1.0}, "#8080ff", [3]float64{0.345256, 0.270768, 0.979954}, [3]float64{0.216329, 0.169656, 0.270768}, [3]float64{0.590453, 0.332846,-0.637099}, [3]float64{0.590453, 0.315806,-0.824040}, [3]float64{0.59045,-0.07568,-1.04877}, [3]float64{0.59045,-0.16257,-1.20027}, [3]float64{297.5843, 0.718805, 0.590453}, [3]float64{290.9689, 0.882482, 0.590453}, [4]uint32{32768, 32768, 65535, 65535}, [3]uint8{128, 128, 255}}, 53 {Color{1.0, 0.5, 0.5}, [3]float64{ 0.0, 1.0, 0.75}, [3]float64{ 0.0, 0.5, 1.0}, "#ff8080", [3]float64{0.527613, 0.381193, 0.248250}, [3]float64{0.455996, 0.329451, 0.381193}, [3]float64{0.681085, 0.483884, 0.228328}, [3]float64{0.681085, 0.464258, 0.110043}, [3]float64{0.68108, 0.92148, 0.19879}, [3]float64{0.68108, 0.82125, 0.02404}, [3]float64{ 25.2610, 0.535049, 0.681085}, [3]float64{ 13.3347, 0.477121, 0.681085}, [4]uint32{65535, 32768, 32768, 65535}, [3]uint8{255, 128, 128}}, 54 {Color{0.5, 1.0, 0.5}, [3]float64{120.0, 1.0, 0.75}, [3]float64{120.0, 0.5, 1.0}, "#80ff80", [3]float64{0.484480, 0.776121, 0.326734}, [3]float64{0.305216, 0.488946, 0.776121}, [3]float64{0.906026,-0.600870, 0.498993}, [3]float64{0.906026,-0.619946, 0.369365}, [3]float64{0.90603,-0.58869, 0.76102}, [3]float64{0.90603,-0.72202, 0.52855}, [3]float64{140.2920, 0.781050, 0.906026}, [3]float64{149.2134, 0.721640, 0.906026}, [4]uint32{32768, 65535, 32768, 65535}, [3]uint8{128, 255, 128}}, 55 {Color{0.5, 0.5, 0.5}, [3]float64{ 0.0, 0.0, 0.50}, [3]float64{ 0.0, 0.0, 0.5}, "#808080", [3]float64{0.203440, 0.214041, 0.233054}, [3]float64{0.312727, 0.329023, 0.214041}, [3]float64{0.533890, 0.000000, 0.000000}, [3]float64{0.533890,-0.014285,-0.115821}, [3]float64{0.53389, 0.00000, 0.00000}, [3]float64{0.53389,-0.07857,-0.13699}, [3]float64{ 0.0000, 0.000000, 0.533890}, [3]float64{262.9688, 0.116699, 0.533890}, [4]uint32{32768, 32768, 32768, 65535}, [3]uint8{128, 128, 128}}, 56 {Color{0.0, 1.0, 1.0}, [3]float64{180.0, 1.0, 0.50}, [3]float64{180.0, 1.0, 1.0}, "#00ffff", [3]float64{0.538014, 0.787327, 1.069496}, [3]float64{0.224656, 0.328760, 0.787327}, [3]float64{0.911132,-0.480875,-0.141312}, [3]float64{0.911132,-0.500630,-0.333781}, [3]float64{0.91113,-0.70477,-0.15204}, [3]float64{0.91113,-0.83886,-0.38582}, [3]float64{196.3762, 0.501209, 0.911132}, [3]float64{213.6923, 0.601698, 0.911132}, [4]uint32{ 0, 65535, 65535, 65535}, [3]uint8{ 0, 255, 255}}, 57 {Color{1.0, 0.0, 1.0}, [3]float64{300.0, 1.0, 0.50}, [3]float64{300.0, 1.0, 1.0}, "#ff00ff", [3]float64{0.592894, 0.284848, 0.969638}, [3]float64{0.320938, 0.154190, 0.284848}, [3]float64{0.603242, 0.982343,-0.608249}, [3]float64{0.603242, 0.961939,-0.794531}, [3]float64{0.60324, 0.84071,-1.08683}, [3]float64{0.60324, 0.75194,-1.24161}, [3]float64{328.2350, 1.155407, 0.603242}, [3]float64{320.4444, 1.247640, 0.603242}, [4]uint32{65535, 0, 65535, 65535}, [3]uint8{255, 0, 255}}, 58 {Color{1.0, 1.0, 0.0}, [3]float64{ 60.0, 1.0, 0.50}, [3]float64{ 60.0, 1.0, 1.0}, "#ffff00", [3]float64{0.770033, 0.927825, 0.138526}, [3]float64{0.419320, 0.505246, 0.927825}, [3]float64{0.971393,-0.215537, 0.944780}, [3]float64{0.971393,-0.237800, 0.847398}, [3]float64{0.97139, 0.07706, 1.06787}, [3]float64{0.97139,-0.06590, 0.81862}, [3]float64{102.8512, 0.969054, 0.971393}, [3]float64{105.6754, 0.880131, 0.971393}, [4]uint32{65535, 65535, 0, 65535}, [3]uint8{255, 255, 0}}, 59 {Color{0.0, 0.0, 1.0}, [3]float64{240.0, 1.0, 0.50}, [3]float64{240.0, 1.0, 1.0}, "#0000ff", [3]float64{0.180437, 0.072175, 0.950304}, [3]float64{0.150000, 0.060000, 0.072175}, [3]float64{0.322970, 0.791875,-1.078602}, [3]float64{0.322970, 0.778150,-1.263638}, [3]float64{0.32297,-0.09405,-1.30342}, [3]float64{0.32297,-0.14158,-1.38629}, [3]float64{306.2849, 1.338076, 0.322970}, [3]float64{301.6248, 1.484014, 0.322970}, [4]uint32{ 0, 0, 65535, 65535}, [3]uint8{ 0, 0, 255}}, 60 {Color{0.0, 1.0, 0.0}, [3]float64{120.0, 1.0, 0.50}, [3]float64{120.0, 1.0, 1.0}, "#00ff00", [3]float64{0.357576, 0.715152, 0.119192}, [3]float64{0.300000, 0.600000, 0.715152}, [3]float64{0.877347,-0.861827, 0.831793}, [3]float64{0.877347,-0.879067, 0.739170}, [3]float64{0.87735,-0.83078, 1.07398}, [3]float64{0.87735,-0.95989, 0.84887}, [3]float64{136.0160, 1.197759, 0.877347}, [3]float64{139.9409, 1.148534, 0.877347}, [4]uint32{ 0, 65535, 0, 65535}, [3]uint8{ 0, 255, 0}}, 61 {Color{1.0, 0.0, 0.0}, [3]float64{ 0.0, 1.0, 0.50}, [3]float64{ 0.0, 1.0, 1.0}, "#ff0000", [3]float64{0.412456, 0.212673, 0.019334}, [3]float64{0.640000, 0.330000, 0.212673}, [3]float64{0.532408, 0.800925, 0.672032}, [3]float64{0.532408, 0.782845, 0.621518}, [3]float64{0.53241, 1.75015, 0.37756}, [3]float64{0.53241, 1.67180, 0.24096}, [3]float64{ 39.9990, 1.045518, 0.532408}, [3]float64{ 38.4469, 0.999566, 0.532408}, [4]uint32{65535, 0, 0, 65535}, [3]uint8{255, 0, 0}}, 62 {Color{0.0, 0.0, 0.0}, [3]float64{ 0.0, 0.0, 0.00}, [3]float64{ 0.0, 0.0, 0.0}, "#000000", [3]float64{0.000000, 0.000000, 0.000000}, [3]float64{0.312727, 0.329023, 0.000000}, [3]float64{0.000000, 0.000000, 0.000000}, [3]float64{0.000000, 0.000000, 0.000000}, [3]float64{0.00000, 0.00000, 0.00000}, [3]float64{0.00000, 0.00000, 0.00000}, [3]float64{ 0.0000, 0.000000, 0.000000}, [3]float64{ 0.0000, 0.000000, 0.000000}, [4]uint32{ 0, 0, 0, 65535}, [3]uint8{ 0, 0, 0}}, 63} 64 65// For testing short-hex values, since the above contains colors which don't 66// have corresponding short hexes. 67var shorthexvals = []struct { 68 c Color 69 hex string 70}{ 71 {Color{1.0, 1.0, 1.0}, "#fff"}, 72 {Color{0.6, 1.0, 1.0}, "#9ff"}, 73 {Color{1.0, 0.6, 1.0}, "#f9f"}, 74 {Color{1.0, 1.0, 0.6}, "#ff9"}, 75 {Color{0.6, 0.6, 1.0}, "#99f"}, 76 {Color{1.0, 0.6, 0.6}, "#f99"}, 77 {Color{0.6, 1.0, 0.6}, "#9f9"}, 78 {Color{0.6, 0.6, 0.6}, "#999"}, 79 {Color{0.0, 1.0, 1.0}, "#0ff"}, 80 {Color{1.0, 0.0, 1.0}, "#f0f"}, 81 {Color{1.0, 1.0, 0.0}, "#ff0"}, 82 {Color{0.0, 0.0, 1.0}, "#00f"}, 83 {Color{0.0, 1.0, 0.0}, "#0f0"}, 84 {Color{1.0, 0.0, 0.0}, "#f00"}, 85 {Color{0.0, 0.0, 0.0}, "#000"}, 86} 87 88/// RGBA /// 89//////////// 90 91func TestRGBAConversion(t *testing.T) { 92 for i, tt := range vals { 93 r, g, b, a := tt.c.RGBA() 94 if r != tt.rgba[0] || g != tt.rgba[1] || b != tt.rgba[2] || a != tt.rgba[3] { 95 t.Errorf("%v. %v.RGBA() => (%v), want %v (delta %v)", i, tt.c, []uint32{r, g, b, a}, tt.rgba, delta) 96 } 97 } 98} 99 100/// RGB255 /// 101//////////// 102 103func TestRGB255Conversion(t *testing.T) { 104 for i, tt := range vals { 105 r, g, b := tt.c.RGB255() 106 if r != tt.rgb255[0] || g != tt.rgb255[1] || b != tt.rgb255[2] { 107 t.Errorf("%v. %v.RGB255() => (%v), want %v (delta %v)", i, tt.c, []uint8{r, g, b}, tt.rgb255, delta) 108 } 109 } 110} 111 112/// HSV /// 113/////////// 114 115func TestHsvCreation(t *testing.T) { 116 for i, tt := range vals { 117 c := Hsv(tt.hsv[0], tt.hsv[1], tt.hsv[2]) 118 if !c.AlmostEqualRgb(tt.c) { 119 t.Errorf("%v. Hsv(%v) => (%v), want %v (delta %v)", i, tt.hsv, c, tt.c, delta) 120 } 121 } 122} 123 124func TestHsvConversion(t *testing.T) { 125 for i, tt := range vals { 126 h, s, v := tt.c.Hsv() 127 if !almosteq(h, tt.hsv[0]) || !almosteq(s, tt.hsv[1]) || !almosteq(v, tt.hsv[2]) { 128 t.Errorf("%v. %v.Hsv() => (%v), want %v (delta %v)", i, tt.c, []float64{h, s, v}, tt.hsv, delta) 129 } 130 } 131} 132 133/// HSL /// 134/////////// 135 136func TestHslCreation(t *testing.T) { 137 for i, tt := range vals { 138 c := Hsl(tt.hsl[0], tt.hsl[1], tt.hsl[2]) 139 if !c.AlmostEqualRgb(tt.c) { 140 t.Errorf("%v. Hsl(%v) => (%v), want %v (delta %v)", i, tt.hsl, c, tt.c, delta) 141 } 142 } 143} 144 145func TestHslConversion(t *testing.T) { 146 for i, tt := range vals { 147 h, s, l := tt.c.Hsl() 148 if !almosteq(h, tt.hsl[0]) || !almosteq(s, tt.hsl[1]) || !almosteq(l, tt.hsl[2]) { 149 t.Errorf("%v. %v.Hsl() => (%v), want %v (delta %v)", i, tt.c, []float64{h, s, l}, tt.hsl, delta) 150 } 151 } 152} 153 154/// Hex /// 155/////////// 156 157func TestHexCreation(t *testing.T) { 158 for i, tt := range vals { 159 c, err := Hex(tt.hex) 160 if err != nil || !c.AlmostEqualRgb(tt.c) { 161 t.Errorf("%v. Hex(%v) => (%v), want %v (delta %v)", i, tt.hex, c, tt.c, delta) 162 } 163 } 164} 165 166func TestHEXCreation(t *testing.T) { 167 for i, tt := range vals { 168 c, err := Hex(strings.ToUpper(tt.hex)) 169 if err != nil || !c.AlmostEqualRgb(tt.c) { 170 t.Errorf("%v. HEX(%v) => (%v), want %v (delta %v)", i, strings.ToUpper(tt.hex), c, tt.c, delta) 171 } 172 } 173} 174 175func TestShortHexCreation(t *testing.T) { 176 for i, tt := range shorthexvals { 177 c, err := Hex(tt.hex) 178 if err != nil || !c.AlmostEqualRgb(tt.c) { 179 t.Errorf("%v. Hex(%v) => (%v), want %v (delta %v)", i, tt.hex, c, tt.c, delta) 180 } 181 } 182} 183 184func TestShortHEXCreation(t *testing.T) { 185 for i, tt := range shorthexvals { 186 c, err := Hex(strings.ToUpper(tt.hex)) 187 if err != nil || !c.AlmostEqualRgb(tt.c) { 188 t.Errorf("%v. Hex(%v) => (%v), want %v (delta %v)", i, strings.ToUpper(tt.hex), c, tt.c, delta) 189 } 190 } 191} 192 193func TestHexConversion(t *testing.T) { 194 for i, tt := range vals { 195 hex := tt.c.Hex() 196 if hex != tt.hex { 197 t.Errorf("%v. %v.Hex() => (%v), want %v (delta %v)", i, tt.c, hex, tt.hex, delta) 198 } 199 } 200} 201 202/// Linear /// 203////////////// 204 205// LinearRgb itself is implicitly tested by XYZ conversions below (they use it). 206// So what we do here is just test that the FastLinearRgb approximation is "good enough" 207func TestFastLinearRgb(t *testing.T) { 208 const eps = 6.0 / 255.0 // We want that "within 6 RGB values total" is "good enough". 209 210 for r := 0.0; r < 256.0; r++ { 211 for g := 0.0; g < 256.0; g++ { 212 for b := 0.0; b < 256.0; b++ { 213 c := Color{r / 255.0, g / 255.0, b / 255.0} 214 r_want, g_want, b_want := c.LinearRgb() 215 r_appr, g_appr, b_appr := c.FastLinearRgb() 216 dr, dg, db := math.Abs(r_want-r_appr), math.Abs(g_want-g_appr), math.Abs(b_want-b_appr) 217 if dr+dg+db > eps { 218 t.Errorf("FastLinearRgb not precise enough for %v: differences are (%v, %v, %v), allowed total difference is %v", c, dr, dg, db, eps) 219 return 220 } 221 222 c_want := LinearRgb(r/255.0, g/255.0, b/255.0) 223 c_appr := FastLinearRgb(r/255.0, g/255.0, b/255.0) 224 dr, dg, db = math.Abs(c_want.R-c_appr.R), math.Abs(c_want.G-c_appr.G), math.Abs(c_want.B-c_appr.B) 225 if dr+dg+db > eps { 226 t.Errorf("FastLinearRgb not precise enough for (%v, %v, %v): differences are (%v, %v, %v), allowed total difference is %v", r, g, b, dr, dg, db, eps) 227 return 228 } 229 } 230 } 231 } 232} 233 234// Also include some benchmarks to make sure the `Fast` versions are actually significantly faster! 235// (Sounds silly, but the original ones weren't!) 236 237func BenchmarkColorToLinear(bench *testing.B) { 238 var r, g, b float64 239 for n := 0; n < bench.N; n++ { 240 r, g, b = Color{rand.Float64(), rand.Float64(), rand.Float64()}.LinearRgb() 241 } 242 bench_result = r + g + b 243} 244 245func BenchmarkFastColorToLinear(bench *testing.B) { 246 var r, g, b float64 247 for n := 0; n < bench.N; n++ { 248 r, g, b = Color{rand.Float64(), rand.Float64(), rand.Float64()}.FastLinearRgb() 249 } 250 bench_result = r + g + b 251} 252 253func BenchmarkLinearToColor(bench *testing.B) { 254 var c Color 255 for n := 0; n < bench.N; n++ { 256 c = LinearRgb(rand.Float64(), rand.Float64(), rand.Float64()) 257 } 258 bench_result = c.R + c.G + c.B 259} 260 261func BenchmarkFastLinearToColor(bench *testing.B) { 262 var c Color 263 for n := 0; n < bench.N; n++ { 264 c = FastLinearRgb(rand.Float64(), rand.Float64(), rand.Float64()) 265 } 266 bench_result = c.R + c.G + c.B 267} 268 269/// XYZ /// 270/////////// 271func TestXyzCreation(t *testing.T) { 272 for i, tt := range vals { 273 c := Xyz(tt.xyz[0], tt.xyz[1], tt.xyz[2]) 274 if !c.AlmostEqualRgb(tt.c) { 275 t.Errorf("%v. Xyz(%v) => (%v), want %v (delta %v)", i, tt.xyz, c, tt.c, delta) 276 } 277 } 278} 279 280func TestXyzConversion(t *testing.T) { 281 for i, tt := range vals { 282 x, y, z := tt.c.Xyz() 283 if !almosteq(x, tt.xyz[0]) || !almosteq(y, tt.xyz[1]) || !almosteq(z, tt.xyz[2]) { 284 t.Errorf("%v. %v.Xyz() => (%v), want %v (delta %v)", i, tt.c, [3]float64{x, y, z}, tt.xyz, delta) 285 } 286 } 287} 288 289/// xyY /// 290/////////// 291func TestXyyCreation(t *testing.T) { 292 for i, tt := range vals { 293 c := Xyy(tt.xyy[0], tt.xyy[1], tt.xyy[2]) 294 if !c.AlmostEqualRgb(tt.c) { 295 t.Errorf("%v. Xyy(%v) => (%v), want %v (delta %v)", i, tt.xyy, c, tt.c, delta) 296 } 297 } 298} 299 300func TestXyyConversion(t *testing.T) { 301 for i, tt := range vals { 302 x, y, Y := tt.c.Xyy() 303 if !almosteq(x, tt.xyy[0]) || !almosteq(y, tt.xyy[1]) || !almosteq(Y, tt.xyy[2]) { 304 t.Errorf("%v. %v.Xyy() => (%v), want %v (delta %v)", i, tt.c, [3]float64{x, y, Y}, tt.xyy, delta) 305 } 306 } 307} 308 309/// L*a*b* /// 310////////////// 311func TestLabCreation(t *testing.T) { 312 for i, tt := range vals { 313 c := Lab(tt.lab[0], tt.lab[1], tt.lab[2]) 314 if !c.AlmostEqualRgb(tt.c) { 315 t.Errorf("%v. Lab(%v) => (%v), want %v (delta %v)", i, tt.lab, c, tt.c, delta) 316 } 317 } 318} 319 320func TestLabConversion(t *testing.T) { 321 for i, tt := range vals { 322 l, a, b := tt.c.Lab() 323 if !almosteq(l, tt.lab[0]) || !almosteq(a, tt.lab[1]) || !almosteq(b, tt.lab[2]) { 324 t.Errorf("%v. %v.Lab() => (%v), want %v (delta %v)", i, tt.c, [3]float64{l, a, b}, tt.lab, delta) 325 } 326 } 327} 328 329func TestLabWhiteRefCreation(t *testing.T) { 330 for i, tt := range vals { 331 c := LabWhiteRef(tt.lab50[0], tt.lab50[1], tt.lab50[2], D50) 332 if !c.AlmostEqualRgb(tt.c) { 333 t.Errorf("%v. LabWhiteRef(%v, D50) => (%v), want %v (delta %v)", i, tt.lab50, c, tt.c, delta) 334 } 335 } 336} 337 338func TestLabWhiteRefConversion(t *testing.T) { 339 for i, tt := range vals { 340 l, a, b := tt.c.LabWhiteRef(D50) 341 if !almosteq(l, tt.lab50[0]) || !almosteq(a, tt.lab50[1]) || !almosteq(b, tt.lab50[2]) { 342 t.Errorf("%v. %v.LabWhiteRef(D50) => (%v), want %v (delta %v)", i, tt.c, [3]float64{l, a, b}, tt.lab50, delta) 343 } 344 } 345} 346 347/// L*u*v* /// 348////////////// 349func TestLuvCreation(t *testing.T) { 350 for i, tt := range vals { 351 c := Luv(tt.luv[0], tt.luv[1], tt.luv[2]) 352 if !c.AlmostEqualRgb(tt.c) { 353 t.Errorf("%v. Luv(%v) => (%v), want %v (delta %v)", i, tt.luv, c, tt.c, delta) 354 } 355 } 356} 357 358func TestLuvConversion(t *testing.T) { 359 for i, tt := range vals { 360 l, u, v := tt.c.Luv() 361 if !almosteq(l, tt.luv[0]) || !almosteq(u, tt.luv[1]) || !almosteq(v, tt.luv[2]) { 362 t.Errorf("%v. %v.Luv() => (%v), want %v (delta %v)", i, tt.c, [3]float64{l, u, v}, tt.luv, delta) 363 } 364 } 365} 366 367func TestLuvWhiteRefCreation(t *testing.T) { 368 for i, tt := range vals { 369 c := LuvWhiteRef(tt.luv50[0], tt.luv50[1], tt.luv50[2], D50) 370 if !c.AlmostEqualRgb(tt.c) { 371 t.Errorf("%v. LuvWhiteRef(%v, D50) => (%v), want %v (delta %v)", i, tt.luv50, c, tt.c, delta) 372 } 373 } 374} 375 376func TestLuvWhiteRefConversion(t *testing.T) { 377 for i, tt := range vals { 378 l, u, v := tt.c.LuvWhiteRef(D50) 379 if !almosteq(l, tt.luv50[0]) || !almosteq(u, tt.luv50[1]) || !almosteq(v, tt.luv50[2]) { 380 t.Errorf("%v. %v.LuvWhiteRef(D50) => (%v), want %v (delta %v)", i, tt.c, [3]float64{l, u, v}, tt.luv50, delta) 381 } 382 } 383} 384 385/// HCL /// 386/////////// 387// CIE-L*a*b* in polar coordinates. 388func TestHclCreation(t *testing.T) { 389 for i, tt := range vals { 390 c := Hcl(tt.hcl[0], tt.hcl[1], tt.hcl[2]) 391 if !c.AlmostEqualRgb(tt.c) { 392 t.Errorf("%v. Hcl(%v) => (%v), want %v (delta %v)", i, tt.hcl, c, tt.c, delta) 393 } 394 } 395} 396 397func TestHclConversion(t *testing.T) { 398 for i, tt := range vals { 399 h, c, l := tt.c.Hcl() 400 if !almosteq(h, tt.hcl[0]) || !almosteq(c, tt.hcl[1]) || !almosteq(l, tt.hcl[2]) { 401 t.Errorf("%v. %v.Hcl() => (%v), want %v (delta %v)", i, tt.c, [3]float64{h, c, l}, tt.hcl, delta) 402 } 403 } 404} 405 406func TestHclWhiteRefCreation(t *testing.T) { 407 for i, tt := range vals { 408 c := HclWhiteRef(tt.hcl50[0], tt.hcl50[1], tt.hcl50[2], D50) 409 if !c.AlmostEqualRgb(tt.c) { 410 t.Errorf("%v. HclWhiteRef(%v, D50) => (%v), want %v (delta %v)", i, tt.hcl50, c, tt.c, delta) 411 } 412 } 413} 414 415func TestHclWhiteRefConversion(t *testing.T) { 416 for i, tt := range vals { 417 h, c, l := tt.c.HclWhiteRef(D50) 418 if !almosteq(h, tt.hcl50[0]) || !almosteq(c, tt.hcl50[1]) || !almosteq(l, tt.hcl50[2]) { 419 t.Errorf("%v. %v.HclWhiteRef(D50) => (%v), want %v (delta %v)", i, tt.c, [3]float64{h, c, l}, tt.hcl50, delta) 420 } 421 } 422} 423 424/// Test distances /// 425////////////////////// 426 427// Ground-truth from http://www.brucelindbloom.com/index.html?ColorDifferenceCalcHelp.html 428var dists = []struct { 429 c1 Color 430 c2 Color 431 d76 float64 // That's also dLab 432 d94 float64 433 d00 float64 434}{ 435 {Color{1.0, 1.0, 1.0}, Color{1.0, 1.0, 1.0}, 0.0, 0.0, 0.0}, 436 {Color{0.0, 0.0, 0.0}, Color{0.0, 0.0, 0.0}, 0.0, 0.0, 0.0}, 437 438 // Just pairs of values of the table way above. 439 {Lab(1.000000, 0.000000, 0.000000), Lab(0.931390, -0.353319, -0.108946), 0.37604638, 0.37604638, 0.23528129}, 440 {Lab(0.720892, 0.651673, -0.422133), Lab(0.977637, -0.165795, 0.602017), 1.33531088, 0.65466377, 0.75175896}, 441 {Lab(0.590453, 0.332846, -0.637099), Lab(0.681085, 0.483884, 0.228328), 0.88317072, 0.42541075, 0.37688153}, 442 {Lab(0.906026, -0.600870, 0.498993), Lab(0.533890, 0.000000, 0.000000), 0.86517280, 0.41038323, 0.39960503}, 443 {Lab(0.911132, -0.480875, -0.141312), Lab(0.603242, 0.982343, -0.608249), 1.56647162, 0.87431457, 0.57983482}, 444 {Lab(0.971393, -0.215537, 0.944780), Lab(0.322970, 0.791875, -1.078602), 2.35146891, 1.11858192, 1.03426977}, 445 {Lab(0.877347, -0.861827, 0.831793), Lab(0.532408, 0.800925, 0.672032), 1.70565338, 0.68800270, 0.86608245}, 446} 447 448func TestLabDistance(t *testing.T) { 449 for i, tt := range dists { 450 d := tt.c1.DistanceCIE76(tt.c2) 451 if !almosteq(d, tt.d76) { 452 t.Errorf("%v. %v.DistanceCIE76(%v) => (%v), want %v (delta %v)", i, tt.c1, tt.c2, d, tt.d76, delta) 453 } 454 } 455} 456 457func TestCIE94Distance(t *testing.T) { 458 for i, tt := range dists { 459 d := tt.c1.DistanceCIE94(tt.c2) 460 if !almosteq(d, tt.d94) { 461 t.Errorf("%v. %v.DistanceCIE94(%v) => (%v), want %v (delta %v)", i, tt.c1, tt.c2, d, tt.d94, delta) 462 } 463 } 464} 465 466func TestCIEDE2000Distance(t *testing.T) { 467 for i, tt := range dists { 468 d := tt.c1.DistanceCIEDE2000(tt.c2) 469 if !almosteq(d, tt.d00) { 470 t.Errorf("%v. %v.DistanceCIEDE2000(%v) => (%v), want %v (delta %v)", i, tt.c1, tt.c2, d, tt.d00, delta) 471 } 472 } 473} 474 475/// Test utilities /// 476////////////////////// 477 478func TestClamp(t *testing.T) { 479 c_orig := Color{1.1, -0.1, 0.5} 480 c_want := Color{1.0, 0.0, 0.5} 481 if c_orig.Clamped() != c_want { 482 t.Errorf("%v.Clamped() => %v, want %v", c_orig, c_orig.Clamped(), c_want) 483 } 484} 485 486func TestMakeColor(t *testing.T) { 487 c_orig_nrgba := color.NRGBA{123, 45, 67, 255} 488 c_ours, ok := MakeColor(c_orig_nrgba) 489 r, g, b := c_ours.RGB255() 490 if r != 123 || g != 45 || b != 67 || !ok { 491 t.Errorf("NRGBA->Colorful->RGB255 error: %v became (%v, %v, %v, %t)", c_orig_nrgba, r, g, b, ok) 492 } 493 494 c_orig_nrgba64 := color.NRGBA64{123 << 8, 45 << 8, 67 << 8, 0xffff} 495 c_ours, ok = MakeColor(c_orig_nrgba64) 496 r, g, b = c_ours.RGB255() 497 if r != 123 || g != 45 || b != 67 || !ok { 498 t.Errorf("NRGBA64->Colorful->RGB255 error: %v became (%v, %v, %v, %t)", c_orig_nrgba64, r, g, b, ok) 499 } 500 501 c_orig_gray := color.Gray{123} 502 c_ours, ok = MakeColor(c_orig_gray) 503 r, g, b = c_ours.RGB255() 504 if r != 123 || g != 123 || b != 123 || !ok { 505 t.Errorf("Gray->Colorful->RGB255 error: %v became (%v, %v, %v, %t)", c_orig_gray, r, g, b, ok) 506 } 507 508 c_orig_gray16 := color.Gray16{123 << 8} 509 c_ours, ok = MakeColor(c_orig_gray16) 510 r, g, b = c_ours.RGB255() 511 if r != 123 || g != 123 || b != 123 || !ok { 512 t.Errorf("Gray16->Colorful->RGB255 error: %v became (%v, %v, %v, %t)", c_orig_gray16, r, g, b, ok) 513 } 514 515 c_orig_rgba := color.RGBA{255, 255, 255, 0} 516 c_ours, ok = MakeColor(c_orig_rgba) 517 r, g, b = c_ours.RGB255() 518 if r != 0 || g != 0 || b != 0 || ok { 519 t.Errorf("RGBA->Colorful->RGB255 error: %v became (%v, %v, %v, %t)", c_orig_rgba, r, g, b, ok) 520 } 521} 522 523/// Issues raised on github /// 524/////////////////////////////// 525 526// https://github.com/lucasb-eyer/go-colorful/issues/11 527func TestIssue11(t *testing.T) { 528 c1hex := "#1a1a46" 529 c2hex := "#666666" 530 531 c1, _ := Hex(c1hex) 532 c2, _ := Hex(c2hex) 533 534 blend := c1.BlendHsv(c2, 0).Hex() 535 if blend != c1hex { 536 t.Errorf("Issue11: %v --Hsv-> %v = %v, want %v", c1hex, c2hex, blend, c1hex) 537 } 538 blend = c1.BlendHsv(c2, 1).Hex() 539 if blend != c2hex { 540 t.Errorf("Issue11: %v --Hsv-> %v = %v, want %v", c1hex, c2hex, blend, c2hex) 541 } 542 543 blend = c1.BlendLuv(c2, 0).Hex() 544 if blend != c1hex { 545 t.Errorf("Issue11: %v --Luv-> %v = %v, want %v", c1hex, c2hex, blend, c1hex) 546 } 547 blend = c1.BlendLuv(c2, 1).Hex() 548 if blend != c2hex { 549 t.Errorf("Issue11: %v --Luv-> %v = %v, want %v", c1hex, c2hex, blend, c2hex) 550 } 551 552 blend = c1.BlendRgb(c2, 0).Hex() 553 if blend != c1hex { 554 t.Errorf("Issue11: %v --Rgb-> %v = %v, want %v", c1hex, c2hex, blend, c1hex) 555 } 556 blend = c1.BlendRgb(c2, 1).Hex() 557 if blend != c2hex { 558 t.Errorf("Issue11: %v --Rgb-> %v = %v, want %v", c1hex, c2hex, blend, c2hex) 559 } 560 561 blend = c1.BlendLab(c2, 0).Hex() 562 if blend != c1hex { 563 t.Errorf("Issue11: %v --Lab-> %v = %v, want %v", c1hex, c2hex, blend, c1hex) 564 } 565 blend = c1.BlendLab(c2, 1).Hex() 566 if blend != c2hex { 567 t.Errorf("Issue11: %v --Lab-> %v = %v, want %v", c1hex, c2hex, blend, c2hex) 568 } 569 570 blend = c1.BlendHcl(c2, 0).Hex() 571 if blend != c1hex { 572 t.Errorf("Issue11: %v --Hcl-> %v = %v, want %v", c1hex, c2hex, blend, c1hex) 573 } 574 blend = c1.BlendHcl(c2, 1).Hex() 575 if blend != c2hex { 576 t.Errorf("Issue11: %v --Hcl-> %v = %v, want %v", c1hex, c2hex, blend, c2hex) 577 } 578} 579 580// For testing angular interpolation internal function 581// NOTE: They are being tested in both directions. 582var anglevals = []struct { 583 a0 float64 584 a1 float64 585 t float64 586 at float64 587}{ 588 {0.0, 1.0, 0.0, 0.0}, 589 {0.0, 1.0, 0.25, 0.25}, 590 {0.0, 1.0, 0.5, 0.5}, 591 {0.0, 1.0, 1.0, 1.0}, 592 {0.0, 90.0, 0.0, 0.0}, 593 {0.0, 90.0, 0.25, 22.5}, 594 {0.0, 90.0, 0.5, 45.0}, 595 {0.0, 90.0, 1.0, 90.0}, 596 {0.0, 178.0, 0.0, 0.0}, // Exact 0-180 is ambiguous. 597 {0.0, 178.0, 0.25, 44.5}, 598 {0.0, 178.0, 0.5, 89.0}, 599 {0.0, 178.0, 1.0, 178.0}, 600 {0.0, 182.0, 0.0, 0.0}, // Exact 0-180 is ambiguous. 601 {0.0, 182.0, 0.25, 315.5}, 602 {0.0, 182.0, 0.5, 271.0}, 603 {0.0, 182.0, 1.0, 182.0}, 604 {0.0, 270.0, 0.0, 0.0}, 605 {0.0, 270.0, 0.25, 337.5}, 606 {0.0, 270.0, 0.5, 315.0}, 607 {0.0, 270.0, 1.0, 270.0}, 608 {0.0, 359.0, 0.0, 0.0}, 609 {0.0, 359.0, 0.25, 359.75}, 610 {0.0, 359.0, 0.5, 359.5}, 611 {0.0, 359.0, 1.0, 359.0}, 612} 613 614func TestInterpolation(t *testing.T) { 615 // Forward 616 for i, tt := range anglevals { 617 res := interp_angle(tt.a0, tt.a1, tt.t) 618 if !almosteq_eps(res, tt.at, 1e-15) { 619 t.Errorf("%v. interp_angle(%v, %v, %v) => (%v), want %v", i, tt.a0, tt.a1, tt.t, res, tt.at) 620 } 621 } 622 // Backward 623 for i, tt := range anglevals { 624 res := interp_angle(tt.a1, tt.a0, 1.0-tt.t) 625 if !almosteq_eps(res, tt.at, 1e-15) { 626 t.Errorf("%v. interp_angle(%v, %v, %v) => (%v), want %v", i, tt.a1, tt.a0, 1.0-tt.t, res, tt.at) 627 } 628 } 629} 630