nt

A sensible note-taking program
git clone git://git.laack.co/nt.git
Log | Files | Refs | README

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 }