2121WHITE_POINT_SC = tuple (util .xyz_to_xyY (XYZ_W )[:- 1 ]) # type: VectorLike
2222# Rows: hue 0 - 360 at steps of 10
2323# Columns: lightness 15 - 90 at steps of 5
24- LCH_L = [* range (15 , 91 , 5 )]
25- LCH_H = [* range (0 , 361 , 10 )]
26- GAMUT = [
24+ LIGHTNESS = [* range (15 , 91 , 5 )]
25+ HUE = [* range (0 , 361 , 10 )]
26+ LUT = [
2727 [10 , 30 , 43 , 56 , 68 , 77 , 79 , 77 , 72 , 65 , 57 , 50 , 40 , 30 , 19 , 8 ],
2828 [15 , 30 , 45 , 56 , 64 , 70 , 73 , 73 , 71 , 65 , 57 , 48 , 39 , 30 , 18 , 7 ],
2929 [14 , 34 , 49 , 61 , 69 , 74 , 76 , 76 , 74 , 68 , 61 , 51 , 40 , 30 , 19 , 9 ],
@@ -90,19 +90,19 @@ def closest_lightness(l: float) -> tuple[int, float]:
9090 """Calculate the two closest lightness values and return the first index and interpolation factor."""
9191
9292 # Handle too low lightness inside tolerance
93- if l <= LCH_L [0 ]:
93+ if l <= LIGHTNESS [0 ]:
9494 li = 0
9595 lf = 0.0
9696
9797 # Handle too high lightness inside tolerance
98- elif l >= LCH_L [- 1 ]:
99- li = len (LCH_L ) - 2
98+ elif l >= LIGHTNESS [- 1 ]:
99+ li = len (LIGHTNESS ) - 2
100100 lf = 1.0
101101
102- # Handle lightness with gamut
102+ # Handle lightness within gamut
103103 else :
104- li = bisect .bisect (LCH_L , l ) - 1
105- l1 , l2 = LCH_L [li :li + 2 ]
104+ li = bisect .bisect (LIGHTNESS , l ) - 1
105+ l1 , l2 = LIGHTNESS [li :li + 2 ]
106106 lf = 1 - (l2 - l ) / (l2 - l1 )
107107
108108 return li , lf
@@ -111,9 +111,21 @@ def closest_lightness(l: float) -> tuple[int, float]:
111111def closest_hue (h : float ) -> tuple [int , float ]:
112112 """Calculate the two closest hues and return the first index and interpolation factor."""
113113
114- hi = bisect .bisect (LCH_H , h ) - 1
115- h1 , h2 = LCH_H [hi :hi + 2 ]
116- hf = 1 - (h2 - h ) / (h2 - h1 )
114+ # Handle hue at the start
115+ if h == HUE [0 ]: # pragma: no cover
116+ hi = 0
117+ hf = 0.0
118+
119+ # Handle hue at the end
120+ elif h == HUE [- 1 ]: # pragma: no cover
121+ hi = len (HUE ) - 2
122+ hf = 1.0
123+
124+ # Handle all other hues
125+ else :
126+ hi = bisect .bisect (HUE , h ) - 1
127+ h1 , h2 = HUE [hi :hi + 2 ]
128+ hf = 1 - (h2 - h ) / (h2 - h1 )
117129
118130 return hi , hf
119131
@@ -128,10 +140,10 @@ def get_chroma_limit(l: float, h: float) -> float:
128140 hi , hf = closest_hue (h )
129141
130142 # Interpolate the chroma limit by interpolating chroma values for the closest lightness values and hues.
131- if hi == len (LCH_H ) - 1 :
132- row1 , row2 = GAMUT [- 1 ], GAMUT [0 ]
143+ if hi == len (HUE ) - 1 :
144+ row1 , row2 = LUT [- 1 ], LUT [0 ]
133145 else :
134- row1 , row2 = GAMUT [hi :hi + 2 ]
146+ row1 , row2 = LUT [hi :hi + 2 ]
135147 return alg .lerp (alg .lerp (row1 [li ], row1 [li + 1 ], lf ), alg .lerp (row2 [li ], row2 [li + 1 ], lf ), hf )
136148
137149
@@ -142,8 +154,8 @@ def fit_pointer_gamut(color: AnyColor) -> AnyColor:
142154 l , c , h = to_lch_sc (color )
143155
144156 # Clamp lightness
145- new_l = max (LCH_L [0 ], l )
146- new_l = min (LCH_L [- 1 ], new_l )
157+ new_l = max (LIGHTNESS [0 ], l )
158+ new_l = min (LIGHTNESS [- 1 ], new_l )
147159
148160 new_c = min (c , get_chroma_limit (l , h ))
149161
@@ -167,7 +179,7 @@ def in_pointer_gamut(color: Color, tolerance: float) -> bool:
167179 l , c , h = to_lch_sc (color )
168180
169181 # If lightness exceeds the acceptable range, then we are not in gamut
170- if (l < (LCH_L [0 ] - tolerance )) or (l > (LCH_L [- 1 ] + tolerance )):
182+ if (l < (LIGHTNESS [0 ] - tolerance )) or (l > (LIGHTNESS [- 1 ] + tolerance )):
171183 return False
172184
173185 # Test that the color does not exceed the max chroma
@@ -186,11 +198,11 @@ def pointer_gamut_boundary(lightness: float | None = None) -> Matrix:
186198 # For each hue, find the lightness/chroma point that is furthest away from the white point.
187199 if lightness is None :
188200 max_gamut = [] # type: Matrix
189- for i , h in enumerate (LCH_H [:- 1 ]):
201+ for i , h in enumerate (HUE [:- 1 ]):
190202 max_dxy = 0.0
191203 max_xyy = [0.0 , 0.0 , 0.0 ]
192- for j , l in enumerate (LCH_L ):
193- xyy = lch_sc_to_xyY ([l , GAMUT [i ][j ], h ])
204+ for j , l in enumerate (LIGHTNESS ):
205+ xyy = lch_sc_to_xyY ([l , LUT [i ][j ], h ])
194206 dxy = math .sqrt ((WHITE_POINT_SC [0 ] - xyy [0 ]) ** 2 + (WHITE_POINT_SC [1 ] - xyy [1 ]) ** 2 )
195207 if dxy > max_dxy :
196208 max_dxy = dxy
@@ -200,11 +212,11 @@ def pointer_gamut_boundary(lightness: float | None = None) -> Matrix:
200212
201213 # Pointer gamut boundary at a given lightness
202214 # Return all the points for a given lightness
203- elif LCH_L [0 ] <= lightness <= LCH_L [- 1 ]:
215+ elif LIGHTNESS [0 ] <= lightness <= LIGHTNESS [- 1 ]:
204216 li , lf = closest_lightness (lightness )
205- chroma = [alg .lerp (row [li ], row [li + 1 ], lf ) for row in GAMUT [:- 1 ]]
206- return [lch_sc_to_xyY ([lightness , c , h ]) for c , h in zip (chroma , LCH_H )]
217+ chroma = [alg .lerp (row [li ], row [li + 1 ], lf ) for row in LUT [:- 1 ]]
218+ return [lch_sc_to_xyY ([lightness , c , h ]) for c , h in zip (chroma , HUE )]
207219
208220 # Lightness exceeds threshold
209221 else :
210- raise ValueError (f'Lightness must be between { LCH_L [0 ]} and { LCH_L [- 1 ]} , but was { lightness } ' )
222+ raise ValueError (f'Lightness must be between { LIGHTNESS [0 ]} and { LIGHTNESS [- 1 ]} , but was { lightness } ' )
0 commit comments