Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
channelmixerrgb_shared.c
Go to the documentation of this file.
1/*
2 This file is part of the Ansel project.
3 Copyright (C) 2026 Aurélien PIERRE.
4
5 Ansel is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 Ansel is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with Ansel. If not, see <http://www.gnu.org/licenses/>.
17*/
18
19#ifdef HAVE_CONFIG_H
20#include "config.h"
22#endif
23
24#include "widgets/bauhaus.h"
26#include "pixel/illuminants.h"
27#include "math/matrices.h"
29
30#include <float.h>
31#include <math.h>
32
33#define DT_IOP_CHANNELMIXER_SHARED_INV_SQRT_3 0.5773502691896258f
34
36{
37 while(angle <= -M_PI) angle += 2.f * (float)M_PI;
38 while(angle > M_PI) angle -= 2.f * (float)M_PI;
39 return angle;
40}
41
43{
44 while(angle <= -(float)M_PI_2) angle += (float)M_PI;
45 while(angle > (float)M_PI_2) angle -= (float)M_PI;
46 return angle;
47}
48
50{
51 if(stretch >= 1.f) return fminf(0.5f * (stretch + 1.f), 1.5f);
52 if(stretch >= -1.f) return stretch;
53 return fmaxf(0.5f * (stretch - 1.f), -1.5f);
54}
55
57{
58 if(slider >= 1.f) return 2.f * slider - 1.f;
59 if(slider >= -1.f) return slider;
60 return 2.f * slider + 1.f;
61}
62
64{
65 return atanf(fmaxf(amount, 0.f)) / DT_IOP_CHANNELMIXER_SHARED_SIMPLE_TAN_SCALE;
66}
67
69{
70 const float bounded = CLAMP(slider, 0.f, 0.999f);
72}
73
84
95
98{
99 primaries->achromatic_hue = dt_bauhaus_slider_get(widgets[0]) * (float)M_PI_2;
100 primaries->achromatic_purity = dt_bauhaus_slider_get(widgets[1]);
101 primaries->red_hue = dt_bauhaus_slider_get(widgets[2]) * (float)M_PI_2;
102 primaries->red_purity = dt_bauhaus_slider_get(widgets[3]);
103 primaries->green_hue = dt_bauhaus_slider_get(widgets[4]) * (float)M_PI_2;
104 primaries->green_purity = dt_bauhaus_slider_get(widgets[5]);
105 primaries->blue_hue = dt_bauhaus_slider_get(widgets[6]) * (float)M_PI_2;
106 primaries->blue_purity = dt_bauhaus_slider_get(widgets[7]);
107 primaries->gain = dt_bauhaus_slider_get(widgets[8]);
108}
109
111 GtkWidget *const widgets[9])
112{
113 dt_bauhaus_slider_set(widgets[0], CLAMP(primaries->achromatic_hue / (float)M_PI_2, -1.f, 1.f));
114 dt_bauhaus_slider_set(widgets[1], primaries->achromatic_purity);
115 dt_bauhaus_slider_set(widgets[2], CLAMP(primaries->red_hue / (float)M_PI_2, -1.f, 1.f));
116 dt_bauhaus_slider_set(widgets[3], primaries->red_purity);
117 dt_bauhaus_slider_set(widgets[4], CLAMP(primaries->green_hue / (float)M_PI_2, -1.f, 1.f));
118 dt_bauhaus_slider_set(widgets[5], primaries->green_purity);
119 dt_bauhaus_slider_set(widgets[6], CLAMP(primaries->blue_hue / (float)M_PI_2, -1.f, 1.f));
120 dt_bauhaus_slider_set(widgets[7], primaries->blue_purity);
121 dt_bauhaus_slider_set(widgets[8], primaries->gain);
122}
123
125 GtkWidget *const widgets[6], dt_iop_channelmixer_shared_white_preserving_params_t *white_preserving)
126{
127 white_preserving->red_rotation = dt_bauhaus_slider_get(widgets[0]) * (float)M_PI_2;
128 white_preserving->red_saturation = dt_bauhaus_slider_get(widgets[1]);
129 white_preserving->green_rotation = dt_bauhaus_slider_get(widgets[2]) * (float)M_PI_2;
130 white_preserving->green_saturation = dt_bauhaus_slider_get(widgets[3]);
131 white_preserving->blue_rotation = dt_bauhaus_slider_get(widgets[4]) * (float)M_PI_2;
132 white_preserving->blue_saturation = dt_bauhaus_slider_get(widgets[5]);
133}
134
136 const dt_iop_channelmixer_shared_white_preserving_params_t *const white_preserving,
137 GtkWidget *const widgets[6])
138{
139 dt_bauhaus_slider_set(widgets[0], CLAMP(white_preserving->red_rotation / (float)M_PI_2, -1.f, 1.f));
140 dt_bauhaus_slider_set(widgets[1], white_preserving->red_saturation);
141 dt_bauhaus_slider_set(widgets[2], CLAMP(white_preserving->green_rotation / (float)M_PI_2, -1.f, 1.f));
142 dt_bauhaus_slider_set(widgets[3], white_preserving->green_saturation);
143 dt_bauhaus_slider_set(widgets[4], CLAMP(white_preserving->blue_rotation / (float)M_PI_2, -1.f, 1.f));
144 dt_bauhaus_slider_set(widgets[5], white_preserving->blue_saturation);
145}
146
148{
149 return normalize[0] && normalize[1] && normalize[2];
150}
151
152gboolean dt_iop_channelmixer_shared_get_matrix(const float rows[3][3], const gboolean normalize[3],
153 const gboolean force_normalize, float M[3][3])
154{
155 for(int row = 0; row < 3; row++)
156 {
157 float sum = 1.f;
158 if(normalize[row] || force_normalize)
159 {
160 sum = rows[row][0] + rows[row][1] + rows[row][2];
161 if(sum == 0.f) return FALSE;
162 }
163
164 for(int col = 0; col < 3; col++) M[row][col] = rows[row][col] / sum;
165 }
166
167 return TRUE;
168}
169
170void dt_iop_channelmixer_shared_set_matrix(float rows[3][3], const float M[3][3])
171{
172 for(int row = 0; row < 3; row++)
173 for(int col = 0; col < 3; col++)
174 rows[row][col] = M[row][col];
175}
176
177void dt_iop_channelmixer_shared_mul3x3(const float A[3][3], const float B[3][3], float C[3][3])
178{
179 for(int row = 0; row < 3; row++)
180 for(int col = 0; col < 3; col++)
181 {
182 C[row][col] = 0.f;
183 for(int k = 0; k < 3; k++) C[row][col] += A[row][k] * B[k][col];
184 }
185}
186
187static void _mixer_to_chroma_basis(const float M[3][3], float B[3][3])
188{
189 static const float P[3][3]
190 = { { 0.7071067811865475f, 0.4082482904638631f, DT_IOP_CHANNELMIXER_SHARED_INV_SQRT_3 },
191 { -0.7071067811865475f, 0.4082482904638631f, DT_IOP_CHANNELMIXER_SHARED_INV_SQRT_3 },
192 { 0.f, -0.8164965809277261f, DT_IOP_CHANNELMIXER_SHARED_INV_SQRT_3 } };
193 float PTM[3][3] = { { 0.f } };
194 float PT[3][3] = { { 0.f } };
195
196 for(int row = 0; row < 3; row++)
197 for(int col = 0; col < 3; col++)
198 PT[row][col] = P[col][row];
199
202}
203
204static void _mixer_from_chroma_basis(const float B[3][3], float M[3][3])
205{
206 static const float P[3][3]
207 = { { 0.7071067811865475f, 0.4082482904638631f, DT_IOP_CHANNELMIXER_SHARED_INV_SQRT_3 },
208 { -0.7071067811865475f, 0.4082482904638631f, DT_IOP_CHANNELMIXER_SHARED_INV_SQRT_3 },
209 { 0.f, -0.8164965809277261f, DT_IOP_CHANNELMIXER_SHARED_INV_SQRT_3 } };
210 float temp[3][3] = { { 0.f } };
211 float PT[3][3] = { { 0.f } };
212
213 for(int row = 0; row < 3; row++)
214 for(int col = 0; col < 3; col++)
215 PT[row][col] = P[col][row];
216
219}
220
223{
224 float B[3][3] = { { 0.f } };
226
227 const float a = B[0][0];
228 const float b = B[0][1];
229 const float c = B[1][0];
230 const float d = B[1][1];
231 const float theta = dt_iop_channelmixer_shared_wrap_pi(atan2f(c - b, a + d));
232 const float ctheta = cosf(theta);
233 const float stheta = sinf(theta);
234
235 const float s00 = ctheta * a + stheta * c;
236 const float s01 = ctheta * b + stheta * d;
237 const float s11 = -stheta * b + ctheta * d;
238 const float diff = s00 - s11;
239 const float psi = dt_iop_channelmixer_shared_wrap_half_pi(0.5f * atan2f(2.f * s01, diff));
240 const float radius = hypotf(0.5f * diff, s01);
241 const float trace = s00 + s11;
242
243 simple->theta = theta;
244 simple->psi = psi;
245 simple->stretch_1 = 0.5f * trace + radius;
246 simple->stretch_2 = 0.5f * trace - radius;
247 simple->coupling_amount = hypotf(B[2][0], B[2][1]);
248 simple->coupling_hue = simple->coupling_amount > 0.f
249 ? dt_iop_channelmixer_shared_wrap_pi(atan2f(B[2][1], B[2][0]))
250 : 0.f;
251}
252
254 float M[3][3])
255{
256 const float ctheta = cosf(simple->theta);
257 const float stheta = sinf(simple->theta);
258 const float cpsi = cosf(simple->psi);
259 const float spsi = sinf(simple->psi);
260 const float coupling_0 = simple->coupling_amount * cosf(simple->coupling_hue);
261 const float coupling_1 = simple->coupling_amount * sinf(simple->coupling_hue);
262
263 const float s00 = simple->stretch_1 * cpsi * cpsi + simple->stretch_2 * spsi * spsi;
264 const float s01 = (simple->stretch_1 - simple->stretch_2) * cpsi * spsi;
265 const float s11 = simple->stretch_1 * spsi * spsi + simple->stretch_2 * cpsi * cpsi;
266 const float B[3][3]
267 = { { ctheta * s00 - stheta * s01, ctheta * s01 - stheta * s11, 0.f },
268 { stheta * s00 + ctheta * s01, stheta * s01 + ctheta * s11, 0.f },
269 { coupling_0, coupling_1, 1.f } };
270
272}
273
274float dt_iop_channelmixer_shared_roundtrip_error(const float M[3][3], const float roundtrip[3][3])
275{
276 float max_error = 0.f;
277 for(int row = 0; row < 3; row++)
278 for(int col = 0; col < 3; col++)
279 max_error = fmaxf(max_error, fabsf(M[row][col] - roundtrip[row][col]));
280
281 return max_error;
282}
283
299float dt_iop_channelmixer_shared_roundtrip_error_relative(const float M[3][3], const float roundtrip[3][3])
300{
301 float max_magnitude = 1.f;
302 for(int row = 0; row < 3; row++)
303 for(int col = 0; col < 3; col++)
304 max_magnitude = fmaxf(max_magnitude, fabsf(M[row][col]));
305
306 return dt_iop_channelmixer_shared_roundtrip_error(M, roundtrip) / max_magnitude;
307}
308
327
329{
330 dt_aligned_pixel_t D50 = { 0.f };
331
332 switch(basis)
333 {
336 break;
339 break;
342 break;
344 default:
346 break;
347 }
348
349 for(int c = 0; c < 3; c++) white[c] = D50[c];
350}
351
352static inline float _affine_sum3(const float vector[3])
353{
354 return vector[0] + vector[1] + vector[2];
355}
356
357static gboolean _affine_normalize(const float vector[3], float normalized[3])
358{
359 const float sum = _affine_sum3(vector);
360 if(fabsf(sum) < DT_IOP_CHANNELMIXER_SHARED_SIMPLE_EPS) return FALSE;
361
362 for(int c = 0; c < 3; c++) normalized[c] = vector[c] / sum;
363 return TRUE;
364}
365
366static void _affine_project_difference(const float difference[3], float uv[2])
367{
368 uv[0] = 0.7071067811865475f * (difference[0] - difference[1]);
369 uv[1] = 0.4082482904638631f * (difference[0] + difference[1]) - 0.8164965809277261f * difference[2];
370}
371
372static void _affine_unproject_difference(const float uv[2], float difference[3])
373{
374 difference[0] = 0.7071067811865475f * uv[0] + 0.4082482904638631f * uv[1];
375 difference[1] = -0.7071067811865475f * uv[0] + 0.4082482904638631f * uv[1];
376 difference[2] = -0.8164965809277261f * uv[1];
377}
378
379static void _rotate_2d(const float vector[2], const float angle, float rotated[2])
380{
381 const float cosine = cosf(angle);
382 const float sine = sinf(angle);
383 rotated[0] = cosine * vector[0] - sine * vector[1];
384 rotated[1] = sine * vector[0] + cosine * vector[1];
385}
386
387static float _intersect_affine_ray_segment(const float direction[2], const float first[2], const float second[2])
388{
389 const float edge_x = second[0] - first[0];
390 const float edge_y = second[1] - first[1];
391 const float denominator = direction[0] * edge_y - direction[1] * edge_x;
392 if(fabsf(denominator) < DT_IOP_CHANNELMIXER_SHARED_SIMPLE_EPS) return FLT_MAX;
393
394 const float t = (first[0] * edge_y - first[1] * edge_x) / denominator;
395 const float u = (first[0] * direction[1] - first[1] * direction[0]) / denominator;
396 if(t >= 0.f && u >= 0.f && u <= 1.f) return t;
397 return FLT_MAX;
398}
399
400static float _affine_distance_to_edge(const float direction[2], const float reference_primaries[3][2])
401{
402 float distance_to_edge = FLT_MAX;
403
404 for(int i = 0; i < 3; i++)
405 {
406 const int next = i == 2 ? 0 : i + 1;
407 const float distance = _intersect_affine_ray_segment(direction, reference_primaries[i],
408 reference_primaries[next]);
409 if(distance < distance_to_edge) distance_to_edge = distance;
410 }
411
412 return distance_to_edge;
413}
414
416 float white_normalized[3], float reference_primaries[3][2])
417{
418 const float identity[3][3] = { { 1.f, 0.f, 0.f },
419 { 0.f, 1.f, 0.f },
420 { 0.f, 0.f, 1.f } };
421 float white[3] = { 0.f };
422
423 _primaries_reference_white(basis, white);
424 if(!_affine_normalize(white, white_normalized)) return FALSE;
425
426 for(int i = 0; i < 3; i++)
427 {
428 float difference[3] = { identity[i][0] - white_normalized[0],
429 identity[i][1] - white_normalized[1],
430 identity[i][2] - white_normalized[2] };
431 _affine_project_difference(difference, reference_primaries[i]);
432 }
433
434 return TRUE;
435}
436
437static gboolean _affine_point_from_polar(const float white_normalized[3], const float reference_primaries[3][2],
438 const int reference_index, const float hue, const float purity,
439 float point_normalized[3])
440{
441 const float *const reference = reference_primaries[reference_index];
442 const float radius = hypotf(reference[0], reference[1]);
444
445 const float direction_reference[2] = { reference[0] / radius, reference[1] / radius };
446 float direction[2] = { 0.f };
447 float difference[3] = { 0.f };
448
449 _rotate_2d(direction_reference, hue, direction);
450
451 const float distance_to_edge = _affine_distance_to_edge(direction, reference_primaries);
452 if(distance_to_edge == FLT_MAX) return FALSE;
453
454 const float uv[2] = { purity * distance_to_edge * direction[0],
455 purity * distance_to_edge * direction[1] };
456 _affine_unproject_difference(uv, difference);
457
458 for(int c = 0; c < 3; c++) point_normalized[c] = white_normalized[c] + difference[c];
459 return TRUE;
460}
461
462static gboolean _affine_polar_from_point(const float white_normalized[3], const float reference_primaries[3][2],
463 const int reference_index, const float point_normalized[3], float *hue,
464 float *purity)
465{
466 const float *const reference = reference_primaries[reference_index];
467 const float reference_angle = atan2f(reference[1], reference[0]);
468 float difference[3] = { point_normalized[0] - white_normalized[0],
469 point_normalized[1] - white_normalized[1],
470 point_normalized[2] - white_normalized[2] };
471 float uv[2] = { 0.f };
472
473 _affine_project_difference(difference, uv);
474
475 const float radius = hypotf(uv[0], uv[1]);
477 {
478 *hue = 0.f;
479 *purity = 0.f;
480 return TRUE;
481 }
482
483 const float direction[2] = { uv[0] / radius, uv[1] / radius };
484 const float distance_to_edge = _affine_distance_to_edge(direction, reference_primaries);
485 if(distance_to_edge == FLT_MAX || distance_to_edge < DT_IOP_CHANNELMIXER_SHARED_SIMPLE_EPS) return FALSE;
486
487 *hue = dt_iop_channelmixer_shared_wrap_pi(atan2f(direction[1], direction[0]) - reference_angle);
488 *purity = radius / distance_to_edge;
489 return TRUE;
490}
491
507static gboolean _white_preserving_point_from_polar(const float white_normalized[3],
508 const float reference_primaries[3][2],
509 const int reference_index, const float rotation,
510 const float saturation, float point_normalized[3])
511{
512 const float *const reference = reference_primaries[reference_index];
513 const float reference_radius = hypotf(reference[0], reference[1]);
514 if(reference_radius < DT_IOP_CHANNELMIXER_SHARED_SIMPLE_EPS) return FALSE;
515
516 const float scale = 1.f + saturation;
517 float rotated[2] = { 0.f };
518 float difference[3] = { 0.f };
519
520 _rotate_2d(reference, rotation, rotated);
521
522 const float uv[2] = { scale * rotated[0], scale * rotated[1] };
523 _affine_unproject_difference(uv, difference);
524
525 for(int c = 0; c < 3; c++) point_normalized[c] = white_normalized[c] + difference[c];
526 return TRUE;
527}
528
540static gboolean _white_preserving_polar_from_point(const float white_normalized[3],
541 const float reference_primaries[3][2],
542 const int reference_index,
543 const float point_normalized[3], float *rotation,
544 float *saturation)
545{
546 const float *const reference = reference_primaries[reference_index];
547 const float reference_radius = hypotf(reference[0], reference[1]);
548 if(reference_radius < DT_IOP_CHANNELMIXER_SHARED_SIMPLE_EPS) return FALSE;
549
550 const float reference_angle = atan2f(reference[1], reference[0]);
551 const float difference[3] = { point_normalized[0] - white_normalized[0],
552 point_normalized[1] - white_normalized[1],
553 point_normalized[2] - white_normalized[2] };
554 float uv[2] = { 0.f };
555
556 _affine_project_difference(difference, uv);
557
558 const float radius = hypotf(uv[0], uv[1]);
560 {
561 // The column collapsed onto the white : the rotation carries no information any more.
562 *rotation = 0.f;
563 *saturation = -1.f;
564 return TRUE;
565 }
566
567 *rotation = dt_iop_channelmixer_shared_wrap_pi(atan2f(uv[1], uv[0]) - reference_angle);
568 *saturation = radius / reference_radius - 1.f;
569 return TRUE;
570}
571
574 float M[3][3])
575{
576 float white_reference[3] = { 0.f };
577 float reference_primaries[3][2] = { { 0.f } };
578 float white_reference_normalized[3] = { 0.f };
579 float custom_white_normalized[3] = { 0.f };
580 float custom_primaries[3][3] = { { 0.f } };
581 dt_colormatrix_t normalized_inverse = { { 0.f } };
582 dt_colormatrix_t normalized_primaries = { { 0.f } };
583
584 _primaries_reference_white(basis, white_reference);
585 const float white_reference_sum = _affine_sum3(white_reference);
586 if(fabsf(white_reference_sum) < DT_IOP_CHANNELMIXER_SHARED_SIMPLE_EPS) return FALSE;
587
588 if(!_build_affine_simplex(basis, white_reference_normalized, reference_primaries)) return FALSE;
589
590 if(!_affine_point_from_polar(white_reference_normalized, reference_primaries, 0, primaries->achromatic_hue,
591 primaries->achromatic_purity, custom_white_normalized))
592 return FALSE;
593
594 if(!_affine_point_from_polar(white_reference_normalized, reference_primaries, 0, primaries->red_hue,
595 primaries->red_purity, custom_primaries[0]))
596 return FALSE;
597 if(!_affine_point_from_polar(white_reference_normalized, reference_primaries, 1, primaries->green_hue,
598 primaries->green_purity, custom_primaries[1]))
599 return FALSE;
600 if(!_affine_point_from_polar(white_reference_normalized, reference_primaries, 2, primaries->blue_hue,
601 primaries->blue_purity, custom_primaries[2]))
602 return FALSE;
603
604 for(int row = 0; row < 3; row++)
605 for(int col = 0; col < 3; col++)
606 normalized_primaries[row][col] = custom_primaries[col][row];
607
608 if(mat3SSEinv(normalized_inverse, normalized_primaries)) return FALSE;
609
610 const float white_gain = primaries->gain * white_reference_sum;
611 const dt_aligned_pixel_t custom_white = { white_gain * custom_white_normalized[0],
612 white_gain * custom_white_normalized[1],
613 white_gain * custom_white_normalized[2],
614 0.f };
615 dt_aligned_pixel_t column_scales = { 0.f };
616
617 // The column scales solve normalized_primaries . scales = custom_white, so this is a plain
618 // matrix-vector product with the inverse. dt_apply_transposed_color_matrix() expects a matrix
619 // that is ALREADY stored transposed (see xyz_to_srgb_matrix_transposed) and would therefore
620 // compute the transpose of the inverse here, which is not the solution of that system.
621 dot_product(custom_white, normalized_inverse, column_scales);
622
623 for(int col = 0; col < 3; col++)
624 for(int row = 0; row < 3; row++)
625 M[row][col] = column_scales[col] * custom_primaries[col][row];
626
627 return TRUE;
628}
629
631 const float M[3][3],
633{
634 dt_colormatrix_t padded = { { 0.f } };
635 dt_colormatrix_t inverse = { { 0.f } };
636 float white_reference[3] = { 0.f };
637 float white_reference_normalized[3] = { 0.f };
638 float reference_primaries[3][2] = { { 0.f } };
639 float custom_white[3] = { 0.f };
640 float custom_white_normalized[3] = { 0.f };
641 float custom_primary_normalized[3] = { 0.f };
642
643 for(int row = 0; row < 3; row++)
644 for(int col = 0; col < 3; col++)
645 padded[row][col] = M[row][col];
646 if(mat3SSEinv(inverse, padded)) return FALSE;
647
648 _primaries_reference_white(basis, white_reference);
649 const float white_reference_sum = _affine_sum3(white_reference);
650 if(fabsf(white_reference_sum) < DT_IOP_CHANNELMIXER_SHARED_SIMPLE_EPS) return FALSE;
651
652 if(!_build_affine_simplex(basis, white_reference_normalized, reference_primaries)) return FALSE;
653
654 for(int row = 0; row < 3; row++) custom_white[row] = M[row][0] + M[row][1] + M[row][2];
655 if(!_affine_normalize(custom_white, custom_white_normalized)) return FALSE;
656
657 primaries->gain = _affine_sum3(custom_white) / white_reference_sum;
658 if(!_affine_polar_from_point(white_reference_normalized, reference_primaries, 0, custom_white_normalized,
659 &primaries->achromatic_hue, &primaries->achromatic_purity))
660 return FALSE;
661
662 for(int primary = 0; primary < 3; primary++)
663 {
664 const float column[3] = { M[0][primary], M[1][primary], M[2][primary] };
665 if(!_affine_normalize(column, custom_primary_normalized)) return FALSE;
666
667 float *hue = primary == 0 ? &primaries->red_hue : primary == 1 ? &primaries->green_hue : &primaries->blue_hue;
668 float *purity = primary == 0 ? &primaries->red_purity
669 : primary == 1 ? &primaries->green_purity
670 : &primaries->blue_purity;
671
672 if(!_affine_polar_from_point(white_reference_normalized, reference_primaries, primary,
673 custom_primary_normalized, hue, purity))
674 return FALSE;
675 }
676
677 return TRUE;
678}
679
686static float _affine_triangle_area(const float vertices[3][2])
687{
688 const float first[2] = { vertices[1][0] - vertices[0][0], vertices[1][1] - vertices[0][1] };
689 const float second[2] = { vertices[2][0] - vertices[0][0], vertices[2][1] - vertices[0][1] };
690
691 return first[0] * second[1] - first[1] * second[0];
692}
693
709 const dt_iop_channelmixer_shared_white_preserving_params_t *const white_preserving, float M[3][3])
710{
711 const float rotations[3] = { white_preserving->red_rotation, white_preserving->green_rotation,
712 white_preserving->blue_rotation };
713 const float saturations[3] = { white_preserving->red_saturation, white_preserving->green_saturation,
714 white_preserving->blue_saturation };
715 float white_reference[3] = { 0.f };
716 float white_reference_normalized[3] = { 0.f };
717 float reference_primaries[3][2] = { { 0.f } };
718 float custom_primaries[3][3] = { { 0.f } };
719 dt_colormatrix_t normalized_primaries = { { 0.f } };
720 dt_colormatrix_t normalized_inverse = { { 0.f } };
721
722 _primaries_reference_white(basis, white_reference);
723 if(!_build_affine_simplex(basis, white_reference_normalized, reference_primaries)) return FALSE;
724
725 float custom_footprint[3][2] = { { 0.f } };
726
727 for(int primary = 0; primary < 3; primary++)
728 {
729 if(!_white_preserving_point_from_polar(white_reference_normalized, reference_primaries, primary,
730 rotations[primary], saturations[primary], custom_primaries[primary]))
731 return FALSE;
732
733 const float difference[3] = { custom_primaries[primary][0] - white_reference_normalized[0],
734 custom_primaries[primary][1] - white_reference_normalized[1],
735 custom_primaries[primary][2] - white_reference_normalized[2] };
736 _affine_project_difference(difference, custom_footprint[primary]);
737 }
738
739 // Two primaries rotated onto the same ray flatten the chromaticity triangle. The matrix is then
740 // still invertible on paper, but the column scales the white constraint solves for explode and
741 // the model stops being numerically reversible, so refuse it here rather than hand back a
742 // matrix no roundtrip can recognize. The bound is a fraction of the untouched basis triangle,
743 // which keeps it scale-free across bases.
744 if(fabsf(_affine_triangle_area(custom_footprint))
746 return FALSE;
747
748 for(int row = 0; row < 3; row++)
749 for(int col = 0; col < 3; col++)
750 normalized_primaries[row][col] = custom_primaries[col][row];
751
752 if(mat3SSEinv(normalized_inverse, normalized_primaries)) return FALSE;
753
754 const dt_aligned_pixel_t padded_white
755 = { white_reference[0], white_reference[1], white_reference[2], 0.f };
756 dt_aligned_pixel_t white_weights = { 0.f };
757 dot_product(padded_white, normalized_inverse, white_weights);
758
759 for(int col = 0; col < 3; col++)
760 {
761 if(fabsf(white_reference[col]) < DT_IOP_CHANNELMIXER_SHARED_SIMPLE_EPS) return FALSE;
762
763 const float column_scale = white_weights[col] / white_reference[col];
764 for(int row = 0; row < 3; row++) M[row][col] = column_scale * custom_primaries[col][row];
765 }
766
767 return TRUE;
768}
769
785 const dt_iop_channelmixer_shared_primaries_basis_t basis, const float M[3][3],
787{
788 float white_reference_normalized[3] = { 0.f };
789 float reference_primaries[3][2] = { { 0.f } };
790 float custom_primary_normalized[3] = { 0.f };
791
792 if(!_build_affine_simplex(basis, white_reference_normalized, reference_primaries)) return FALSE;
793
794 for(int primary = 0; primary < 3; primary++)
795 {
796 const float column[3] = { M[0][primary], M[1][primary], M[2][primary] };
797 if(!_affine_normalize(column, custom_primary_normalized)) return FALSE;
798
799 float *rotation = primary == 0 ? &white_preserving->red_rotation
800 : primary == 1 ? &white_preserving->green_rotation
801 : &white_preserving->blue_rotation;
802 float *saturation = primary == 0 ? &white_preserving->red_saturation
803 : primary == 1 ? &white_preserving->green_saturation
804 : &white_preserving->blue_saturation;
805
806 if(!_white_preserving_polar_from_point(white_reference_normalized, reference_primaries, primary,
807 custom_primary_normalized, rotation, saturation))
808 return FALSE;
809 }
810
811 return TRUE;
812}
813
815 float source[3])
816{
817 static const float P[3][3]
818 = { { 0.7071067811865475f, 0.4082482904638631f, DT_IOP_CHANNELMIXER_SHARED_INV_SQRT_3 },
819 { -0.7071067811865475f, 0.4082482904638631f, DT_IOP_CHANNELMIXER_SHARED_INV_SQRT_3 },
820 { 0.f, -0.8164965809277261f, DT_IOP_CHANNELMIXER_SHARED_INV_SQRT_3 } };
821 const float basis[3]
825 : 0.f,
829 : 0.f,
830 1.f };
831
832 for(int row = 0; row < 3; row++)
833 {
834 source[row] = 0.f;
835 for(int col = 0; col < 3; col++) source[row] += P[row][col] * basis[col];
836 }
837}
838
840{
841 const float max_RGB = fmaxf(fmaxf(linear_display_rgb[0], linear_display_rgb[1]), linear_display_rgb[2]);
842 if(max_RGB > 1.f)
843 for(int c = 0; c < 3; c++) linear_display_rgb[c] = fmaxf(linear_display_rgb[c] / max_RGB, 0.f);
844 else
845 for(int c = 0; c < 3; c++) linear_display_rgb[c] = fmaxf(linear_display_rgb[c], 0.f);
846}
847
849 const dt_iop_order_iccprofile_info_t *const work_profile,
850 const dt_iop_order_iccprofile_info_t *const display_profile,
851 dt_aligned_pixel_t display_rgb)
852{
853 if(!IS_NULL_PTR(work_profile) && !IS_NULL_PTR(display_profile))
854 {
855 dt_aligned_pixel_t XYZ = { 0.f };
856 dt_aligned_pixel_t linear_display_rgb = { 0.f };
857
858 dt_ioppr_rgb_matrix_to_xyz(work_rgb, XYZ, work_profile->matrix_in_transposed, work_profile->lut_in,
859 work_profile->unbounded_coeffs_in, work_profile->lutsize,
860 work_profile->nonlinearlut);
861 dt_apply_transposed_color_matrix(XYZ, display_profile->matrix_out_transposed, linear_display_rgb);
862 _normalize_linear_display_rgb(linear_display_rgb);
863
864 if(display_profile->nonlinearlut)
865 _apply_trc(linear_display_rgb, display_rgb, display_profile->lut_out, display_profile->unbounded_coeffs_out,
866 display_profile->lutsize);
867 else
868 for(int c = 0; c < 4; c++) display_rgb[c] = linear_display_rgb[c];
869 }
870 else
871 {
872 for(int c = 0; c < 4; c++) display_rgb[c] = work_rgb[c];
874 }
875
876 for(int c = 0; c < 3; c++) display_rgb[c] = CLAMP(display_rgb[c], 0.f, 1.f);
877}
878
880 const dt_iop_order_iccprofile_info_t *const work_profile,
881 const dt_iop_order_iccprofile_info_t *const display_profile,
882 float display_rgb[3])
883{
884 dt_aligned_pixel_t work_rgb = { module_color[0], module_color[1], module_color[2], 0.f };
885 dt_aligned_pixel_t display = { 0.f };
886
888 {
889 dt_aligned_pixel_t LMS = { module_color[0], module_color[1], module_color[2], 0.f };
890 convert_any_LMS_to_RGB(LMS, work_rgb, adaptation);
891 }
892
893 dt_iop_channelmixer_shared_work_rgb_to_display(work_rgb, work_profile, display_profile, display);
894 for(int c = 0; c < 3; c++) display_rgb[c] = display[c];
895}
896
897void dt_iop_channelmixer_shared_paint_temperature_slider(GtkWidget *const widget, const float temperature_min,
898 const float temperature_max)
899{
901
902 const float temp_range = temperature_max - temperature_min;
903 for(int i = 0; i < DT_BAUHAUS_SLIDER_MAX_STOPS; i++)
904 {
905 const float stop = (float)i / (float)(DT_BAUHAUS_SLIDER_MAX_STOPS - 1);
906 const float temperature = temperature_min + stop * temp_range;
907 dt_aligned_pixel_t RGB = { 0.f };
908
909 illuminant_CCT_to_RGB(temperature, RGB);
910 dt_bauhaus_slider_set_stop(widget, stop, RGB[0], RGB[1], RGB[2]);
911 }
912
913 gtk_widget_queue_draw(widget);
914}
915
917 const dt_iop_order_iccprofile_info_t *const work_profile,
918 const dt_iop_order_iccprofile_info_t *const display_profile,
919 GtkWidget *const widget, const float stop, const float c,
920 const float r, const float g, const float b)
921{
922 const float module_color[3]
923 = { 0.5f * (c * r + 1.f - r), 0.5f * (c * g + 1.f - g), 0.5f * (c * b + 1.f - b) };
924 float display_rgb[3] = { 0.f };
925
926 dt_iop_channelmixer_shared_module_color_to_display(module_color, adaptation, work_profile, display_profile,
927 display_rgb);
928 dt_bauhaus_slider_set_stop(widget, stop, display_rgb[0], display_rgb[1], display_rgb[2]);
929}
930
932 const dt_iop_order_iccprofile_info_t *const work_profile,
933 const dt_iop_order_iccprofile_info_t *const display_profile,
934 const float r, const float g, const float b,
935 const gboolean normalize, const float row[3],
936 GtkWidget *const widgets[3])
937{
938 dt_aligned_pixel_t RGB = { row[0], row[1], row[2], 0.f };
939
940 if(normalize)
941 {
942 const float sum = RGB[0] + RGB[1] + RGB[2];
943 if(sum != 0.f)
944 for(int c = 0; c < 3; c++) RGB[c] /= sum;
945 }
946
947 for(int widget = 0; widget < 3; widget++) dt_bauhaus_slider_clear_stops(widgets[widget]);
948
949 for(int i = 0; i < DT_BAUHAUS_SLIDER_MAX_STOPS; i++)
950 {
951 const float stop = (float)i / (float)(DT_BAUHAUS_SLIDER_MAX_STOPS - 1);
952 const float x_r = dt_bauhaus_slider_get_hard_min(widgets[0])
953 + stop * (dt_bauhaus_slider_get_hard_max(widgets[0]) - dt_bauhaus_slider_get_hard_min(widgets[0]));
954 const float x_g = dt_bauhaus_slider_get_hard_min(widgets[1])
955 + stop * (dt_bauhaus_slider_get_hard_max(widgets[1]) - dt_bauhaus_slider_get_hard_min(widgets[1]));
956 const float x_b = dt_bauhaus_slider_get_hard_min(widgets[2])
957 + stop * (dt_bauhaus_slider_get_hard_max(widgets[2]) - dt_bauhaus_slider_get_hard_min(widgets[2]));
958
959 _paint_RGB_slider_stop(adaptation, work_profile, display_profile, widgets[0], stop, x_r + RGB[1] + RGB[2],
960 r, g, b);
961 _paint_RGB_slider_stop(adaptation, work_profile, display_profile, widgets[1], stop, RGB[0] + x_g + RGB[2],
962 r, g, b);
963 _paint_RGB_slider_stop(adaptation, work_profile, display_profile, widgets[2], stop, RGB[0] + RGB[1] + x_b,
964 r, g, b);
965 }
966
967 for(int widget = 0; widget < 3; widget++) gtk_widget_queue_draw(widgets[widget]);
968}
969
970static void _shared_paint_probe_matrix(const float source[3], const float M[3][3], float module_color[3])
971{
972 dt_colormatrix_t mix = { { 0.f } };
973 dt_aligned_pixel_t padded_source = { source[0], source[1], source[2], 0.f };
974 dt_aligned_pixel_t padded_color = { 0.f };
975
976 for(int row = 0; row < 3; row++)
977 for(int col = 0; col < 3; col++)
978 mix[row][col] = M[row][col];
979
980 dot_product(padded_source, mix, padded_color);
981 for(int c = 0; c < 3; c++) module_color[c] = padded_color[c];
982}
983
997static void _shared_simple_hue_probe(const float hue, float module_color[3])
998{
999 static const float P[3][3]
1000 = { { 0.7071067811865475f, 0.4082482904638631f, DT_IOP_CHANNELMIXER_SHARED_INV_SQRT_3 },
1001 { -0.7071067811865475f, 0.4082482904638631f, DT_IOP_CHANNELMIXER_SHARED_INV_SQRT_3 },
1002 { 0.f, -0.8164965809277261f, DT_IOP_CHANNELMIXER_SHARED_INV_SQRT_3 } };
1003 const float basis[3]
1006
1007 for(int row = 0; row < 3; row++)
1008 {
1009 module_color[row] = 0.f;
1010 for(int col = 0; col < 3; col++) module_color[row] += P[row][col] * basis[col];
1011 }
1012}
1013
1015 const dt_adaptation_t adaptation, const dt_iop_order_iccprofile_info_t *const work_profile,
1016 const dt_iop_order_iccprofile_info_t *const display_profile,
1017 const dt_iop_channelmixer_shared_simple_params_t *const simple, GtkWidget *const widgets[6])
1018{
1019 static const dt_iop_channelmixer_shared_simple_probe_t probes[6]
1026
1027 for(int widget = 0; widget < 6; widget++) dt_bauhaus_slider_clear_stops(widgets[widget]);
1028
1029 for(int i = 0; i < DT_BAUHAUS_SLIDER_MAX_STOPS; i++)
1030 {
1031 const float stop = (float)i / (float)(DT_BAUHAUS_SLIDER_MAX_STOPS - 1);
1032 const float slider = 2.f * stop - 1.f;
1033 const float stretch_slider = 3.f * stop - 1.5f;
1034
1035 for(int widget = 0; widget < 6; widget++)
1036 {
1037 dt_iop_channelmixer_shared_simple_params_t probe_simple = *simple;
1038 float source[3] = { 0.f };
1039 float M[3][3] = { { 0.f } };
1040 float module_color[3] = { 0.f };
1041 float display_rgb[3] = { 0.f };
1042 float coupling_hue = simple->coupling_hue;
1043
1044 switch(widget)
1045 {
1046 case 0:
1047 probe_simple.theta = slider * (float)M_PI;
1048 break;
1049 case 1:
1050 probe_simple.psi = slider * (float)M_PI_2;
1051 break;
1052 case 2:
1053 probe_simple.stretch_1 = dt_iop_channelmixer_shared_decode_simple_stretch(stretch_slider);
1054 break;
1055 case 3:
1056 probe_simple.stretch_2 = dt_iop_channelmixer_shared_decode_simple_stretch(stretch_slider);
1057 break;
1058 case 4:
1060 break;
1061 case 5:
1062 probe_simple.coupling_hue = slider * (float)M_PI;
1063 coupling_hue = probe_simple.coupling_hue;
1064 break;
1065 default:
1066 break;
1067 }
1068
1070 if(widget < 4)
1071 dt_iop_channelmixer_shared_simple_probe_source(probes[widget], source);
1072 else
1073 {
1074 static const float P[3][3]
1075 = { { 0.7071067811865475f, 0.4082482904638631f, DT_IOP_CHANNELMIXER_SHARED_INV_SQRT_3 },
1076 { -0.7071067811865475f, 0.4082482904638631f, DT_IOP_CHANNELMIXER_SHARED_INV_SQRT_3 },
1077 { 0.f, -0.8164965809277261f, DT_IOP_CHANNELMIXER_SHARED_INV_SQRT_3 } };
1078 const float basis[3]
1079 = { DT_IOP_CHANNELMIXER_SHARED_SIMPLE_CHROMA_PROBE * cosf(coupling_hue),
1080 DT_IOP_CHANNELMIXER_SHARED_SIMPLE_CHROMA_PROBE * sinf(coupling_hue), 1.f };
1081
1082 for(int row = 0; row < 3; row++)
1083 {
1084 source[row] = 0.f;
1085 for(int col = 0; col < 3; col++) source[row] += P[row][col] * basis[col];
1086 }
1087 }
1088
1089 _shared_paint_probe_matrix(source, M, module_color);
1090 dt_iop_channelmixer_shared_module_color_to_display(module_color, adaptation, work_profile, display_profile,
1091 display_rgb);
1092
1093 if((widget == 0 || widget == 5)
1094 && fabsf(fmaxf(fmaxf(display_rgb[0], display_rgb[1]), display_rgb[2])
1095 - fminf(fminf(display_rgb[0], display_rgb[1]), display_rgb[2])) < 0.05f)
1096 {
1097 const float hue = widget == 0 ? probe_simple.theta : coupling_hue;
1098 _shared_simple_hue_probe(hue, module_color);
1099 dt_iop_channelmixer_shared_module_color_to_display(module_color, adaptation, work_profile, display_profile,
1100 display_rgb);
1101 }
1102
1103 dt_bauhaus_slider_set_stop(widgets[widget], stop, display_rgb[0], display_rgb[1], display_rgb[2]);
1104 }
1105 }
1106
1107 for(int widget = 0; widget < 6; widget++) gtk_widget_queue_draw(widgets[widget]);
1108}
1109
1110static void _shared_primaries_probe_color(const float M[3][3], const int widget_index, float module_color[3])
1111{
1112 if(widget_index <= 1 || widget_index == 8)
1113 {
1114 for(int row = 0; row < 3; row++)
1115 module_color[row] = M[row][0] + M[row][1] + M[row][2];
1116 return;
1117 }
1118
1119 const int column = widget_index <= 3 ? 0 : widget_index <= 5 ? 1 : 2;
1120 for(int row = 0; row < 3; row++) module_color[row] = M[row][column];
1121}
1122
1124 const dt_adaptation_t adaptation, const dt_iop_order_iccprofile_info_t *const work_profile,
1125 const dt_iop_order_iccprofile_info_t *const display_profile,
1127 const dt_iop_channelmixer_shared_primaries_params_t *const primaries, GtkWidget *const widgets[9])
1128{
1129 for(int widget = 0; widget < 9; widget++) dt_bauhaus_slider_clear_stops(widgets[widget]);
1130
1131 for(int i = 0; i < DT_BAUHAUS_SLIDER_MAX_STOPS; i++)
1132 {
1133 const float stop = (float)i / (float)(DT_BAUHAUS_SLIDER_MAX_STOPS - 1);
1134
1135 for(int widget = 0; widget < 9; widget++)
1136 {
1138 float M[3][3] = { { 0.f } };
1139 float module_color[3] = { 0.f };
1140 float display_rgb[3] = { 0.f };
1141 const float hard_min = dt_bauhaus_slider_get_hard_min(widgets[widget]);
1142 const float hard_max = dt_bauhaus_slider_get_hard_max(widgets[widget]);
1143 const float value = hard_min + stop * (hard_max - hard_min);
1144
1145 switch(widget)
1146 {
1147 case 0:
1148 probe.achromatic_hue = value * (float)M_PI_2;
1149 break;
1150 case 1:
1151 probe.achromatic_purity = value;
1152 break;
1153 case 2:
1154 probe.red_hue = value * (float)M_PI_2;
1155 break;
1156 case 3:
1157 probe.red_purity = value;
1158 break;
1159 case 4:
1160 probe.green_hue = value * (float)M_PI_2;
1161 break;
1162 case 5:
1163 probe.green_purity = value;
1164 break;
1165 case 6:
1166 probe.blue_hue = value * (float)M_PI_2;
1167 break;
1168 case 7:
1169 probe.blue_purity = value;
1170 break;
1171 case 8:
1172 probe.gain = value;
1173 break;
1174 default:
1175 break;
1176 }
1177
1178 if(!dt_iop_channelmixer_shared_primaries_to_matrix(basis, &probe, M)) continue;
1179
1180 _shared_primaries_probe_color(M, widget, module_color);
1181 dt_iop_channelmixer_shared_module_color_to_display(module_color, adaptation, work_profile, display_profile,
1182 display_rgb);
1183 dt_bauhaus_slider_set_stop(widgets[widget], stop, display_rgb[0], display_rgb[1], display_rgb[2]);
1184 }
1185 }
1186
1187 for(int widget = 0; widget < 9; widget++) gtk_widget_queue_draw(widgets[widget]);
1188}
1189
1191 const dt_adaptation_t adaptation, const dt_iop_order_iccprofile_info_t *const work_profile,
1192 const dt_iop_order_iccprofile_info_t *const display_profile,
1194 const dt_iop_channelmixer_shared_white_preserving_params_t *const white_preserving,
1195 GtkWidget *const widgets[6])
1196{
1197 for(int widget = 0; widget < 6; widget++) dt_bauhaus_slider_clear_stops(widgets[widget]);
1198
1199 for(int i = 0; i < DT_BAUHAUS_SLIDER_MAX_STOPS; i++)
1200 {
1201 const float stop = (float)i / (float)(DT_BAUHAUS_SLIDER_MAX_STOPS - 1);
1202
1203 for(int widget = 0; widget < 6; widget++)
1204 {
1206 float M[3][3] = { { 0.f } };
1207 float module_color[3] = { 0.f };
1208 float display_rgb[3] = { 0.f };
1209 const float hard_min = dt_bauhaus_slider_get_hard_min(widgets[widget]);
1210 const float hard_max = dt_bauhaus_slider_get_hard_max(widgets[widget]);
1211 const float value = hard_min + stop * (hard_max - hard_min);
1212
1213 switch(widget)
1214 {
1215 case 0:
1216 probe.red_rotation = value * (float)M_PI_2;
1217 break;
1218 case 1:
1219 probe.red_saturation = value;
1220 break;
1221 case 2:
1222 probe.green_rotation = value * (float)M_PI_2;
1223 break;
1224 case 3:
1225 probe.green_saturation = value;
1226 break;
1227 case 4:
1228 probe.blue_rotation = value * (float)M_PI_2;
1229 break;
1230 case 5:
1231 probe.blue_saturation = value;
1232 break;
1233 default:
1234 break;
1235 }
1236
1237 if(!dt_iop_channelmixer_shared_white_preserving_to_matrix(basis, &probe, M)) continue;
1238
1239 // Each pair of sliders drives one mixer column : show that column's own color.
1240 const int column = widget / 2;
1241 for(int row = 0; row < 3; row++) module_color[row] = M[row][column];
1242
1243 dt_iop_channelmixer_shared_module_color_to_display(module_color, adaptation, work_profile, display_profile,
1244 display_rgb);
1245 dt_bauhaus_slider_set_stop(widgets[widget], stop, display_rgb[0], display_rgb[1], display_rgb[2]);
1246 }
1247 }
1248
1249 for(int widget = 0; widget < 6; widget++) gtk_widget_queue_draw(widgets[widget]);
1250}
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
void dt_bauhaus_slider_clear_stops(GtkWidget *widget)
Definition bauhaus.c:2153
void dt_bauhaus_slider_set_stop(GtkWidget *widget, float stop, float r, float g, float b)
Definition bauhaus.c:2161
float dt_bauhaus_slider_get(GtkWidget *widget)
Definition bauhaus.c:3280
float dt_bauhaus_slider_get_hard_min(GtkWidget *widget)
Definition bauhaus.c:1425
void dt_bauhaus_slider_set(GtkWidget *widget, float pos)
Definition bauhaus.c:3331
float dt_bauhaus_slider_get_hard_max(GtkWidget *widget)
Definition bauhaus.c:1450
#define DT_BAUHAUS_SLIDER_MAX_STOPS
Definition bauhaus.h:73
static __DT_CLONE_TARGETS__ void normalize(float *const buffer, const size_t width, const size_t height, const float norm)
Definition blurs.c:351
void dt_iop_channelmixer_shared_module_color_to_display(const float module_color[3], const dt_adaptation_t adaptation, const dt_iop_order_iccprofile_info_t *const work_profile, const dt_iop_order_iccprofile_info_t *const display_profile, float display_rgb[3])
void dt_iop_channelmixer_shared_paint_row_sliders(dt_adaptation_t adaptation, const dt_iop_order_iccprofile_info_t *const work_profile, const dt_iop_order_iccprofile_info_t *const display_profile, const float r, const float g, const float b, const gboolean normalize, const float row[3], GtkWidget *const widgets[3])
void dt_iop_channelmixer_shared_paint_primaries_sliders(const dt_adaptation_t adaptation, const dt_iop_order_iccprofile_info_t *const work_profile, const dt_iop_order_iccprofile_info_t *const display_profile, const dt_iop_channelmixer_shared_primaries_basis_t basis, const dt_iop_channelmixer_shared_primaries_params_t *const primaries, GtkWidget *const widgets[9])
static gboolean _white_preserving_point_from_polar(const float white_normalized[3], const float reference_primaries[3][2], const int reference_index, const float rotation, const float saturation, float point_normalized[3])
Place one white-preserving primary from its rotation and saturation.
static void _shared_paint_probe_matrix(const float source[3], const float M[3][3], float module_color[3])
void dt_iop_channelmixer_shared_paint_white_preserving_sliders(const dt_adaptation_t adaptation, const dt_iop_order_iccprofile_info_t *const work_profile, const dt_iop_order_iccprofile_info_t *const display_profile, const dt_iop_channelmixer_shared_primaries_basis_t basis, const dt_iop_channelmixer_shared_white_preserving_params_t *const white_preserving, GtkWidget *const widgets[6])
static void _shared_primaries_probe_color(const float M[3][3], const int widget_index, float module_color[3])
static void _affine_project_difference(const float difference[3], float uv[2])
void dt_iop_channelmixer_shared_white_preserving_from_sliders(GtkWidget *const widgets[6], dt_iop_channelmixer_shared_white_preserving_params_t *white_preserving)
void dt_iop_channelmixer_shared_simple_from_sliders(GtkWidget *const widgets[6], dt_iop_channelmixer_shared_simple_params_t *simple)
static gboolean _white_preserving_polar_from_point(const float white_normalized[3], const float reference_primaries[3][2], const int reference_index, const float point_normalized[3], float *rotation, float *saturation)
Read back the rotation and saturation of one white-preserving primary.
void dt_iop_channelmixer_shared_paint_temperature_slider(GtkWidget *const widget, const float temperature_min, const float temperature_max)
void dt_iop_channelmixer_shared_white_preserving_to_sliders(const dt_iop_channelmixer_shared_white_preserving_params_t *const white_preserving, GtkWidget *const widgets[6])
static float _affine_distance_to_edge(const float direction[2], const float reference_primaries[3][2])
void dt_iop_channelmixer_shared_primaries_to_sliders(const dt_iop_channelmixer_shared_primaries_params_t *const primaries, GtkWidget *const widgets[9])
void dt_iop_channelmixer_shared_simple_probe_source(const dt_iop_channelmixer_shared_simple_probe_t probe, float source[3])
float dt_iop_channelmixer_shared_decode_simple_stretch(const float slider)
void dt_iop_channelmixer_shared_simple_to_matrix(const dt_iop_channelmixer_shared_simple_params_t *const simple, float M[3][3])
static gboolean _affine_point_from_polar(const float white_normalized[3], const float reference_primaries[3][2], const int reference_index, const float hue, const float purity, float point_normalized[3])
gboolean dt_iop_channelmixer_shared_white_preserving_from_matrix(const dt_iop_channelmixer_shared_primaries_basis_t basis, const float M[3][3], dt_iop_channelmixer_shared_white_preserving_params_t *white_preserving)
Read a white-preserving primaries state back from a mixer matrix.
gboolean dt_iop_channelmixer_shared_white_preserving_to_matrix(const dt_iop_channelmixer_shared_primaries_basis_t basis, const dt_iop_channelmixer_shared_white_preserving_params_t *const white_preserving, float M[3][3])
Build the mixer matrix of a white-preserving primaries state.
static void _mixer_from_chroma_basis(const float B[3][3], float M[3][3])
static void _mixer_to_chroma_basis(const float M[3][3], float B[3][3])
static void _paint_RGB_slider_stop(const dt_adaptation_t adaptation, const dt_iop_order_iccprofile_info_t *const work_profile, const dt_iop_order_iccprofile_info_t *const display_profile, GtkWidget *const widget, const float stop, const float c, const float r, const float g, const float b)
float dt_iop_channelmixer_shared_encode_simple_coupling_amount(const float amount)
void dt_iop_channelmixer_shared_simple_to_sliders(const dt_iop_channelmixer_shared_simple_params_t *const simple, GtkWidget *const widgets[6])
float dt_iop_channelmixer_shared_decode_simple_coupling_amount(const float slider)
dt_iop_channelmixer_shared_primaries_basis_t dt_iop_channelmixer_shared_primaries_basis_from_adaptation(const dt_adaptation_t adaptation)
float dt_iop_channelmixer_shared_encode_simple_stretch(const float stretch)
void dt_iop_channelmixer_shared_work_rgb_to_display(const dt_aligned_pixel_t work_rgb, const dt_iop_order_iccprofile_info_t *const work_profile, const dt_iop_order_iccprofile_info_t *const display_profile, dt_aligned_pixel_t display_rgb)
static void _shared_simple_hue_probe(const float hue, float module_color[3])
Build a stable hue cue in the fixed chroma basis for hue-like sliders.
void dt_iop_channelmixer_shared_set_matrix(float rows[3][3], const float M[3][3])
static void _normalize_linear_display_rgb(dt_aligned_pixel_t linear_display_rgb)
static float _intersect_affine_ray_segment(const float direction[2], const float first[2], const float second[2])
void dt_iop_channelmixer_shared_paint_simple_sliders(const dt_adaptation_t adaptation, const dt_iop_order_iccprofile_info_t *const work_profile, const dt_iop_order_iccprofile_info_t *const display_profile, const dt_iop_channelmixer_shared_simple_params_t *const simple, GtkWidget *const widgets[6])
gboolean dt_iop_channelmixer_shared_primaries_from_matrix(const dt_iop_channelmixer_shared_primaries_basis_t basis, const float M[3][3], dt_iop_channelmixer_shared_primaries_params_t *primaries)
static void _primaries_reference_white(const dt_iop_channelmixer_shared_primaries_basis_t basis, float white[3])
static void _rotate_2d(const float vector[2], const float angle, float rotated[2])
static void _affine_unproject_difference(const float uv[2], float difference[3])
#define DT_IOP_CHANNELMIXER_SHARED_INV_SQRT_3
void dt_iop_channelmixer_shared_simple_from_matrix(const float M[3][3], dt_iop_channelmixer_shared_simple_params_t *simple)
static float _affine_triangle_area(const float vertices[3][2])
Twice the signed area of a triangle given in affine plane coordinates.
void dt_iop_channelmixer_shared_mul3x3(const float A[3][3], const float B[3][3], float C[3][3])
gboolean dt_iop_channelmixer_shared_rows_are_normalized(const gboolean normalize[3])
float dt_iop_channelmixer_shared_wrap_pi(float angle)
static gboolean _affine_normalize(const float vector[3], float normalized[3])
float dt_iop_channelmixer_shared_roundtrip_error(const float M[3][3], const float roundtrip[3][3])
gboolean dt_iop_channelmixer_shared_get_matrix(const float rows[3][3], const gboolean normalize[3], const gboolean force_normalize, float M[3][3])
gboolean dt_iop_channelmixer_shared_primaries_to_matrix(const dt_iop_channelmixer_shared_primaries_basis_t basis, const dt_iop_channelmixer_shared_primaries_params_t *primaries, float M[3][3])
float dt_iop_channelmixer_shared_roundtrip_error_relative(const float M[3][3], const float roundtrip[3][3])
Roundtrip error, measured against the magnitude of the matrix itself.
void dt_iop_channelmixer_shared_primaries_from_sliders(GtkWidget *const widgets[9], dt_iop_channelmixer_shared_primaries_params_t *primaries)
static float _affine_sum3(const float vector[3])
static gboolean _affine_polar_from_point(const float white_normalized[3], const float reference_primaries[3][2], const int reference_index, const float point_normalized[3], float *hue, float *purity)
float dt_iop_channelmixer_shared_wrap_half_pi(float angle)
static gboolean _build_affine_simplex(const dt_iop_channelmixer_shared_primaries_basis_t basis, float white_normalized[3], float reference_primaries[3][2])
dt_iop_channelmixer_shared_simple_probe_t
@ DT_IOP_CHANNELMIXER_SHARED_SIMPLE_PROBE_AXIS_2
@ DT_IOP_CHANNELMIXER_SHARED_SIMPLE_PROBE_AXIS_1
@ DT_IOP_CHANNELMIXER_SHARED_SIMPLE_PROBE_ROTATION
#define DT_IOP_CHANNELMIXER_SHARED_SIMPLE_CHROMA_PROBE
#define DT_IOP_CHANNELMIXER_SHARED_SIMPLE_TAN_SCALE
#define DT_IOP_CHANNELMIXER_SHARED_MIN_FOOTPRINT
#define DT_IOP_CHANNELMIXER_SHARED_SIMPLE_EPS
dt_iop_channelmixer_shared_primaries_basis_t
@ DT_IOP_CHANNELMIXER_SHARED_PRIMARIES_BASIS_CAT16
@ DT_IOP_CHANNELMIXER_SHARED_PRIMARIES_BASIS_RGB
@ DT_IOP_CHANNELMIXER_SHARED_PRIMARIES_BASIS_BRADFORD
@ DT_IOP_CHANNELMIXER_SHARED_PRIMARIES_BASIS_XYZ
static const dt_aligned_pixel_simd_t const dt_adaptation_t adaptation
static void convert_D50_to_LMS(const dt_adaptation_t adaptation, dt_aligned_pixel_t D50)
@ DT_ADAPTATION_LAST
@ DT_ADAPTATION_FULL_BRADFORD
@ DT_ADAPTATION_XYZ
@ DT_ADAPTATION_CAT16
@ DT_ADAPTATION_RGB
@ DT_ADAPTATION_LINEAR_BRADFORD
const int t
#define B(y, x)
#define A(y, x)
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
dt_aligned_pixel_t LMS
dt_apply_transposed_color_matrix(XYZ, xyz_to_srgb_matrix_transposed, sRGB)
static dt_aligned_pixel_t XYZ
static const float const float C
static const int row
static dt_aligned_pixel_t RGB
static const dt_colormatrix_t M
#define P(V, params)
static void illuminant_CCT_to_RGB(const float t, dt_aligned_pixel_t RGB)
static float mix(const float a, const float b, const float t)
Definition liquify.c:751
float *const restrict const size_t k
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition macros.h:96
#define M_PI
Definition math.h:47
float DT_ALIGNED_ARRAY dt_colormatrix_t[4][4]
Definition matrices.h:34
static int mat3SSEinv(dt_colormatrix_t dst, const dt_colormatrix_t src)
Definition matrices.h:37
static double reference(const op_t op, const double x)
static double max_error(const float *const got, const float *const in, const int n, const op_t op)
DT_ALIGNED_PIXEL float dt_aligned_pixel_t[4]
Definition simd.h:53
static const dt_aligned_pixel_simd_t value
Definition simd.h:144
const float r
White-preserving primaries model : per-primary rotation and saturation, no white freedom.
A profile reduced to the arithmetic the pixel loop can run: two matrices and six tone-curve LUTs,...
int nonlinearlut
Non-zero when the profile has tone curves at all; tested as a boolean everywhere, but it is really th...
int lutsize
Entry count of each of the six LUTs. Always 65536 in practice: both callers of dt_ioppr_init_profile_...
float * lut_out[3]
Per-channel linear -> encoded tone curve, same convention as lut_in.
float * lut_in[3]
Per-channel encoded -> linear tone curve, lutsize entries each, sampled over [0,1]....