...

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

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

     1  package colorful
     2  
     3  import "math"
     4  
     5  // Source: https://github.com/hsluv/hsluv-go
     6  // Under MIT License
     7  // Modified so that Saturation and Luminance are in [0..1] instead of [0..100].
     8  
     9  // HSLuv uses a rounded version of the D65. This has no impact on the final RGB
    10  // values, but to keep high levels of accuracy for internal operations and when
    11  // comparing to the test values, this modified white reference is used internally.
    12  //
    13  // See this GitHub thread for details on these values:
    14  //     https://github.com/hsluv/hsluv/issues/79
    15  var hSLuvD65 = [3]float64{0.95045592705167, 1.0, 1.089057750759878}
    16  
    17  func LuvLChToHSLuv(l, c, h float64) (float64, float64, float64) {
    18  	// [-1..1] but the code expects it to be [-100..100]
    19  	c *= 100.0
    20  	l *= 100.0
    21  
    22  	var s, max float64
    23  	if l > 99.9999999 || l < 0.00000001 {
    24  		s = 0.0
    25  	} else {
    26  		max = maxChromaForLH(l, h)
    27  		s = c / max * 100.0
    28  	}
    29  	return h, clamp01(s / 100.0), clamp01(l / 100.0)
    30  }
    31  
    32  func HSLuvToLuvLCh(h, s, l float64) (float64, float64, float64) {
    33  	l *= 100.0
    34  	s *= 100.0
    35  
    36  	var c, max float64
    37  	if l > 99.9999999 || l < 0.00000001 {
    38  		c = 0.0
    39  	} else {
    40  		max = maxChromaForLH(l, h)
    41  		c = max / 100.0 * s
    42  	}
    43  
    44  	// c is [-100..100], but for LCh it's supposed to be almost [-1..1]
    45  	return clamp01(l / 100.0), c / 100.0, h
    46  }
    47  
    48  func LuvLChToHPLuv(l, c, h float64) (float64, float64, float64) {
    49  	// [-1..1] but the code expects it to be [-100..100]
    50  	c *= 100.0
    51  	l *= 100.0
    52  
    53  	var s, max float64
    54  	if l > 99.9999999 || l < 0.00000001 {
    55  		s = 0.0
    56  	} else {
    57  		max = maxSafeChromaForL(l)
    58  		s = c / max * 100.0
    59  	}
    60  	return h, s / 100.0, l / 100.0
    61  }
    62  
    63  func HPLuvToLuvLCh(h, s, l float64) (float64, float64, float64) {
    64  	// [-1..1] but the code expects it to be [-100..100]
    65  	l *= 100.0
    66  	s *= 100.0
    67  
    68  	var c, max float64
    69  	if l > 99.9999999 || l < 0.00000001 {
    70  		c = 0.0
    71  	} else {
    72  		max = maxSafeChromaForL(l)
    73  		c = max / 100.0 * s
    74  	}
    75  	return l / 100.0, c / 100.0, h
    76  }
    77  
    78  // HSLuv creates a new Color from values in the HSLuv color space.
    79  // Hue in [0..360], a Saturation [0..1], and a Luminance (lightness) in [0..1].
    80  //
    81  // The returned color values are clamped (using .Clamped), so this will never output
    82  // an invalid color.
    83  func HSLuv(h, s, l float64) Color {
    84  	// HSLuv -> LuvLCh -> CIELUV -> CIEXYZ -> Linear RGB -> sRGB
    85  	l, u, v := LuvLChToLuv(HSLuvToLuvLCh(h, s, l))
    86  	return LinearRgb(XyzToLinearRgb(LuvToXyzWhiteRef(l, u, v, hSLuvD65))).Clamped()
    87  }
    88  
    89  // HPLuv creates a new Color from values in the HPLuv color space.
    90  // Hue in [0..360], a Saturation [0..1], and a Luminance (lightness) in [0..1].
    91  //
    92  // The returned color values are clamped (using .Clamped), so this will never output
    93  // an invalid color.
    94  func HPLuv(h, s, l float64) Color {
    95  	// HPLuv -> LuvLCh -> CIELUV -> CIEXYZ -> Linear RGB -> sRGB
    96  	l, u, v := LuvLChToLuv(HPLuvToLuvLCh(h, s, l))
    97  	return LinearRgb(XyzToLinearRgb(LuvToXyzWhiteRef(l, u, v, hSLuvD65))).Clamped()
    98  }
    99  
   100  // HSLuv returns the Hue, Saturation and Luminance of the color in the HSLuv
   101  // color space. Hue in [0..360], a Saturation [0..1], and a Luminance
   102  // (lightness) in [0..1].
   103  func (col Color) HSLuv() (h, s, l float64) {
   104  	// sRGB -> Linear RGB -> CIEXYZ -> CIELUV -> LuvLCh -> HSLuv
   105  	return LuvLChToHSLuv(col.LuvLChWhiteRef(hSLuvD65))
   106  }
   107  
   108  // HPLuv returns the Hue, Saturation and Luminance of the color in the HSLuv
   109  // color space. Hue in [0..360], a Saturation [0..1], and a Luminance
   110  // (lightness) in [0..1].
   111  //
   112  // Note that HPLuv can only represent pastel colors, and so the Saturation
   113  // value could be much larger than 1 for colors it can't represent.
   114  func (col Color) HPLuv() (h, s, l float64) {
   115  	return LuvLChToHPLuv(col.LuvLChWhiteRef(hSLuvD65))
   116  }
   117  
   118  // DistanceHSLuv calculates Euclidan distance in the HSLuv colorspace. No idea
   119  // how useful this is.
   120  //
   121  // The Hue value is divided by 100 before the calculation, so that H, S, and L
   122  // have the same relative ranges.
   123  func (c1 Color) DistanceHSLuv(c2 Color) float64 {
   124  	h1, s1, l1 := c1.HSLuv()
   125  	h2, s2, l2 := c2.HSLuv()
   126  	return math.Sqrt(sq((h1-h2)/100.0) + sq(s1-s2) + sq(l1-l2))
   127  }
   128  
   129  // DistanceHPLuv calculates Euclidean distance in the HPLuv colorspace. No idea
   130  // how useful this is.
   131  //
   132  // The Hue value is divided by 100 before the calculation, so that H, S, and L
   133  // have the same relative ranges.
   134  func (c1 Color) DistanceHPLuv(c2 Color) float64 {
   135  	h1, s1, l1 := c1.HPLuv()
   136  	h2, s2, l2 := c2.HPLuv()
   137  	return math.Sqrt(sq((h1-h2)/100.0) + sq(s1-s2) + sq(l1-l2))
   138  }
   139  
   140  var m = [3][3]float64{
   141  	{3.2409699419045214, -1.5373831775700935, -0.49861076029300328},
   142  	{-0.96924363628087983, 1.8759675015077207, 0.041555057407175613},
   143  	{0.055630079696993609, -0.20397695888897657, 1.0569715142428786},
   144  }
   145  
   146  const kappa = 903.2962962962963
   147  const epsilon = 0.0088564516790356308
   148  
   149  func maxChromaForLH(l, h float64) float64 {
   150  	hRad := h / 360.0 * math.Pi * 2.0
   151  	minLength := math.MaxFloat64
   152  	for _, line := range getBounds(l) {
   153  		length := lengthOfRayUntilIntersect(hRad, line[0], line[1])
   154  		if length > 0.0 && length < minLength {
   155  			minLength = length
   156  		}
   157  	}
   158  	return minLength
   159  }
   160  
   161  func getBounds(l float64) [6][2]float64 {
   162  	var sub2 float64
   163  	var ret [6][2]float64
   164  	sub1 := math.Pow(l+16.0, 3.0) / 1560896.0
   165  	if sub1 > epsilon {
   166  		sub2 = sub1
   167  	} else {
   168  		sub2 = l / kappa
   169  	}
   170  	for i := range m {
   171  		for k := 0; k < 2; k++ {
   172  			top1 := (284517.0*m[i][0] - 94839.0*m[i][2]) * sub2
   173  			top2 := (838422.0*m[i][2]+769860.0*m[i][1]+731718.0*m[i][0])*l*sub2 - 769860.0*float64(k)*l
   174  			bottom := (632260.0*m[i][2]-126452.0*m[i][1])*sub2 + 126452.0*float64(k)
   175  			ret[i*2+k][0] = top1 / bottom
   176  			ret[i*2+k][1] = top2 / bottom
   177  		}
   178  	}
   179  	return ret
   180  }
   181  
   182  func lengthOfRayUntilIntersect(theta, x, y float64) (length float64) {
   183  	length = y / (math.Sin(theta) - x*math.Cos(theta))
   184  	return
   185  }
   186  
   187  func maxSafeChromaForL(l float64) float64 {
   188  	minLength := math.MaxFloat64
   189  	for _, line := range getBounds(l) {
   190  		m1 := line[0]
   191  		b1 := line[1]
   192  		x := intersectLineLine(m1, b1, -1.0/m1, 0.0)
   193  		dist := distanceFromPole(x, b1+x*m1)
   194  		if dist < minLength {
   195  			minLength = dist
   196  		}
   197  	}
   198  	return minLength
   199  }
   200  
   201  func intersectLineLine(x1, y1, x2, y2 float64) float64 {
   202  	return (y1 - y2) / (x2 - x1)
   203  }
   204  
   205  func distanceFromPole(x, y float64) float64 {
   206  	return math.Sqrt(math.Pow(x, 2.0) + math.Pow(y, 2.0))
   207  }
   208  

View as plain text