...

Source file src/github.com/lucasb-eyer/go-colorful/colors.go

Documentation: github.com/lucasb-eyer/go-colorful

     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  }
   980  

View as plain text