colors.go (27612B)
1 // The colorful package provides all kinds of functions for working with colors. 2 package colorful 3 4 import ( 5 "fmt" 6 "image/color" 7 "math" 8 ) 9 10 // A color is stored internally using sRGB (standard RGB) values in the range 0-1 11 type Color struct { 12 R, G, B float64 13 } 14 15 // Implement the Go color.Color interface. 16 func (col Color) RGBA() (r, g, b, a uint32) { 17 r = uint32(col.R*65535.0 + 0.5) 18 g = uint32(col.G*65535.0 + 0.5) 19 b = uint32(col.B*65535.0 + 0.5) 20 a = 0xFFFF 21 return 22 } 23 24 // Constructs a colorful.Color from something implementing color.Color 25 func MakeColor(col color.Color) (Color, bool) { 26 r, g, b, a := col.RGBA() 27 if a == 0 { 28 return Color{0, 0, 0}, false 29 } 30 31 // Since color.Color is alpha pre-multiplied, we need to divide the 32 // RGB values by alpha again in order to get back the original RGB. 33 r *= 0xffff 34 r /= a 35 g *= 0xffff 36 g /= a 37 b *= 0xffff 38 b /= a 39 40 return Color{float64(r) / 65535.0, float64(g) / 65535.0, float64(b) / 65535.0}, true 41 } 42 43 // Might come in handy sometimes to reduce boilerplate code. 44 func (col Color) RGB255() (r, g, b uint8) { 45 r = uint8(col.R*255.0 + 0.5) 46 g = uint8(col.G*255.0 + 0.5) 47 b = uint8(col.B*255.0 + 0.5) 48 return 49 } 50 51 // Used to simplify HSLuv testing. 52 func (col Color) values() (float64, float64, float64) { 53 return col.R, col.G, col.B 54 } 55 56 // This is the tolerance used when comparing colors using AlmostEqualRgb. 57 const Delta = 1.0 / 255.0 58 59 // This is the default reference white point. 60 var D65 = [3]float64{0.95047, 1.00000, 1.08883} 61 62 // And another one. 63 var D50 = [3]float64{0.96422, 1.00000, 0.82521} 64 65 // Checks whether the color exists in RGB space, i.e. all values are in [0..1] 66 func (c Color) IsValid() bool { 67 return 0.0 <= c.R && c.R <= 1.0 && 68 0.0 <= c.G && c.G <= 1.0 && 69 0.0 <= c.B && c.B <= 1.0 70 } 71 72 // clamp01 clamps from 0 to 1. 73 func clamp01(v float64) float64 { 74 return math.Max(0.0, math.Min(v, 1.0)) 75 } 76 77 // Returns Clamps the color into valid range, clamping each value to [0..1] 78 // If the color is valid already, this is a no-op. 79 func (c Color) Clamped() Color { 80 return Color{clamp01(c.R), clamp01(c.G), clamp01(c.B)} 81 } 82 83 func sq(v float64) float64 { 84 return v * v 85 } 86 87 func cub(v float64) float64 { 88 return v * v * v 89 } 90 91 // DistanceRgb computes the distance between two colors in RGB space. 92 // This is not a good measure! Rather do it in Lab space. 93 func (c1 Color) DistanceRgb(c2 Color) float64 { 94 return math.Sqrt(sq(c1.R-c2.R) + sq(c1.G-c2.G) + sq(c1.B-c2.B)) 95 } 96 97 // DistanceLinearRGB computes the distance between two colors in linear RGB 98 // space. This is not useful for measuring how humans perceive color, but 99 // might be useful for other things, like dithering. 100 func (c1 Color) DistanceLinearRGB(c2 Color) float64 { 101 r1, g1, b1 := c1.LinearRgb() 102 r2, g2, b2 := c2.LinearRgb() 103 return math.Sqrt(sq(r1-r2) + sq(g1-g2) + sq(b1-b2)) 104 } 105 106 // Check for equality between colors within the tolerance Delta (1/255). 107 func (c1 Color) AlmostEqualRgb(c2 Color) bool { 108 return math.Abs(c1.R-c2.R)+ 109 math.Abs(c1.G-c2.G)+ 110 math.Abs(c1.B-c2.B) < 3.0*Delta 111 } 112 113 // You don't really want to use this, do you? Go for BlendLab, BlendLuv or BlendHcl. 114 func (c1 Color) BlendRgb(c2 Color, t float64) Color { 115 return Color{c1.R + t*(c2.R-c1.R), 116 c1.G + t*(c2.G-c1.G), 117 c1.B + t*(c2.B-c1.B)} 118 } 119 120 // Utility used by Hxx color-spaces for interpolating between two angles in [0,360]. 121 func interp_angle(a0, a1, t float64) float64 { 122 // Based on the answer here: http://stackoverflow.com/a/14498790/2366315 123 // With potential proof that it works here: http://math.stackexchange.com/a/2144499 124 delta := math.Mod(math.Mod(a1-a0, 360.0)+540, 360.0) - 180.0 125 return math.Mod(a0+t*delta+360.0, 360.0) 126 } 127 128 /// HSV /// 129 /////////// 130 // From http://en.wikipedia.org/wiki/HSL_and_HSV 131 // Note that h is in [0..360] and s,v in [0..1] 132 133 // Hsv returns the Hue [0..360], Saturation and Value [0..1] of the color. 134 func (col Color) Hsv() (h, s, v float64) { 135 min := math.Min(math.Min(col.R, col.G), col.B) 136 v = math.Max(math.Max(col.R, col.G), col.B) 137 C := v - min 138 139 s = 0.0 140 if v != 0.0 { 141 s = C / v 142 } 143 144 h = 0.0 // We use 0 instead of undefined as in wp. 145 if min != v { 146 if v == col.R { 147 h = math.Mod((col.G-col.B)/C, 6.0) 148 } 149 if v == col.G { 150 h = (col.B-col.R)/C + 2.0 151 } 152 if v == col.B { 153 h = (col.R-col.G)/C + 4.0 154 } 155 h *= 60.0 156 if h < 0.0 { 157 h += 360.0 158 } 159 } 160 return 161 } 162 163 // Hsv creates a new Color given a Hue in [0..360], a Saturation and a Value in [0..1] 164 func Hsv(H, S, V float64) Color { 165 Hp := H / 60.0 166 C := V * S 167 X := C * (1.0 - math.Abs(math.Mod(Hp, 2.0)-1.0)) 168 169 m := V - C 170 r, g, b := 0.0, 0.0, 0.0 171 172 switch { 173 case 0.0 <= Hp && Hp < 1.0: 174 r = C 175 g = X 176 case 1.0 <= Hp && Hp < 2.0: 177 r = X 178 g = C 179 case 2.0 <= Hp && Hp < 3.0: 180 g = C 181 b = X 182 case 3.0 <= Hp && Hp < 4.0: 183 g = X 184 b = C 185 case 4.0 <= Hp && Hp < 5.0: 186 r = X 187 b = C 188 case 5.0 <= Hp && Hp < 6.0: 189 r = C 190 b = X 191 } 192 193 return Color{m + r, m + g, m + b} 194 } 195 196 // You don't really want to use this, do you? Go for BlendLab, BlendLuv or BlendHcl. 197 func (c1 Color) BlendHsv(c2 Color, t float64) Color { 198 h1, s1, v1 := c1.Hsv() 199 h2, s2, v2 := c2.Hsv() 200 201 // We know that h are both in [0..360] 202 return Hsv(interp_angle(h1, h2, t), s1+t*(s2-s1), v1+t*(v2-v1)) 203 } 204 205 /// HSL /// 206 /////////// 207 208 // Hsl returns the Hue [0..360], Saturation [0..1], and Luminance (lightness) [0..1] of the color. 209 func (col Color) Hsl() (h, s, l float64) { 210 min := math.Min(math.Min(col.R, col.G), col.B) 211 max := math.Max(math.Max(col.R, col.G), col.B) 212 213 l = (max + min) / 2 214 215 if min == max { 216 s = 0 217 h = 0 218 } else { 219 if l < 0.5 { 220 s = (max - min) / (max + min) 221 } else { 222 s = (max - min) / (2.0 - max - min) 223 } 224 225 if max == col.R { 226 h = (col.G - col.B) / (max - min) 227 } else if max == col.G { 228 h = 2.0 + (col.B-col.R)/(max-min) 229 } else { 230 h = 4.0 + (col.R-col.G)/(max-min) 231 } 232 233 h *= 60 234 235 if h < 0 { 236 h += 360 237 } 238 } 239 240 return 241 } 242 243 // Hsl creates a new Color given a Hue in [0..360], a Saturation [0..1], and a Luminance (lightness) in [0..1] 244 func Hsl(h, s, l float64) Color { 245 if s == 0 { 246 return Color{l, l, l} 247 } 248 249 var r, g, b float64 250 var t1 float64 251 var t2 float64 252 var tr float64 253 var tg float64 254 var tb float64 255 256 if l < 0.5 { 257 t1 = l * (1.0 + s) 258 } else { 259 t1 = l + s - l*s 260 } 261 262 t2 = 2*l - t1 263 h /= 360 264 tr = h + 1.0/3.0 265 tg = h 266 tb = h - 1.0/3.0 267 268 if tr < 0 { 269 tr++ 270 } 271 if tr > 1 { 272 tr-- 273 } 274 if tg < 0 { 275 tg++ 276 } 277 if tg > 1 { 278 tg-- 279 } 280 if tb < 0 { 281 tb++ 282 } 283 if tb > 1 { 284 tb-- 285 } 286 287 // Red 288 if 6*tr < 1 { 289 r = t2 + (t1-t2)*6*tr 290 } else if 2*tr < 1 { 291 r = t1 292 } else if 3*tr < 2 { 293 r = t2 + (t1-t2)*(2.0/3.0-tr)*6 294 } else { 295 r = t2 296 } 297 298 // Green 299 if 6*tg < 1 { 300 g = t2 + (t1-t2)*6*tg 301 } else if 2*tg < 1 { 302 g = t1 303 } else if 3*tg < 2 { 304 g = t2 + (t1-t2)*(2.0/3.0-tg)*6 305 } else { 306 g = t2 307 } 308 309 // Blue 310 if 6*tb < 1 { 311 b = t2 + (t1-t2)*6*tb 312 } else if 2*tb < 1 { 313 b = t1 314 } else if 3*tb < 2 { 315 b = t2 + (t1-t2)*(2.0/3.0-tb)*6 316 } else { 317 b = t2 318 } 319 320 return Color{r, g, b} 321 } 322 323 /// Hex /// 324 /////////// 325 326 // Hex returns the hex "html" representation of the color, as in #ff0080. 327 func (col Color) Hex() string { 328 // Add 0.5 for rounding 329 return fmt.Sprintf("#%02x%02x%02x", uint8(col.R*255.0+0.5), uint8(col.G*255.0+0.5), uint8(col.B*255.0+0.5)) 330 } 331 332 // Hex parses a "html" hex color-string, either in the 3 "#f0c" or 6 "#ff1034" digits form. 333 func Hex(scol string) (Color, error) { 334 format := "#%02x%02x%02x" 335 factor := 1.0 / 255.0 336 if len(scol) == 4 { 337 format = "#%1x%1x%1x" 338 factor = 1.0 / 15.0 339 } 340 341 var r, g, b uint8 342 n, err := fmt.Sscanf(scol, format, &r, &g, &b) 343 if err != nil { 344 return Color{}, err 345 } 346 if n != 3 { 347 return Color{}, fmt.Errorf("color: %v is not a hex-color", scol) 348 } 349 350 return Color{float64(r) * factor, float64(g) * factor, float64(b) * factor}, nil 351 } 352 353 /// Linear /// 354 ////////////// 355 // http://www.sjbrown.co.uk/2004/05/14/gamma-correct-rendering/ 356 // http://www.brucelindbloom.com/Eqn_RGB_to_XYZ.html 357 358 func linearize(v float64) float64 { 359 if v <= 0.04045 { 360 return v / 12.92 361 } 362 return math.Pow((v+0.055)/1.055, 2.4) 363 } 364 365 // LinearRgb converts the color into the linear RGB space (see http://www.sjbrown.co.uk/2004/05/14/gamma-correct-rendering/). 366 func (col Color) LinearRgb() (r, g, b float64) { 367 r = linearize(col.R) 368 g = linearize(col.G) 369 b = linearize(col.B) 370 return 371 } 372 373 // A much faster and still quite precise linearization using a 6th-order Taylor approximation. 374 // See the accompanying Jupyter notebook for derivation of the constants. 375 func linearize_fast(v float64) float64 { 376 v1 := v - 0.5 377 v2 := v1 * v1 378 v3 := v2 * v1 379 v4 := v2 * v2 380 //v5 := v3*v2 381 return -0.248750514614486 + 0.925583310193438*v + 1.16740237321695*v2 + 0.280457026598666*v3 - 0.0757991963780179*v4 //+ 0.0437040411548932*v5 382 } 383 384 // FastLinearRgb is much faster than and almost as accurate as LinearRgb. 385 // BUT it is important to NOTE that they only produce good results for valid colors r,g,b in [0,1]. 386 func (col Color) FastLinearRgb() (r, g, b float64) { 387 r = linearize_fast(col.R) 388 g = linearize_fast(col.G) 389 b = linearize_fast(col.B) 390 return 391 } 392 393 func delinearize(v float64) float64 { 394 if v <= 0.0031308 { 395 return 12.92 * v 396 } 397 return 1.055*math.Pow(v, 1.0/2.4) - 0.055 398 } 399 400 // LinearRgb creates an sRGB color out of the given linear RGB color (see http://www.sjbrown.co.uk/2004/05/14/gamma-correct-rendering/). 401 func LinearRgb(r, g, b float64) Color { 402 return Color{delinearize(r), delinearize(g), delinearize(b)} 403 } 404 405 func delinearize_fast(v float64) float64 { 406 // This function (fractional root) is much harder to linearize, so we need to split. 407 if v > 0.2 { 408 v1 := v - 0.6 409 v2 := v1 * v1 410 v3 := v2 * v1 411 v4 := v2 * v2 412 v5 := v3 * v2 413 return 0.442430344268235 + 0.592178981271708*v - 0.287864782562636*v2 + 0.253214392068985*v3 - 0.272557158129811*v4 + 0.325554383321718*v5 414 } else if v > 0.03 { 415 v1 := v - 0.115 416 v2 := v1 * v1 417 v3 := v2 * v1 418 v4 := v2 * v2 419 v5 := v3 * v2 420 return 0.194915592891669 + 1.55227076330229*v - 3.93691860257828*v2 + 18.0679839248761*v3 - 101.468750302746*v4 + 632.341487393927*v5 421 } else { 422 v1 := v - 0.015 423 v2 := v1 * v1 424 v3 := v2 * v1 425 v4 := v2 * v2 426 v5 := v3 * v2 427 // You can clearly see from the involved constants that the low-end is highly nonlinear. 428 return 0.0519565234928877 + 5.09316778537561*v - 99.0338180489702*v2 + 3484.52322764895*v3 - 150028.083412663*v4 + 7168008.42971613*v5 429 } 430 } 431 432 // FastLinearRgb is much faster than and almost as accurate as LinearRgb. 433 // BUT it is important to NOTE that they only produce good results for valid inputs r,g,b in [0,1]. 434 func FastLinearRgb(r, g, b float64) Color { 435 return Color{delinearize_fast(r), delinearize_fast(g), delinearize_fast(b)} 436 } 437 438 // XyzToLinearRgb converts from CIE XYZ-space to Linear RGB space. 439 func XyzToLinearRgb(x, y, z float64) (r, g, b float64) { 440 r = 3.2409699419045214*x - 1.5373831775700935*y - 0.49861076029300328*z 441 g = -0.96924363628087983*x + 1.8759675015077207*y + 0.041555057407175613*z 442 b = 0.055630079696993609*x - 0.20397695888897657*y + 1.0569715142428786*z 443 return 444 } 445 446 func LinearRgbToXyz(r, g, b float64) (x, y, z float64) { 447 x = 0.41239079926595948*r + 0.35758433938387796*g + 0.18048078840183429*b 448 y = 0.21263900587151036*r + 0.71516867876775593*g + 0.072192315360733715*b 449 z = 0.019330818715591851*r + 0.11919477979462599*g + 0.95053215224966058*b 450 return 451 } 452 453 /// XYZ /// 454 /////////// 455 // http://www.sjbrown.co.uk/2004/05/14/gamma-correct-rendering/ 456 457 func (col Color) Xyz() (x, y, z float64) { 458 return LinearRgbToXyz(col.LinearRgb()) 459 } 460 461 func Xyz(x, y, z float64) Color { 462 return LinearRgb(XyzToLinearRgb(x, y, z)) 463 } 464 465 /// xyY /// 466 /////////// 467 // http://www.brucelindbloom.com/Eqn_XYZ_to_xyY.html 468 469 // Well, the name is bad, since it's xyY but Golang needs me to start with a 470 // capital letter to make the method public. 471 func XyzToXyy(X, Y, Z float64) (x, y, Yout float64) { 472 return XyzToXyyWhiteRef(X, Y, Z, D65) 473 } 474 475 func XyzToXyyWhiteRef(X, Y, Z float64, wref [3]float64) (x, y, Yout float64) { 476 Yout = Y 477 N := X + Y + Z 478 if math.Abs(N) < 1e-14 { 479 // When we have black, Bruce Lindbloom recommends to use 480 // the reference white's chromacity for x and y. 481 x = wref[0] / (wref[0] + wref[1] + wref[2]) 482 y = wref[1] / (wref[0] + wref[1] + wref[2]) 483 } else { 484 x = X / N 485 y = Y / N 486 } 487 return 488 } 489 490 func XyyToXyz(x, y, Y float64) (X, Yout, Z float64) { 491 Yout = Y 492 493 if -1e-14 < y && y < 1e-14 { 494 X = 0.0 495 Z = 0.0 496 } else { 497 X = Y / y * x 498 Z = Y / y * (1.0 - x - y) 499 } 500 501 return 502 } 503 504 // Converts the given color to CIE xyY space using D65 as reference white. 505 // (Note that the reference white is only used for black input.) 506 // x, y and Y are in [0..1] 507 func (col Color) Xyy() (x, y, Y float64) { 508 return XyzToXyy(col.Xyz()) 509 } 510 511 // Converts the given color to CIE xyY space, taking into account 512 // a given reference white. (i.e. the monitor's white) 513 // (Note that the reference white is only used for black input.) 514 // x, y and Y are in [0..1] 515 func (col Color) XyyWhiteRef(wref [3]float64) (x, y, Y float64) { 516 X, Y2, Z := col.Xyz() 517 return XyzToXyyWhiteRef(X, Y2, Z, wref) 518 } 519 520 // Generates a color by using data given in CIE xyY space. 521 // x, y and Y are in [0..1] 522 func Xyy(x, y, Y float64) Color { 523 return Xyz(XyyToXyz(x, y, Y)) 524 } 525 526 /// L*a*b* /// 527 ////////////// 528 // http://en.wikipedia.org/wiki/Lab_color_space#CIELAB-CIEXYZ_conversions 529 // For L*a*b*, we need to L*a*b*<->XYZ->RGB and the first one is device dependent. 530 531 func lab_f(t float64) float64 { 532 if t > 6.0/29.0*6.0/29.0*6.0/29.0 { 533 return math.Cbrt(t) 534 } 535 return t/3.0*29.0/6.0*29.0/6.0 + 4.0/29.0 536 } 537 538 func XyzToLab(x, y, z float64) (l, a, b float64) { 539 // Use D65 white as reference point by default. 540 // http://www.fredmiranda.com/forum/topic/1035332 541 // http://en.wikipedia.org/wiki/Standard_illuminant 542 return XyzToLabWhiteRef(x, y, z, D65) 543 } 544 545 func XyzToLabWhiteRef(x, y, z float64, wref [3]float64) (l, a, b float64) { 546 fy := lab_f(y / wref[1]) 547 l = 1.16*fy - 0.16 548 a = 5.0 * (lab_f(x/wref[0]) - fy) 549 b = 2.0 * (fy - lab_f(z/wref[2])) 550 return 551 } 552 553 func lab_finv(t float64) float64 { 554 if t > 6.0/29.0 { 555 return t * t * t 556 } 557 return 3.0 * 6.0 / 29.0 * 6.0 / 29.0 * (t - 4.0/29.0) 558 } 559 560 func LabToXyz(l, a, b float64) (x, y, z float64) { 561 // D65 white (see above). 562 return LabToXyzWhiteRef(l, a, b, D65) 563 } 564 565 func LabToXyzWhiteRef(l, a, b float64, wref [3]float64) (x, y, z float64) { 566 l2 := (l + 0.16) / 1.16 567 x = wref[0] * lab_finv(l2+a/5.0) 568 y = wref[1] * lab_finv(l2) 569 z = wref[2] * lab_finv(l2-b/2.0) 570 return 571 } 572 573 // Converts the given color to CIE L*a*b* space using D65 as reference white. 574 func (col Color) Lab() (l, a, b float64) { 575 return XyzToLab(col.Xyz()) 576 } 577 578 // Converts the given color to CIE L*a*b* space, taking into account 579 // a given reference white. (i.e. the monitor's white) 580 func (col Color) LabWhiteRef(wref [3]float64) (l, a, b float64) { 581 x, y, z := col.Xyz() 582 return XyzToLabWhiteRef(x, y, z, wref) 583 } 584 585 // Generates a color by using data given in CIE L*a*b* space using D65 as reference white. 586 // WARNING: many combinations of `l`, `a`, and `b` values do not have corresponding 587 // valid RGB values, check the FAQ in the README if you're unsure. 588 func Lab(l, a, b float64) Color { 589 return Xyz(LabToXyz(l, a, b)) 590 } 591 592 // Generates a color by using data given in CIE L*a*b* space, taking 593 // into account a given reference white. (i.e. the monitor's white) 594 func LabWhiteRef(l, a, b float64, wref [3]float64) Color { 595 return Xyz(LabToXyzWhiteRef(l, a, b, wref)) 596 } 597 598 // DistanceLab is a good measure of visual similarity between two colors! 599 // A result of 0 would mean identical colors, while a result of 1 or higher 600 // means the colors differ a lot. 601 func (c1 Color) DistanceLab(c2 Color) float64 { 602 l1, a1, b1 := c1.Lab() 603 l2, a2, b2 := c2.Lab() 604 return math.Sqrt(sq(l1-l2) + sq(a1-a2) + sq(b1-b2)) 605 } 606 607 // DistanceCIE76 is the same as DistanceLab. 608 func (c1 Color) DistanceCIE76(c2 Color) float64 { 609 return c1.DistanceLab(c2) 610 } 611 612 // Uses the CIE94 formula to calculate color distance. More accurate than 613 // DistanceLab, but also more work. 614 func (cl Color) DistanceCIE94(cr Color) float64 { 615 l1, a1, b1 := cl.Lab() 616 l2, a2, b2 := cr.Lab() 617 618 // NOTE: Since all those formulas expect L,a,b values 100x larger than we 619 // have them in this library, we either need to adjust all constants 620 // in the formula, or convert the ranges of L,a,b before, and then 621 // scale the distances down again. The latter is less error-prone. 622 l1, a1, b1 = l1*100.0, a1*100.0, b1*100.0 623 l2, a2, b2 = l2*100.0, a2*100.0, b2*100.0 624 625 kl := 1.0 // 2.0 for textiles 626 kc := 1.0 627 kh := 1.0 628 k1 := 0.045 // 0.048 for textiles 629 k2 := 0.015 // 0.014 for textiles. 630 631 deltaL := l1 - l2 632 c1 := math.Sqrt(sq(a1) + sq(b1)) 633 c2 := math.Sqrt(sq(a2) + sq(b2)) 634 deltaCab := c1 - c2 635 636 // Not taking Sqrt here for stability, and it's unnecessary. 637 deltaHab2 := sq(a1-a2) + sq(b1-b2) - sq(deltaCab) 638 sl := 1.0 639 sc := 1.0 + k1*c1 640 sh := 1.0 + k2*c1 641 642 vL2 := sq(deltaL / (kl * sl)) 643 vC2 := sq(deltaCab / (kc * sc)) 644 vH2 := deltaHab2 / sq(kh*sh) 645 646 return math.Sqrt(vL2+vC2+vH2) * 0.01 // See above. 647 } 648 649 // DistanceCIEDE2000 uses the Delta E 2000 formula to calculate color 650 // distance. It is more expensive but more accurate than both DistanceLab 651 // and DistanceCIE94. 652 func (cl Color) DistanceCIEDE2000(cr Color) float64 { 653 return cl.DistanceCIEDE2000klch(cr, 1.0, 1.0, 1.0) 654 } 655 656 // DistanceCIEDE2000klch uses the Delta E 2000 formula with custom values 657 // for the weighting factors kL, kC, and kH. 658 func (cl Color) DistanceCIEDE2000klch(cr Color, kl, kc, kh float64) float64 { 659 l1, a1, b1 := cl.Lab() 660 l2, a2, b2 := cr.Lab() 661 662 // As with CIE94, we scale up the ranges of L,a,b beforehand and scale 663 // them down again afterwards. 664 l1, a1, b1 = l1*100.0, a1*100.0, b1*100.0 665 l2, a2, b2 = l2*100.0, a2*100.0, b2*100.0 666 667 cab1 := math.Sqrt(sq(a1) + sq(b1)) 668 cab2 := math.Sqrt(sq(a2) + sq(b2)) 669 cabmean := (cab1 + cab2) / 2 670 671 g := 0.5 * (1 - math.Sqrt(math.Pow(cabmean, 7)/(math.Pow(cabmean, 7)+math.Pow(25, 7)))) 672 ap1 := (1 + g) * a1 673 ap2 := (1 + g) * a2 674 cp1 := math.Sqrt(sq(ap1) + sq(b1)) 675 cp2 := math.Sqrt(sq(ap2) + sq(b2)) 676 677 hp1 := 0.0 678 if b1 != ap1 || ap1 != 0 { 679 hp1 = math.Atan2(b1, ap1) 680 if hp1 < 0 { 681 hp1 += math.Pi * 2 682 } 683 hp1 *= 180 / math.Pi 684 } 685 hp2 := 0.0 686 if b2 != ap2 || ap2 != 0 { 687 hp2 = math.Atan2(b2, ap2) 688 if hp2 < 0 { 689 hp2 += math.Pi * 2 690 } 691 hp2 *= 180 / math.Pi 692 } 693 694 deltaLp := l2 - l1 695 deltaCp := cp2 - cp1 696 dhp := 0.0 697 cpProduct := cp1 * cp2 698 if cpProduct != 0 { 699 dhp = hp2 - hp1 700 if dhp > 180 { 701 dhp -= 360 702 } else if dhp < -180 { 703 dhp += 360 704 } 705 } 706 deltaHp := 2 * math.Sqrt(cpProduct) * math.Sin(dhp/2*math.Pi/180) 707 708 lpmean := (l1 + l2) / 2 709 cpmean := (cp1 + cp2) / 2 710 hpmean := hp1 + hp2 711 if cpProduct != 0 { 712 hpmean /= 2 713 if math.Abs(hp1-hp2) > 180 { 714 if hp1+hp2 < 360 { 715 hpmean += 180 716 } else { 717 hpmean -= 180 718 } 719 } 720 } 721 722 t := 1 - 0.17*math.Cos((hpmean-30)*math.Pi/180) + 0.24*math.Cos(2*hpmean*math.Pi/180) + 0.32*math.Cos((3*hpmean+6)*math.Pi/180) - 0.2*math.Cos((4*hpmean-63)*math.Pi/180) 723 deltaTheta := 30 * math.Exp(-sq((hpmean-275)/25)) 724 rc := 2 * math.Sqrt(math.Pow(cpmean, 7)/(math.Pow(cpmean, 7)+math.Pow(25, 7))) 725 sl := 1 + (0.015*sq(lpmean-50))/math.Sqrt(20+sq(lpmean-50)) 726 sc := 1 + 0.045*cpmean 727 sh := 1 + 0.015*cpmean*t 728 rt := -math.Sin(2*deltaTheta*math.Pi/180) * rc 729 730 return math.Sqrt(sq(deltaLp/(kl*sl))+sq(deltaCp/(kc*sc))+sq(deltaHp/(kh*sh))+rt*(deltaCp/(kc*sc))*(deltaHp/(kh*sh))) * 0.01 731 } 732 733 // BlendLab blends two colors in the L*a*b* color-space, which should result in a smoother blend. 734 // t == 0 results in c1, t == 1 results in c2 735 func (c1 Color) BlendLab(c2 Color, t float64) Color { 736 l1, a1, b1 := c1.Lab() 737 l2, a2, b2 := c2.Lab() 738 return Lab(l1+t*(l2-l1), 739 a1+t*(a2-a1), 740 b1+t*(b2-b1)) 741 } 742 743 /// L*u*v* /// 744 ////////////// 745 // http://en.wikipedia.org/wiki/CIELUV#XYZ_.E2.86.92_CIELUV_and_CIELUV_.E2.86.92_XYZ_conversions 746 // For L*u*v*, we need to L*u*v*<->XYZ<->RGB and the first one is device dependent. 747 748 func XyzToLuv(x, y, z float64) (l, a, b float64) { 749 // Use D65 white as reference point by default. 750 // http://www.fredmiranda.com/forum/topic/1035332 751 // http://en.wikipedia.org/wiki/Standard_illuminant 752 return XyzToLuvWhiteRef(x, y, z, D65) 753 } 754 755 func XyzToLuvWhiteRef(x, y, z float64, wref [3]float64) (l, u, v float64) { 756 if y/wref[1] <= 6.0/29.0*6.0/29.0*6.0/29.0 { 757 l = y / wref[1] * (29.0 / 3.0 * 29.0 / 3.0 * 29.0 / 3.0) / 100.0 758 } else { 759 l = 1.16*math.Cbrt(y/wref[1]) - 0.16 760 } 761 ubis, vbis := xyz_to_uv(x, y, z) 762 un, vn := xyz_to_uv(wref[0], wref[1], wref[2]) 763 u = 13.0 * l * (ubis - un) 764 v = 13.0 * l * (vbis - vn) 765 return 766 } 767 768 // For this part, we do as R's graphics.hcl does, not as wikipedia does. 769 // Or is it the same? 770 func xyz_to_uv(x, y, z float64) (u, v float64) { 771 denom := x + 15.0*y + 3.0*z 772 if denom == 0.0 { 773 u, v = 0.0, 0.0 774 } else { 775 u = 4.0 * x / denom 776 v = 9.0 * y / denom 777 } 778 return 779 } 780 781 func LuvToXyz(l, u, v float64) (x, y, z float64) { 782 // D65 white (see above). 783 return LuvToXyzWhiteRef(l, u, v, D65) 784 } 785 786 func LuvToXyzWhiteRef(l, u, v float64, wref [3]float64) (x, y, z float64) { 787 //y = wref[1] * lab_finv((l + 0.16) / 1.16) 788 if l <= 0.08 { 789 y = wref[1] * l * 100.0 * 3.0 / 29.0 * 3.0 / 29.0 * 3.0 / 29.0 790 } else { 791 y = wref[1] * cub((l+0.16)/1.16) 792 } 793 un, vn := xyz_to_uv(wref[0], wref[1], wref[2]) 794 if l != 0.0 { 795 ubis := u/(13.0*l) + un 796 vbis := v/(13.0*l) + vn 797 x = y * 9.0 * ubis / (4.0 * vbis) 798 z = y * (12.0 - 3.0*ubis - 20.0*vbis) / (4.0 * vbis) 799 } else { 800 x, y = 0.0, 0.0 801 } 802 return 803 } 804 805 // Converts the given color to CIE L*u*v* space using D65 as reference white. 806 // L* is in [0..1] and both u* and v* are in about [-1..1] 807 func (col Color) Luv() (l, u, v float64) { 808 return XyzToLuv(col.Xyz()) 809 } 810 811 // Converts the given color to CIE L*u*v* space, taking into account 812 // a given reference white. (i.e. the monitor's white) 813 // L* is in [0..1] and both u* and v* are in about [-1..1] 814 func (col Color) LuvWhiteRef(wref [3]float64) (l, u, v float64) { 815 x, y, z := col.Xyz() 816 return XyzToLuvWhiteRef(x, y, z, wref) 817 } 818 819 // Generates a color by using data given in CIE L*u*v* space using D65 as reference white. 820 // L* is in [0..1] and both u* and v* are in about [-1..1] 821 // WARNING: many combinations of `l`, `u`, and `v` values do not have corresponding 822 // valid RGB values, check the FAQ in the README if you're unsure. 823 func Luv(l, u, v float64) Color { 824 return Xyz(LuvToXyz(l, u, v)) 825 } 826 827 // Generates a color by using data given in CIE L*u*v* space, taking 828 // into account a given reference white. (i.e. the monitor's white) 829 // L* is in [0..1] and both u* and v* are in about [-1..1] 830 func LuvWhiteRef(l, u, v float64, wref [3]float64) Color { 831 return Xyz(LuvToXyzWhiteRef(l, u, v, wref)) 832 } 833 834 // DistanceLuv is a good measure of visual similarity between two colors! 835 // A result of 0 would mean identical colors, while a result of 1 or higher 836 // means the colors differ a lot. 837 func (c1 Color) DistanceLuv(c2 Color) float64 { 838 l1, u1, v1 := c1.Luv() 839 l2, u2, v2 := c2.Luv() 840 return math.Sqrt(sq(l1-l2) + sq(u1-u2) + sq(v1-v2)) 841 } 842 843 // BlendLuv blends two colors in the CIE-L*u*v* color-space, which should result in a smoother blend. 844 // t == 0 results in c1, t == 1 results in c2 845 func (c1 Color) BlendLuv(c2 Color, t float64) Color { 846 l1, u1, v1 := c1.Luv() 847 l2, u2, v2 := c2.Luv() 848 return Luv(l1+t*(l2-l1), 849 u1+t*(u2-u1), 850 v1+t*(v2-v1)) 851 } 852 853 /// HCL /// 854 /////////// 855 // HCL is nothing else than L*a*b* in cylindrical coordinates! 856 // (this was wrong on English wikipedia, I fixed it, let's hope the fix stays.) 857 // But it is widely popular since it is a "correct HSV" 858 // http://www.hunterlab.com/appnotes/an09_96a.pdf 859 860 // Converts the given color to HCL space using D65 as reference white. 861 // H values are in [0..360], C and L values are in [0..1] although C can overshoot 1.0 862 func (col Color) Hcl() (h, c, l float64) { 863 return col.HclWhiteRef(D65) 864 } 865 866 func LabToHcl(L, a, b float64) (h, c, l float64) { 867 // Oops, floating point workaround necessary if a ~= b and both are very small (i.e. almost zero). 868 if math.Abs(b-a) > 1e-4 && math.Abs(a) > 1e-4 { 869 h = math.Mod(57.29577951308232087721*math.Atan2(b, a)+360.0, 360.0) // Rad2Deg 870 } else { 871 h = 0.0 872 } 873 c = math.Sqrt(sq(a) + sq(b)) 874 l = L 875 return 876 } 877 878 // Converts the given color to HCL space, taking into account 879 // a given reference white. (i.e. the monitor's white) 880 // H values are in [0..360], C and L values are in [0..1] 881 func (col Color) HclWhiteRef(wref [3]float64) (h, c, l float64) { 882 L, a, b := col.LabWhiteRef(wref) 883 return LabToHcl(L, a, b) 884 } 885 886 // Generates a color by using data given in HCL space using D65 as reference white. 887 // H values are in [0..360], C and L values are in [0..1] 888 // WARNING: many combinations of `h`, `c`, and `l` values do not have corresponding 889 // valid RGB values, check the FAQ in the README if you're unsure. 890 func Hcl(h, c, l float64) Color { 891 return HclWhiteRef(h, c, l, D65) 892 } 893 894 func HclToLab(h, c, l float64) (L, a, b float64) { 895 H := 0.01745329251994329576 * h // Deg2Rad 896 a = c * math.Cos(H) 897 b = c * math.Sin(H) 898 L = l 899 return 900 } 901 902 // Generates a color by using data given in HCL space, taking 903 // into account a given reference white. (i.e. the monitor's white) 904 // H values are in [0..360], C and L values are in [0..1] 905 func HclWhiteRef(h, c, l float64, wref [3]float64) Color { 906 L, a, b := HclToLab(h, c, l) 907 return LabWhiteRef(L, a, b, wref) 908 } 909 910 // BlendHcl blends two colors in the CIE-L*C*h° color-space, which should result in a smoother blend. 911 // t == 0 results in c1, t == 1 results in c2 912 func (col1 Color) BlendHcl(col2 Color, t float64) Color { 913 h1, c1, l1 := col1.Hcl() 914 h2, c2, l2 := col2.Hcl() 915 916 // We know that h are both in [0..360] 917 return Hcl(interp_angle(h1, h2, t), c1+t*(c2-c1), l1+t*(l2-l1)).Clamped() 918 } 919 920 // LuvLch 921 922 // Converts the given color to LuvLCh space using D65 as reference white. 923 // h values are in [0..360], C and L values are in [0..1] although C can overshoot 1.0 924 func (col Color) LuvLCh() (l, c, h float64) { 925 return col.LuvLChWhiteRef(D65) 926 } 927 928 func LuvToLuvLCh(L, u, v float64) (l, c, h float64) { 929 // Oops, floating point workaround necessary if u ~= v and both are very small (i.e. almost zero). 930 if math.Abs(v-u) > 1e-4 && math.Abs(u) > 1e-4 { 931 h = math.Mod(57.29577951308232087721*math.Atan2(v, u)+360.0, 360.0) // Rad2Deg 932 } else { 933 h = 0.0 934 } 935 l = L 936 c = math.Sqrt(sq(u) + sq(v)) 937 return 938 } 939 940 // Converts the given color to LuvLCh space, taking into account 941 // a given reference white. (i.e. the monitor's white) 942 // h values are in [0..360], c and l values are in [0..1] 943 func (col Color) LuvLChWhiteRef(wref [3]float64) (l, c, h float64) { 944 return LuvToLuvLCh(col.LuvWhiteRef(wref)) 945 } 946 947 // Generates a color by using data given in LuvLCh space using D65 as reference white. 948 // h values are in [0..360], C and L values are in [0..1] 949 // WARNING: many combinations of `l`, `c`, and `h` values do not have corresponding 950 // valid RGB values, check the FAQ in the README if you're unsure. 951 func LuvLCh(l, c, h float64) Color { 952 return LuvLChWhiteRef(l, c, h, D65) 953 } 954 955 func LuvLChToLuv(l, c, h float64) (L, u, v float64) { 956 H := 0.01745329251994329576 * h // Deg2Rad 957 u = c * math.Cos(H) 958 v = c * math.Sin(H) 959 L = l 960 return 961 } 962 963 // Generates a color by using data given in LuvLCh space, taking 964 // into account a given reference white. (i.e. the monitor's white) 965 // h values are in [0..360], C and L values are in [0..1] 966 func LuvLChWhiteRef(l, c, h float64, wref [3]float64) Color { 967 L, u, v := LuvLChToLuv(l, c, h) 968 return LuvWhiteRef(L, u, v, wref) 969 } 970 971 // BlendLuvLCh blends two colors in the cylindrical CIELUV color space. 972 // t == 0 results in c1, t == 1 results in c2 973 func (col1 Color) BlendLuvLCh(col2 Color, t float64) Color { 974 l1, c1, h1 := col1.LuvLCh() 975 l2, c2, h2 := col2.LuvLCh() 976 977 // We know that h are both in [0..360] 978 return LuvLCh(l1+t*(l2-l1), c1+t*(c2-c1), interp_angle(h1, h2, t)) 979 }