LSST Applications g0f08755f38+c89d42e150,g1635faa6d4+b6cf076a36,g1653933729+a8ce1bb630,g1a0ca8cf93+4c08b13bf7,g28da252d5a+f33f8200ef,g29321ee8c0+0187be18b1,g2bbee38e9b+9634bc57db,g2bc492864f+9634bc57db,g2cdde0e794+c2c89b37c4,g3156d2b45e+41e33cbcdc,g347aa1857d+9634bc57db,g35bb328faa+a8ce1bb630,g3a166c0a6a+9634bc57db,g3e281a1b8c+9f2c4e2fc3,g414038480c+077ccc18e7,g41af890bb2+e740673f1a,g5fbc88fb19+17cd334064,g7642f7d749+c89d42e150,g781aacb6e4+a8ce1bb630,g80478fca09+f8b2ab54e1,g82479be7b0+e2bd23ab8b,g858d7b2824+c89d42e150,g9125e01d80+a8ce1bb630,g9726552aa6+10f999ec6a,ga5288a1d22+065360aec4,gacf8899fa4+9553554aa7,gae0086650b+a8ce1bb630,gb58c049af0+d64f4d3760,gbd46683f8f+ac57cbb13d,gc28159a63d+9634bc57db,gcf0d15dbbd+e37acf7834,gda3e153d99+c89d42e150,gda6a2b7d83+e37acf7834,gdaeeff99f8+1711a396fd,ge2409df99d+cb1e6652d6,ge79ae78c31+9634bc57db,gf0baf85859+147a0692ba,gf3967379c6+02b11634a5,w.2024.45
LSST Data Management Base Package
Loading...
Searching...
No Matches
_oklabTools.h
Go to the documentation of this file.
1/*
2Copyright (c) 2021 Björn Ottosson
3Copyright (c) 2024 The Trustees of Princeton University
4
5Permission is hereby granted, free of charge, to any person obtaining a copy of
6this software and associated documentation files (the "Software"), to deal in
7the Software without restriction, including without limitation the rights to
8use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
9of the Software, and to permit persons to whom the Software is furnished to do
10so, subject to the following conditions:
11
12The above copyright notice and this permission notice shall be included in all
13copies or substantial portions of the Software.
14
15THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21SOFTWARE.
22
23Modified 2024 to us Display P3 Conversions - Princeton University
24*/
25
26#include <math.h>
27#include <cmath>
28#include <float.h>
29
30namespace lsst {
31namespace cpputils {
32namespace details {
33
34struct Lab {float L; float a; float b;};
35struct RGB {float r; float g; float b;};
36
38{
39 float l = 0.4122214708f * c.r + 0.5363325363f * c.g + 0.0514459929f * c.b;
40 float m = 0.2119034982f * c.r + 0.6806995451f * c.g + 0.1073969566f * c.b;
41 float s = 0.0883024619f * c.r + 0.2817188376f * c.g + 0.6299787005f * c.b;
42
43 float l_ = cbrtf(l);
44 float m_ = cbrtf(m);
45 float s_ = cbrtf(s);
46
47 return {
48 0.2104542553f * l_ + 0.7936177850f * m_ - 0.0040720468f * s_,
49 1.9779984951f * l_ - 2.4285922050f * m_ + 0.4505937099f * s_,
50 0.0259040371f * l_ + 0.7827717662f * m_ - 0.8086757660f * s_,
51 };
52}
53
55{
56 float l = 0.48132729f * c.r + 0.46206791f * c.g + 00.0564956f * c.b;
57 float m = 0.2288381f * c.r + 0.6532344f * c.g + 0.11795441f * c.b;
58 float s = 0.08398602f * c.r + 0.22427279f * c.g + 0.69222084f * c.b;
59
60 float l_ = cbrtf(l);
61 float m_ = cbrtf(m);
62 float s_ = cbrtf(s);
63
64 return {
65 0.2104542553f * l_ + 0.7936177850f * m_ - 0.0040720468f * s_,
66 1.9779984951f * l_ - 2.4285922050f * m_ + 0.4505937099f * s_,
67 0.0259040371f * l_ + 0.7827717662f * m_ - 0.8086757660f * s_,
68 };
69}
70
71
73{
74 float l_ = c.L + 0.3963377774f * c.a + 0.2158037573f * c.b;
75 float m_ = c.L - 0.1055613458f * c.a - 0.0638541728f * c.b;
76 float s_ = c.L - 0.0894841775f * c.a - 1.2914855480f * c.b;
77
78 float l = l_ * l_ * l_;
79 float m = m_ * m_ * m_;
80 float s = s_ * s_ * s_;
81
82 return {
83 +4.0767416621f * l - 3.3077115913f * m + 0.2309699292f * s,
84 -1.2684380046f * l + 2.6097574011f * m - 0.3413193965f * s,
85 -0.0041960863f * l - 0.7034186147f * m + 1.7076147010f * s,
86 };
87}
88
90{
91 float l_ = c.L + 0.3963377774f * c.a + 0.2158037573f * c.b;
92 float m_ = c.L - 0.1055613458f * c.a - 0.0638541728f * c.b;
93 float s_ = c.L - 0.0894841775f * c.a - 1.2914855480f * c.b;
94
95 float l = l_ * l_ * l_;
96 float m = m_ * m_ * m_;
97 float s = s_ * s_ * s_;
98
99 return {
100 3.12811053f * l - 2.25707502f * m + 0.12930479f * s,
101 -1.09112816f * l + 2.41326676f * m - 0.32216817f * s,
102 -0.02601365f * l - 0.50802765f * m + 1.53331668f * s,
103 };
104}
105
106// Finds the maximum saturation possible for a given hue that fits in sRGB
107// Saturation here is defined as S = C/L
108// a and b must be normalized so a^2 + b^2 == 1
109float compute_max_saturation(float a, float b)
110{
111 // Max saturation will be when one of r, g or b goes below zero.
112
113 // Select different coefficients depending on which component goes below zero first
114 float k0, k1, k2, k3, k4, wl, wm, ws;
115
116 if (-1.88170328f * a - 0.80936493f * b > 1)
117 {
118 // Red component
119 k0 = +1.19086277f; k1 = +1.76576728f; k2 = +0.59662641f; k3 = +0.75515197f; k4 = +0.56771245f;
120 wl = +4.0767416621f; wm = -3.3077115913f; ws = +0.2309699292f;
121 }
122 else if (1.81444104f * a - 1.19445276f * b > 1)
123 {
124 // Green component
125 k0 = +0.73956515f; k1 = -0.45954404f; k2 = +0.08285427f; k3 = +0.12541070f; k4 = +0.14503204f;
126 wl = -1.2684380046f; wm = +2.6097574011f; ws = -0.3413193965f;
127 }
128 else
129 {
130 // Blue component
131 k0 = +1.35733652f; k1 = -0.00915799f; k2 = -1.15130210f; k3 = -0.50559606f; k4 = +0.00692167f;
132 wl = -0.0041960863f; wm = -0.7034186147f; ws = +1.7076147010f;
133 }
134
135 // Approximate max saturation using a polynomial:
136 float S = k0 + k1 * a + k2 * b + k3 * a * a + k4 * a * b;
137
138 // Do one step Halley's method to get closer
139 // this gives an error less than 10e6, except for some blue hues where the dS/dh is close to infinite
140 // this should be sufficient for most applications, otherwise do two/three steps
141
142 float k_l = +0.3963377774f * a + 0.2158037573f * b;
143 float k_m = -0.1055613458f * a - 0.0638541728f * b;
144 float k_s = -0.0894841775f * a - 1.2914855480f * b;
145
146 {
147 float l_ = 1.f + S * k_l;
148 float m_ = 1.f + S * k_m;
149 float s_ = 1.f + S * k_s;
150
151 float l = l_ * l_ * l_;
152 float m = m_ * m_ * m_;
153 float s = s_ * s_ * s_;
154
155 float l_dS = 3.f * k_l * l_ * l_;
156 float m_dS = 3.f * k_m * m_ * m_;
157 float s_dS = 3.f * k_s * s_ * s_;
158
159 float l_dS2 = 6.f * k_l * k_l * l_;
160 float m_dS2 = 6.f * k_m * k_m * m_;
161 float s_dS2 = 6.f * k_s * k_s * s_;
162
163 float f = wl * l + wm * m + ws * s;
164 float f1 = wl * l_dS + wm * m_dS + ws * s_dS;
165 float f2 = wl * l_dS2 + wm * m_dS2 + ws * s_dS2;
166
167 S = S - f * f1 / (f1*f1 - 0.5f * f * f2);
168 }
169
170 return S;
171}
172
173// finds L_cusp and C_cusp for a given hue
174// a and b must be normalized so a^2 + b^2 == 1
175struct LC { float L; float C; };
176LC find_cusp(float a, float b)
177{
178 // First, find the maximum saturation (saturation S = C/L)
179 float S_cusp = compute_max_saturation(a, b);
180
181 // Convert to linear sRGB to find the first point where at least one of r,g or b >= 1:
182 RGB rgb_at_max = oklab_to_linear_displayP3({ 1, S_cusp * a, S_cusp * b });
183 float L_cusp = cbrtf(1.f / fmax(fmax(rgb_at_max.r, rgb_at_max.g), rgb_at_max.b));
184 float C_cusp = L_cusp * S_cusp;
185
186 return { L_cusp , C_cusp };
187}
188
189// Finds intersection of the line defined by
190// L = L0 * (1 - t) + t * L1;
191// C = t * C1;
192// a and b must be normalized so a^2 + b^2 == 1
193float find_gamut_intersection(float a, float b, float L1, float C1, float L0)
194{
195 // Find the cusp of the gamut triangle
196 LC cusp = find_cusp(a, b);
197
198 // Find the intersection for upper and lower half seprately
199 float t;
200 if (((L1 - L0) * cusp.C - (cusp.L - L0) * C1) <= 0.f)
201 {
202 // Lower half
203
204 t = cusp.C * L0 / (C1 * cusp.L + cusp.C * (L0 - L1));
205 }
206 else
207 {
208 // Upper half
209
210 // First intersect with triangle
211 t = cusp.C * (L0 - 1.f) / (C1 * (cusp.L - 1.f) + cusp.C * (L0 - L1));
212
213 // Then one step Halley's method
214 {
215 float dL = L1 - L0;
216 float dC = C1;
217
218 float k_l = +0.3963377774f * a + 0.2158037573f * b;
219 float k_m = -0.1055613458f * a - 0.0638541728f * b;
220 float k_s = -0.0894841775f * a - 1.2914855480f * b;
221
222 float l_dt = dL + dC * k_l;
223 float m_dt = dL + dC * k_m;
224 float s_dt = dL + dC * k_s;
225
226
227 // If higher accuracy is required, 2 or 3 iterations of the following block can be used:
228 {
229 float L = L0 * (1.f - t) + t * L1;
230 float C = t * C1;
231
232 float l_ = L + C * k_l;
233 float m_ = L + C * k_m;
234 float s_ = L + C * k_s;
235
236 float l = l_ * l_ * l_;
237 float m = m_ * m_ * m_;
238 float s = s_ * s_ * s_;
239
240 float ldt = 3 * l_dt * l_ * l_;
241 float mdt = 3 * m_dt * m_ * m_;
242 float sdt = 3 * s_dt * s_ * s_;
243
244 float ldt2 = 6 * l_dt * l_dt * l_;
245 float mdt2 = 6 * m_dt * m_dt * m_;
246 float sdt2 = 6 * s_dt * s_dt * s_;
247
248 float r = 4.0767416621f * l - 3.3077115913f * m + 0.2309699292f * s - 1;
249 float r1 = 4.0767416621f * ldt - 3.3077115913f * mdt + 0.2309699292f * sdt;
250 float r2 = 4.0767416621f * ldt2 - 3.3077115913f * mdt2 + 0.2309699292f * sdt2;
251
252 float u_r = r1 / (r1 * r1 - 0.5f * r * r2);
253 float t_r = -r * u_r;
254
255 float g = -1.2684380046f * l + 2.6097574011f * m - 0.3413193965f * s - 1;
256 float g1 = -1.2684380046f * ldt + 2.6097574011f * mdt - 0.3413193965f * sdt;
257 float g2 = -1.2684380046f * ldt2 + 2.6097574011f * mdt2 - 0.3413193965f * sdt2;
258
259 float u_g = g1 / (g1 * g1 - 0.5f * g * g2);
260 float t_g = -g * u_g;
261
262 float b = -0.0041960863f * l - 0.7034186147f * m + 1.7076147010f * s - 1;
263 float b1 = -0.0041960863f * ldt - 0.7034186147f * mdt + 1.7076147010f * sdt;
264 float b2 = -0.0041960863f * ldt2 - 0.7034186147f * mdt2 + 1.7076147010f * sdt2;
265
266 float u_b = b1 / (b1 * b1 - 0.5f * b * b2);
267 float t_b = -b * u_b;
268
269 t_r = u_r >= 0.f ? t_r : FLT_MAX;
270 t_g = u_g >= 0.f ? t_g : FLT_MAX;
271 t_b = u_b >= 0.f ? t_b : FLT_MAX;
272
273 t += fmin(t_r, fmin(t_g, t_b));
274 }
275 }
276 }
277
278 return t;
279}
280
281float clamp(float x, float min, float max)
282{
283 if (x < min)
284 return min;
285 if (x > max)
286 return max;
287
288 return x;
289}
290
291float sgn(float x)
292{
293 return (float)(0.f < x) - (float)(x < 0.f);
294}
295
297{
298 if (rgb.r < 1 && rgb.g < 1 && rgb.b < 1 && rgb.r > 0 && rgb.g > 0 && rgb.b > 0)
299 return rgb;
300
302
303 float L = lab.L;
304 float eps = 0.00001f;
305 float C = fmax(eps, sqrtf(lab.a * lab.a + lab.b * lab.b));
306 float a_ = lab.a / C;
307 float b_ = lab.b / C;
308
309 float L0 = clamp(L, 0, 1);
310
311 float t = find_gamut_intersection(a_, b_, L, C, L0);
312 float L_clipped = L0 * (1 - t) + t * L;
313 float C_clipped = t * C;
314
315 return oklab_to_linear_displayP3({ L_clipped, C_clipped * a_, C_clipped * b_ });
316}
317
319{
320 if (rgb.r < 1 && rgb.g < 1 && rgb.b < 1 && rgb.r > 0 && rgb.g > 0 && rgb.b > 0)
321 return rgb;
322
324
325 float L = lab.L;
326 float eps = 0.00001f;
327 float C = fmax(eps, sqrtf(lab.a * lab.a + lab.b * lab.b));
328 float a_ = lab.a / C;
329 float b_ = lab.b / C;
330
331 float L0 = 0.5;
332
333 float t = find_gamut_intersection(a_, b_, L, C, L0);
334 float L_clipped = L0 * (1 - t) + t * L;
335 float C_clipped = t * C;
336
337 return oklab_to_linear_displayP3({ L_clipped, C_clipped * a_, C_clipped * b_ });
338}
339
341{
342 if (rgb.r < 1 && rgb.g < 1 && rgb.b < 1 && rgb.r > 0 && rgb.g > 0 && rgb.b > 0)
343 return rgb;
344
346
347 float L = lab.L;
348 float eps = 0.00001f;
349 float C = fmax(eps, sqrtf(lab.a * lab.a + lab.b * lab.b));
350 float a_ = lab.a / C;
351 float b_ = lab.b / C;
352
353 // The cusp is computed here and in find_gamut_intersection, an optimized solution would only compute it once.
354 LC cusp = find_cusp(a_, b_);
355
356 float L0 = cusp.L;
357
358 float t = find_gamut_intersection(a_, b_, L, C, L0);
359
360 float L_clipped = L0 * (1 - t) + t * L;
361 float C_clipped = t * C;
362
363 return oklab_to_linear_displayP3({ L_clipped, C_clipped * a_, C_clipped * b_ });
364}
365
366RGB gamut_clip_adaptive_L0_0_5(RGB rgb, float alpha = 0.05f)
367{
368 if (rgb.r < 1 && rgb.g < 1 && rgb.b < 1 && rgb.r > 0 && rgb.g > 0 && rgb.b > 0)
369 return rgb;
370
372
373 float L = lab.L;
374 float eps = 0.00001f;
375 float C = fmax(eps, sqrtf(lab.a * lab.a + lab.b * lab.b));
376 float a_ = lab.a / C;
377 float b_ = lab.b / C;
378
379 float Ld = L - 0.5f;
380 float e1 = 0.5f + fabs(Ld) + alpha * C;
381 float L0 = 0.5f*(1.f + sgn(Ld)*(e1 - sqrtf(e1*e1 - 2.f *fabs(Ld))));
382
383 float t = find_gamut_intersection(a_, b_, L, C, L0);
384 float L_clipped = L0 * (1.f - t) + t * L;
385 float C_clipped = t * C;
386
387 return oklab_to_linear_displayP3({ L_clipped, C_clipped * a_, C_clipped * b_ });
388}
389
390RGB gamut_clip_adaptive_L0_L_cusp(RGB rgb, float alpha = 0.05f)
391{
392 if (rgb.r < 1 && rgb.g < 1 && rgb.b < 1 && rgb.r > 0 && rgb.g > 0 && rgb.b > 0)
393 return rgb;
394
396
397 float L = lab.L;
398 float eps = 0.00001f;
399 float C = fmax(eps, sqrtf(lab.a * lab.a + lab.b * lab.b));
400 float a_ = lab.a / C;
401 float b_ = lab.b / C;
402
403 // The cusp is computed here and in find_gamut_intersection, an optimized solution would only compute it once.
404 LC cusp = find_cusp(a_, b_);
405
406 float Ld = L - cusp.L;
407 float k = 2.f * (Ld > 0 ? 1.f - cusp.L : cusp.L);
408
409 float e1 = 0.5f*k + fabs(Ld) + alpha * C/k;
410 float L0 = cusp.L + 0.5f * (sgn(Ld) * (e1 - sqrtf(e1 * e1 - 2.f * k * fabs(Ld))));
411
412 float t = find_gamut_intersection(a_, b_, L, C, L0);
413 float L_clipped = L0 * (1.f - t) + t * L;
414 float C_clipped = t * C;
415
416 return oklab_to_linear_displayP3({ L_clipped, C_clipped * a_, C_clipped * b_ });
417}
418
419}
420}
421}
int min
int max
int m
Definition SpanSet.cc:48
table::Key< int > b
table::Key< int > a
Lab linear_srgb_to_oklab(RGB c)
Definition _oklabTools.h:37
RGB gamut_clip_preserve_chroma(RGB rgb)
RGB gamut_clip_project_to_L_cusp(RGB rgb)
Lab linear_displayP3_to_oklab(RGB c)
Definition _oklabTools.h:54
RGB oklab_to_linear_displayP3(Lab c)
Definition _oklabTools.h:89
float clamp(float x, float min, float max)
RGB oklab_to_linear_srgb(Lab c)
Definition _oklabTools.h:72
RGB gamut_clip_adaptive_L0_L_cusp(RGB rgb, float alpha=0.05f)
LC find_cusp(float a, float b)
RGB gamut_clip_adaptive_L0_0_5(RGB rgb, float alpha=0.05f)
float compute_max_saturation(float a, float b)
float find_gamut_intersection(float a, float b, float L1, float C1, float L0)
RGB gamut_clip_project_to_0_5(RGB rgb)