Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
splittoningrgb.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"
21#endif
22
23#include "bauhaus/bauhaus.h"
25#include "common/darktable.h"
26#include "common/iop_profile.h"
27#include "common/illuminants.h"
28#include "common/matrices.h"
29#include "common/opencl.h"
30#include "control/control.h"
31#include "develop/develop.h"
32#include "develop/imageop.h"
33#include "develop/imageop_gui.h"
37#include "gui/gtk.h"
39#include "iop/iop_api.h"
40
41// Keep the shared implementation in this translation unit to avoid
42// duplicate globals from a separate compiled object.
44
45#include <gtk/gtk.h>
46#include <math.h>
47#include <stdlib.h>
48#include <string.h>
49
51
52#define DT_SPLITTONING_RGB_POINT_COUNT 2
53#define DT_SPLITTONING_RGB_ROW_COUNT 3
54#define DT_SPLITTONING_RGB_COMPLETE_COUNT 3
55#define DT_SPLITTONING_RGB_PREVIEW_HEIGHT 60
61
68
78
88
93
119
129
130static const char *const _mode_conf[DT_SPLITTONING_RGB_POINT_COUNT] = {
131 "plugins/darkroom/splittoningrgb/dark_mixer_mode",
132 "plugins/darkroom/splittoningrgb/bright_mixer_mode"
133};
134
135static const char *const _point_label[DT_SPLITTONING_RGB_POINT_COUNT] = {
136 N_("dark"),
137 N_("bright")
138};
139
141
142const char *name()
143{
144 return _("split-toning");
145}
146
147const char *aliases()
148{
149 return _("split toning|split tone RGB");
150}
151
152const char **description(struct dt_iop_module_t *self)
153{
154 return dt_iop_set_description(self,
155 _("blend two CAT16 plus RGB mixer corrections across brightness keyframes"),
156 _("creative or corrective"), _("linear, RGB, scene-referred"),
157 _("linear, RGB"), _("linear, RGB, scene-referred"));
158}
159
161{
162 return IOP_GROUP_COLOR;
163}
164
169
171{
172 return IOP_CS_RGB;
173}
174
177{
178 default_input_format(self, pipe, piece, dsc);
179 dsc->channels = 4;
180 dsc->datatype = TYPE_FLOAT;
181}
182
183static inline float _ev_to_grey(const float ev)
184{
185 return exp2f(ev);
186}
187
188static inline float _ev_to_luminance(const float ev)
189{
190 return _ev_to_grey(ev);
191}
192
193static void _temperature_to_xy(const float temperature, float *x, float *y)
194{
195 if(temperature > 4000.f)
196 CCT_to_xy_daylight(temperature, x, y);
197 else
198 CCT_to_xy_blackbody(temperature, x, y);
199}
200
201static void _get_point_rows(const dt_iop_splittoning_rgb_params_t *p, const int point, float rows[3][3],
202 gboolean normalize[3])
203{
204 for(int col = 0; col < 3; col++)
205 {
206 rows[0][col] = p->red[point][col];
207 rows[1][col] = p->green[point][col];
208 rows[2][col] = p->blue[point][col];
209 }
210
211 for(int row = 0; row < 3; row++) normalize[row] = p->normalize[point][row];
212}
213
214static void _set_point_rows(dt_iop_splittoning_rgb_params_t *p, const int point, const float M[3][3])
215{
216 for(int col = 0; col < 3; col++)
217 {
218 p->red[point][col] = M[0][col];
219 p->green[point][col] = M[1][col];
220 p->blue[point][col] = M[2][col];
221 }
222}
223
225 const int point)
226{
227 const float *const rows[3] = { p->red[point], p->green[point], p->blue[point] };
228
229 for(int row = 0; row < 3; row++)
230 {
231 for(int col = 0; col < 3; col++) dt_bauhaus_slider_set(g->point[point].complete[row][col], rows[row][col]);
232 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->point[point].normalize[row]), p->normalize[point][row]);
233 }
234}
235
238{
239 gtk_stack_set_visible_child_name(GTK_STACK(g->point[point].mixer_stack),
240 mode == DT_SPLITTONING_RGB_MIXER_SIMPLE ? "simple"
241 : mode == DT_SPLITTONING_RGB_MIXER_PRIMARIES ? "primaries"
242 : "complete");
243 gtk_widget_queue_resize(g->point[point].mixer_stack);
244 gtk_widget_queue_resize(g->point[point].page);
245 gtk_widget_queue_resize(GTK_WIDGET(g->tabs));
246}
247
248static void _build_cat16_rgb_matrix(const dt_iop_order_iccprofile_info_t *work_profile, const float temperature,
249 float CAT[3][3])
250{
251 for(int row = 0; row < 3; row++)
252 for(int col = 0; col < 3; col++)
253 CAT[row][col] = row == col ? 1.f : 0.f;
254
255 if(IS_NULL_PTR(work_profile)) return;
256
257 float x = 0.f;
258 float y = 0.f;
259 dt_aligned_pixel_t illuminant_XYZ = { 0.f };
260 dt_aligned_pixel_t illuminant_LMS = { 0.f };
261
262 _temperature_to_xy(temperature, &x, &y);
263 illuminant_xy_to_XYZ(x, y, illuminant_XYZ);
264 dt_store_simd_aligned(illuminant_LMS,
265 convert_any_XYZ_to_LMS(dt_load_simd_aligned(illuminant_XYZ), DT_ADAPTATION_CAT16));
266
267 // Build the RGB adaptation by probing the three working-space basis vectors independently.
268 for(int col = 0; col < 3; col++)
269 {
270 dt_aligned_pixel_t RGB_in = { 0.f };
271 dt_aligned_pixel_t XYZ_in = { 0.f };
272 dt_aligned_pixel_t XYZ_out = { 0.f };
273 dt_aligned_pixel_t RGB_out = { 0.f };
274
275 RGB_in[col] = 1.f;
276 dt_apply_transposed_color_matrix(RGB_in, work_profile->matrix_in_transposed, XYZ_in);
277 dt_store_simd_aligned(XYZ_out,
278 chroma_adapt_pixel(dt_load_simd_aligned(XYZ_in), dt_load_simd_aligned(illuminant_LMS),
279 DT_ADAPTATION_CAT16, 1.f));
280 dt_apply_transposed_color_matrix(XYZ_out, work_profile->matrix_out_transposed, RGB_out);
281
282 for(int row = 0; row < 3; row++) CAT[row][col] = RGB_out[row];
283 }
284}
285
287 const dt_iop_order_iccprofile_info_t *work_profile, dt_colormatrix_t point_matrix)
288{
289 float rows[3][3] = { { 0.f } };
290 gboolean normalize[3] = { FALSE, FALSE, FALSE };
291 float mixer[3][3] = { { 0.f } };
292 float CAT[3][3] = { { 0.f } };
293 float combined[3][3] = { { 0.f } };
294
296 if(!dt_iop_channelmixer_shared_get_matrix(rows, normalize, FALSE, mixer)) return 1;
297
298 _build_cat16_rgb_matrix(work_profile, p->temperature[point], CAT);
299 dt_iop_channelmixer_shared_mul3x3(mixer, CAT, combined);
300
301 memset(point_matrix, 0, sizeof(dt_colormatrix_t));
302 for(int row = 0; row < 3; row++)
303 for(int col = 0; col < 3; col++)
304 point_matrix[col][row] = combined[row][col];
305 return 0;
306}
307
321static inline __attribute__((always_inline)) void _interpolate_matrix(const dt_colormatrix_t from,
322 const dt_colormatrix_t to, const float alpha,
323 dt_colormatrix_t interpolated)
324{
325 for(int row = 0; row < 3; row++)
326 for(int col = 0; col < 3; col++)
327 interpolated[row][col] = from[row][col] + alpha * (to[row][col] - from[row][col]);
328
329 interpolated[0][3] = 0.f;
330 interpolated[1][3] = 0.f;
331 interpolated[2][3] = 0.f;
332}
333
346static inline __attribute__((always_inline)) void _get_split_matrix(const float luminance,
349{
350 dt_colormatrix_t identity = { { 1.f, 0.f, 0.f, 0.f }, { 0.f, 1.f, 0.f, 0.f }, { 0.f, 0.f, 1.f, 0.f } };
351 const float segment = fmaxf(d->bright_luminance - d->dark_luminance, NORM_MIN);
352
353 if(luminance <= d->dark_luminance)
354 {
355 const float alpha = CLAMP(1.f - (d->dark_luminance - fmaxf(luminance, 0.f)) / segment, 0.f, 1.f);
356 _interpolate_matrix(identity, d->point_matrix[DT_SPLITTONING_RGB_POINT_DARK], alpha, matrix);
357 return;
358 }
359
360 if(luminance >= d->bright_luminance)
361 {
362 const float alpha = CLAMP(1.f - (luminance - d->bright_luminance) / segment, 0.f, 1.f);
363 _interpolate_matrix(identity, d->point_matrix[DT_SPLITTONING_RGB_POINT_BRIGHT], alpha, matrix);
364 return;
365 }
366
367 const float alpha = CLAMP((luminance - d->dark_luminance) / segment, 0.f, 1.f);
368 _interpolate_matrix(d->point_matrix[DT_SPLITTONING_RGB_POINT_DARK],
369 d->point_matrix[DT_SPLITTONING_RGB_POINT_BRIGHT], alpha, matrix);
370}
371
372static gboolean _sync_simple_from_params(dt_iop_module_t *self, const int point, float *error)
373{
376 GtkWidget *const widgets[6]
377 = { g->point[point].simple_theta, g->point[point].simple_psi, g->point[point].simple_stretch_1,
378 g->point[point].simple_stretch_2, g->point[point].simple_coupling_1,
379 g->point[point].simple_coupling_2 };
380 float rows[3][3] = { { 0.f } };
381 gboolean normalize[3] = { FALSE, FALSE, FALSE };
382 float M[3][3] = { { 0.f } };
383 float roundtrip[3][3] = { { 0.f } };
385
386 if(!IS_NULL_PTR(error)) *error = INFINITY;
387
391
394
395 const float roundtrip_error = dt_iop_channelmixer_shared_roundtrip_error(M, roundtrip);
396 if(!IS_NULL_PTR(error)) *error = roundtrip_error;
397
401
402 return isfinite(roundtrip_error) && roundtrip_error <= DT_IOP_CHANNELMIXER_SHARED_SIMPLE_EPS;
403}
404
405static gboolean _sync_primaries_from_params(dt_iop_module_t *self, const int point, float *error)
406{
409 GtkWidget *const widgets[9]
410 = { g->point[point].primaries_achromatic_hue, g->point[point].primaries_achromatic_purity,
411 g->point[point].primaries_red_hue, g->point[point].primaries_red_purity,
412 g->point[point].primaries_green_hue, g->point[point].primaries_green_purity,
413 g->point[point].primaries_blue_hue, g->point[point].primaries_blue_purity,
414 g->point[point].primaries_gain };
415 float rows[3][3] = { { 0.f } };
416 gboolean normalize[3] = { FALSE, FALSE, FALSE };
417 float M[3][3] = { { 0.f } };
418 float roundtrip[3][3] = { { 0.f } };
420
421 if(!IS_NULL_PTR(error)) *error = INFINITY;
422
426 &primaries))
427 return FALSE;
429 roundtrip))
430 return FALSE;
431
432 const float roundtrip_error = dt_iop_channelmixer_shared_roundtrip_error(M, roundtrip);
433 if(!IS_NULL_PTR(error)) *error = roundtrip_error;
434
438
439 return isfinite(roundtrip_error) && roundtrip_error <= DT_IOP_CHANNELMIXER_SHARED_SIMPLE_EPS;
440}
441
443{
445 if(g && g->preview) gtk_widget_queue_draw(g->preview);
446}
447
459static void _pipe_finished_callback(gpointer instance, gpointer user_data)
460{
461 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
463
464 if(IS_NULL_PTR(g)) return;
465
467
468 if(g->preview_surface)
469 {
470 cairo_surface_destroy(g->preview_surface);
471 g->preview_surface = NULL;
472 }
473 g->preview_width = 0;
474 g->preview_height = 0;
476}
477
490{
493 const dt_iop_order_iccprofile_info_t *work_profile = self->dev && self->dev->pipe
495 : NULL;
496 const dt_iop_order_iccprofile_info_t *display_profile = self->dev && self->dev->pipe
498 : NULL;
499 const float *const rows[3] = { p->red[point], p->green[point], p->blue[point] };
500 const float basis[3][3] = { { 1.f, 0.f, 0.f }, { 0.f, 1.f, 0.f }, { 0.f, 0.f, 1.f } };
503 GtkWidget *const simple_widgets[6]
504 = { g->point[point].simple_theta, g->point[point].simple_psi, g->point[point].simple_stretch_1,
505 g->point[point].simple_stretch_2, g->point[point].simple_coupling_1, g->point[point].simple_coupling_2 };
506 GtkWidget *const primaries_widgets[9]
507 = { g->point[point].primaries_achromatic_hue, g->point[point].primaries_achromatic_purity,
508 g->point[point].primaries_red_hue, g->point[point].primaries_red_purity,
509 g->point[point].primaries_green_hue, g->point[point].primaries_green_purity,
510 g->point[point].primaries_blue_hue, g->point[point].primaries_blue_purity,
511 g->point[point].primaries_gain };
512
513 dt_iop_channelmixer_shared_paint_temperature_slider(g->point[point].temperature, 1667.f, 25000.f);
514
515 for(int row = 0; row < 3; row++)
516 {
517 GtkWidget *const complete_widgets[3]
518 = { g->point[point].complete[row][0], g->point[point].complete[row][1], g->point[point].complete[row][2] };
519 dt_iop_channelmixer_shared_paint_row_sliders(DT_ADAPTATION_RGB, work_profile, display_profile, basis[row][0],
520 basis[row][1], basis[row][2], p->normalize[point][row], rows[row],
521 complete_widgets);
522 }
523
524 dt_iop_channelmixer_shared_simple_from_sliders(simple_widgets, &simple);
525 dt_iop_channelmixer_shared_paint_simple_sliders(DT_ADAPTATION_RGB, work_profile, display_profile, &simple,
526 simple_widgets);
527
528 dt_iop_channelmixer_shared_primaries_from_sliders(primaries_widgets, &primaries);
531 primaries_widgets);
532}
533
534static void _update_point_gui(dt_iop_module_t *self, const int point, GtkWidget *changed)
535{
538
539 const dt_iop_splittoning_rgb_mixer_mode_t active_mode = dt_bauhaus_combobox_get(g->point[point].mixer_mode);
540 gboolean complete_widget = FALSE;
541
542 for(int row = 0; row < 3; row++)
543 {
544 complete_widget = complete_widget || changed == g->point[point].normalize[row];
545 for(int col = 0; col < 3; col++) complete_widget = complete_widget || changed == g->point[point].complete[row][col];
546 }
547
548 if(IS_NULL_PTR(changed) || complete_widget)
549 {
550 float simple_error = INFINITY;
551 float primaries_error = INFINITY;
552 const gboolean simple_ok = _sync_simple_from_params(self, point, &simple_error);
553 const gboolean primaries_ok = _sync_primaries_from_params(self, point, &primaries_error);
554
555 if(active_mode == DT_SPLITTONING_RGB_MIXER_SIMPLE && !simple_ok)
556 {
562 dt_control_log(_("simple mixer mode requires normalized rows with non-zero sums."));
563 }
564 else if(active_mode == DT_SPLITTONING_RGB_MIXER_PRIMARIES && !primaries_ok)
565 {
571 dt_control_log(_("primaries mixer mode requires a non-singular 3x3 matrix with non-zero affine sums."));
572 }
573 }
574
579}
580
581static void _commit_gui_change(dt_iop_module_t *self, GtkWidget *changed)
582{
584 const int point = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(changed), "split-point"));
585
587 if(g->preview_surface)
588 {
589 cairo_surface_destroy(g->preview_surface);
590 g->preview_surface = NULL;
591 }
592 g->preview_width = 0;
593 g->preview_height = 0;
596}
597
609static void _render_preview_surface(dt_iop_module_t *self, cairo_surface_t *surface)
610{
611 if(IS_NULL_PTR(self->dev) || IS_NULL_PTR(self->dev->preview_pipe)) return;
612
616 if(IS_NULL_PTR(work_profile) || IS_NULL_PTR(display_profile)) return;
617
619 cairo_t *cr = cairo_create(surface);
620 // logical dimensions (the surface is device-scaled by dt_cairo_image_surface_create);
621 // we draw in logical coordinates and Cairo maps them to device pixels.
622 const int width = dt_cairo_image_surface_get_width(surface);
623 const int height = dt_cairo_image_surface_get_height(surface);
624
627 for(int i = 0; i < 3; i++) state.point_matrix[DT_SPLITTONING_RGB_POINT_DARK][i][i] = 1.f;
630 for(int i = 0; i < 3; i++) state.point_matrix[DT_SPLITTONING_RGB_POINT_BRIGHT][i][i] = 1.f;
631
632 memset(state.rgb_to_xyz_transposed, 0, sizeof(dt_colormatrix_t));
633 if(!IS_NULL_PTR(work_profile))
634 memcpy(state.rgb_to_xyz_transposed, work_profile->matrix_in_transposed, sizeof(dt_colormatrix_t));
635 else
636 {
637 for(int i = 0; i < 3; i++) state.rgb_to_xyz_transposed[i][i] = 1.f;
638 }
639
642 if(state.bright_luminance <= state.dark_luminance)
643 state.bright_luminance = state.dark_luminance + fmaxf(state.dark_luminance * 0.01f, 1e-4f);
644 for(int x = 0; x < width; x++)
645 {
646 const float grey = width > 1 ? (float)x / (float)(width - 1) : 0.f;
647 dt_aligned_pixel_t input = { grey, grey, grey, 0.f };
648 dt_aligned_pixel_t XYZ = { 0.f };
649 dt_aligned_pixel_t output = { 0.f };
650 dt_aligned_pixel_t display = { 0.f };
651 dt_colormatrix_t combined_matrix = { { 0.f } };
652 const dt_aligned_pixel_simd_t input_v = dt_load_simd_aligned(input);
653
654 dt_apply_transposed_color_matrix(input, state.rgb_to_xyz_transposed, XYZ);
655 _get_split_matrix(fmaxf(XYZ[1], 0.f), &state, combined_matrix);
656
657 dt_store_simd_aligned(output, dt_mat3x4_mul_vec4(input_v,
658 dt_colormatrix_row_to_simd(combined_matrix, 0),
659 dt_colormatrix_row_to_simd(combined_matrix, 1),
660 dt_colormatrix_row_to_simd(combined_matrix, 2)));
661
662 dt_iop_channelmixer_shared_work_rgb_to_display(output, work_profile, display_profile, display);
663 cairo_set_source_rgb(cr, display[0], display[1], display[2]);
664 cairo_rectangle(cr, x, 0, 1, height);
665 cairo_fill(cr);
666 }
667
668 cairo_destroy(cr);
669 cairo_surface_mark_dirty(surface);
670}
671
672static gboolean _preview_draw(GtkWidget *widget, cairo_t *cr, gpointer user_data)
673{
674 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
676 GtkAllocation allocation;
677
678 gtk_widget_get_allocation(widget, &allocation);
679 if(allocation.width <= 0 || allocation.height <= 0) return TRUE;
680
681 if(IS_NULL_PTR(g->preview_surface) || g->preview_width != allocation.width || g->preview_height != allocation.height)
682 {
683 if(g->preview_surface) cairo_surface_destroy(g->preview_surface);
684 g->preview_surface = dt_cairo_image_surface_create(CAIRO_FORMAT_RGB24, allocation.width, allocation.height);
685 if(IS_NULL_PTR(g->preview_surface)) return FALSE;
686 g->preview_width = allocation.width;
687 g->preview_height = allocation.height;
688 _render_preview_surface(self, g->preview_surface);
689 }
690
691 cairo_set_source_surface(cr, g->preview_surface, 0., 0.);
692 cairo_paint(cr);
693
694 return TRUE;
695}
696
697static void _general_callback(GtkWidget *widget, gpointer user_data)
698{
699 if(darktable.gui->reset) return;
700
701 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
704 const int point = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "split-point"));
705 gboolean changed_complete = FALSE;
706
707 if(widget == g->point[point].ev)
708 p->ev[point] = dt_bauhaus_slider_get(widget);
709 else if(widget == g->point[point].temperature)
710 p->temperature[point] = dt_bauhaus_slider_get(widget);
711
712 for(int row = 0; row < 3; row++)
713 {
714 if(widget == g->point[point].normalize[row])
715 {
716 p->normalize[point][row] = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
717 changed_complete = TRUE;
718 }
719
720 for(int col = 0; col < 3; col++)
721 {
722 if(widget == g->point[point].complete[row][col])
723 {
724 float *target = row == 0 ? &p->red[point][col] : row == 1 ? &p->green[point][col] : &p->blue[point][col];
725 *target = dt_bauhaus_slider_get(widget);
726 changed_complete = TRUE;
727 }
728 }
729 }
730
731 if(changed_complete) _update_point_gui(self, point, widget);
732 _commit_gui_change(self, widget);
733}
734
735static void _mixer_mode_callback(GtkWidget *widget, gpointer user_data)
736{
737 if(darktable.gui->reset) return;
738
739 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
742 const int point = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "split-point"));
744
746
748 {
749 float error = INFINITY;
751 {
752 dt_control_log(_("simple mixer mode requires normalized rows with non-zero sums."));
758 return;
759 }
760 }
762 {
763 float error = INFINITY;
765 {
766 dt_control_log(_("primaries mixer mode requires a non-singular 3x3 matrix with non-zero affine sums."));
772 return;
773 }
774 }
775
777 for(int row = 0; row < 3; row++) p->normalize[point][row] = TRUE;
778
783 _commit_gui_change(self, widget);
784}
785
786static void _simple_slider_callback(GtkWidget *widget, gpointer user_data)
787{
788 if(darktable.gui->reset) return;
789
790 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
793 const int point = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "split-point"));
794 GtkWidget *const widgets[6]
795 = { g->point[point].simple_theta, g->point[point].simple_psi, g->point[point].simple_stretch_1,
796 g->point[point].simple_stretch_2, g->point[point].simple_coupling_1,
797 g->point[point].simple_coupling_2 };
799 float M[3][3] = { { 0.f } };
800
804
805 for(int row = 0; row < 3; row++) p->normalize[point][row] = TRUE;
806
811
812 _commit_gui_change(self, widget);
813}
814
815static void _primaries_slider_callback(GtkWidget *widget, gpointer user_data)
816{
817 if(darktable.gui->reset) return;
818
819 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
822 const int point = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "split-point"));
823 GtkWidget *const widgets[9]
824 = { g->point[point].primaries_achromatic_hue, g->point[point].primaries_achromatic_purity,
825 g->point[point].primaries_red_hue, g->point[point].primaries_red_purity,
826 g->point[point].primaries_green_hue, g->point[point].primaries_green_purity,
827 g->point[point].primaries_blue_hue, g->point[point].primaries_blue_purity,
828 g->point[point].primaries_gain };
830 float M[3][3] = { { 0.f } };
831
834 {
835 dt_control_log(_("primaries mixer mode requires a non-singular 3x3 matrix with non-zero affine sums."));
836 return;
837 }
838
840 for(int row = 0; row < 3; row++) p->normalize[point][row] = FALSE;
841
845
846 _commit_gui_change(self, widget);
847}
848
851{
855 dt_colormatrix_t point_matrix = { { 0.f } };
856 dt_colormatrix_t rgb_to_xyz = { { 0.f } };
857
859 d->point_matrix[DT_SPLITTONING_RGB_POINT_DARK]))
860 for(int i = 0; i < 3; i++) d->point_matrix[DT_SPLITTONING_RGB_POINT_DARK][i][i] = 1.f;
862 d->point_matrix[DT_SPLITTONING_RGB_POINT_BRIGHT]))
863 for(int i = 0; i < 3; i++) d->point_matrix[DT_SPLITTONING_RGB_POINT_BRIGHT][i][i] = 1.f;
864
865 memset(d->rgb_to_xyz_transposed, 0, sizeof(dt_colormatrix_t));
866 if(!IS_NULL_PTR(work_profile))
867 memcpy(d->rgb_to_xyz_transposed, work_profile->matrix_in_transposed, sizeof(dt_colormatrix_t));
868 else
869 {
870 for(int i = 0; i < 3; i++) d->rgb_to_xyz_transposed[i][i] = 1.f;
871 }
872
874 {
875 transpose_3xSSE(d->point_matrix[point], point_matrix);
876 pack_3xSSE_to_3x4(point_matrix, d->point_matrix_cl[point]);
877 }
878
879 transpose_3xSSE(d->rgb_to_xyz_transposed, rgb_to_xyz);
880 pack_3xSSE_to_3x4(rgb_to_xyz, d->rgb_to_xyz_cl);
881
882 d->dark_luminance = _ev_to_luminance(p->ev[DT_SPLITTONING_RGB_POINT_DARK]);
883 d->bright_luminance = _ev_to_luminance(p->ev[DT_SPLITTONING_RGB_POINT_BRIGHT]);
884 if(d->bright_luminance <= d->dark_luminance)
885 d->bright_luminance = d->dark_luminance + fmaxf(d->dark_luminance * 0.01f, 1e-4f);
886}
887
893
895{
896 dt_free_align(piece->data);
897 piece->data = NULL;
898}
899
901int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece,
902 const void *const restrict ivoid, void *const restrict ovoid)
903{
905 const float *const restrict in = (const float *const restrict)ivoid;
906 float *const restrict out = (float *const restrict)ovoid;
907 const size_t width = piece->roi_out.width;
908 const size_t height = piece->roi_out.height;
909 const size_t pixels = width * height;
911 for(size_t k = 0; k < pixels * 4; k += 4)
912 {
913 dt_aligned_pixel_t input = { in[k + 0], in[k + 1], in[k + 2], in[k + 3] };
914 dt_aligned_pixel_t XYZ = { 0.f };
915 dt_colormatrix_t combined_matrix = { { 0.f } };
916 const dt_aligned_pixel_simd_t input_v = dt_load_simd_aligned(input);
917
919 dt_mat3x4_mul_vec4(input_v, dt_colormatrix_row_to_simd(d->rgb_to_xyz_transposed, 0),
920 dt_colormatrix_row_to_simd(d->rgb_to_xyz_transposed, 1),
921 dt_colormatrix_row_to_simd(d->rgb_to_xyz_transposed, 2)));
922
923 _get_split_matrix(fmaxf(XYZ[1], 0.f), d, combined_matrix);
924
925 dt_store_simd_aligned(out + k, dt_mat3x4_mul_vec4(input_v,
926 dt_colormatrix_row_to_simd(combined_matrix, 0),
927 dt_colormatrix_row_to_simd(combined_matrix, 1),
928 dt_colormatrix_row_to_simd(combined_matrix, 2)));
929 out[k + 3] = input[3];
930 }
931
933 return 0;
934}
935
936#if HAVE_OPENCL
937int process_cl(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece,
938 cl_mem dev_in, cl_mem dev_out)
939{
940 const dt_iop_roi_t *const roi_in = &piece->roi_in;
943
944 cl_int err = -999;
945 const int devid = pipe->devid;
946 const int width = roi_in->width;
947 const int height = roi_in->height;
948 size_t sizes[] = { ROUNDUPDWD(width, devid), ROUNDUPDHT(height, devid), 1 };
949
950 cl_mem rgb_to_xyz_cl = NULL;
951 cl_mem dark_matrix_cl = NULL;
952 cl_mem bright_matrix_cl = NULL;
953
954 rgb_to_xyz_cl
955 = dt_opencl_copy_host_to_device_constant(devid, sizeof(d->rgb_to_xyz_cl), (void *)d->rgb_to_xyz_cl);
957 devid, sizeof(d->point_matrix_cl[DT_SPLITTONING_RGB_POINT_DARK]),
958 (void *)d->point_matrix_cl[DT_SPLITTONING_RGB_POINT_DARK]);
960 devid, sizeof(d->point_matrix_cl[DT_SPLITTONING_RGB_POINT_BRIGHT]),
961 (void *)d->point_matrix_cl[DT_SPLITTONING_RGB_POINT_BRIGHT]);
962
963 dt_opencl_set_kernel_arg(devid, gd->kernel_splittoningrgb, 0, sizeof(cl_mem), (void *)&dev_in);
964 dt_opencl_set_kernel_arg(devid, gd->kernel_splittoningrgb, 1, sizeof(cl_mem), (void *)&dev_out);
965 dt_opencl_set_kernel_arg(devid, gd->kernel_splittoningrgb, 2, sizeof(int), (void *)&width);
966 dt_opencl_set_kernel_arg(devid, gd->kernel_splittoningrgb, 3, sizeof(int), (void *)&height);
967 dt_opencl_set_kernel_arg(devid, gd->kernel_splittoningrgb, 4, sizeof(cl_mem), (void *)&rgb_to_xyz_cl);
968 dt_opencl_set_kernel_arg(devid, gd->kernel_splittoningrgb, 5, sizeof(cl_mem), (void *)&dark_matrix_cl);
969 dt_opencl_set_kernel_arg(devid, gd->kernel_splittoningrgb, 6, sizeof(cl_mem), (void *)&bright_matrix_cl);
970 dt_opencl_set_kernel_arg(devid, gd->kernel_splittoningrgb, 7, sizeof(float), (void *)&d->dark_luminance);
971 dt_opencl_set_kernel_arg(devid, gd->kernel_splittoningrgb, 8, sizeof(float), (void *)&d->bright_luminance);
972 err = dt_opencl_enqueue_kernel_2d(devid, gd->kernel_splittoningrgb, sizes);
973 if(err != CL_SUCCESS) goto error;
974
975 dt_opencl_release_mem_object(rgb_to_xyz_cl);
976 dt_opencl_release_mem_object(dark_matrix_cl);
977 dt_opencl_release_mem_object(bright_matrix_cl);
978 return TRUE;
979
980error:
981 dt_opencl_release_mem_object(rgb_to_xyz_cl);
982 dt_opencl_release_mem_object(dark_matrix_cl);
983 dt_opencl_release_mem_object(bright_matrix_cl);
984 dt_print(DT_DEBUG_OPENCL, "[opencl_splittoningrgb] couldn't enqueue kernel! %d\n", err);
985 return FALSE;
986}
987
989{
990 const int program = 32; // channelmixer.cl, from programs.conf
993
994 module->data = gd;
995 gd->kernel_splittoningrgb = dt_opencl_create_kernel(program, "splittoningrgb");
996}
997
1004#endif
1005
1019{
1020 if(darktable.gui->reset) return;
1021
1024 const dt_iop_module_t *const sampled_module = piece && piece->module ? piece->module : self;
1025 const dt_iop_order_iccprofile_info_t *const work_profile
1026 = pipe ? dt_ioppr_get_pipe_current_profile_info(self, pipe)
1028
1029 if(IS_NULL_PTR(g)) return;
1030 if(sampled_module->picked_color_max[0] < sampled_module->picked_color_min[0]) return;
1031
1032 const int point = picker == g->point[DT_SPLITTONING_RGB_POINT_DARK].ev
1034 : picker == g->point[DT_SPLITTONING_RGB_POINT_BRIGHT].ev
1036 : -1;
1037 if(point < 0) return;
1038
1039 const float luminance = work_profile
1040 ? dt_ioppr_get_rgb_matrix_luminance(sampled_module->picked_color, work_profile->matrix_in,
1041 work_profile->lut_in,
1042 work_profile->unbounded_coeffs_in,
1043 work_profile->lutsize, work_profile->nonlinearlut)
1044 : dt_camera_rgb_luminance(sampled_module->picked_color);
1045
1046 p->ev[point] = CLAMP(log2f(fmaxf(luminance, NORM_MIN)), -16.f, 16.f);
1047
1048 ++darktable.gui->reset;
1049 dt_bauhaus_slider_set(g->point[point].ev, p->ev[point]);
1050 --darktable.gui->reset;
1051
1052 _commit_gui_change(self, picker);
1053}
1054
1055void gui_update(struct dt_iop_module_t *self)
1056{
1059
1060 ++darktable.gui->reset;
1061
1063 {
1064 dt_bauhaus_slider_set(g->point[point].ev, p->ev[point]);
1065 dt_bauhaus_slider_set(g->point[point].temperature, p->temperature[point]);
1067
1068 float simple_error = INFINITY;
1069 float primaries_error = INFINITY;
1070 const gboolean simple_ok = _sync_simple_from_params(self, point, &simple_error);
1071 const gboolean primaries_ok = _sync_primaries_from_params(self, point, &primaries_error);
1076 = requested_mode == DT_SPLITTONING_RGB_MIXER_SIMPLE && simple_ok
1078 : requested_mode == DT_SPLITTONING_RGB_MIXER_PRIMARIES && primaries_ok
1081
1082 dt_bauhaus_combobox_set(g->point[point].mixer_mode, mode);
1085 }
1086
1087 --darktable.gui->reset;
1088 if(g->preview_surface)
1089 {
1090 cairo_surface_destroy(g->preview_surface);
1091 g->preview_surface = NULL;
1092 }
1093 g->preview_width = 0;
1094 g->preview_height = 0;
1096}
1097
1098static void _tag_widget(GtkWidget *widget, const int point)
1099{
1100 g_object_set_data(G_OBJECT(widget), "split-point", GINT_TO_POINTER(point));
1101}
1102
1105{
1106 static const char *const row_label[3] = { N_("output red"), N_("output green"), N_("output blue") };
1107 static const char *const input_label[3] = { N_("input R"), N_("input G"), N_("input B") };
1108
1109 for(int row = 0; row < 3; row++)
1110 {
1111 gtk_box_pack_start(GTK_BOX(container), dt_ui_section_label_new(_(row_label[row])), FALSE, FALSE, 0);
1112
1113 // Each row scans the three input channels explicitly because the sliders own the output basis.
1114 for(int col = 0; col < 3; col++)
1115 {
1116 g->point[point].complete[row][col]
1117 = dt_bauhaus_slider_new_with_range(darktable.bauhaus, DT_GUI_MODULE(self), -2.f, 2.f, 0, row == col, 3);
1118 dt_bauhaus_widget_set_label(g->point[point].complete[row][col], input_label[col]);
1119 _tag_widget(g->point[point].complete[row][col], point);
1120 g_signal_connect(G_OBJECT(g->point[point].complete[row][col]), "value-changed", G_CALLBACK(_general_callback),
1121 self);
1122 gtk_box_pack_start(GTK_BOX(container), g->point[point].complete[row][col], FALSE, FALSE, 0);
1123 }
1124
1125 g->point[point].normalize[row] = gtk_check_button_new_with_label(_("normalize"));
1126 _tag_widget(g->point[point].normalize[row], point);
1127 g_signal_connect(G_OBJECT(g->point[point].normalize[row]), "toggled", G_CALLBACK(_general_callback), self);
1128 gtk_box_pack_start(GTK_BOX(container), g->point[point].normalize[row], FALSE, FALSE, 0);
1129 }
1130}
1131
1134{
1135 g->point[point].simple_theta
1136 = dt_bauhaus_slider_new_with_range(darktable.bauhaus, DT_GUI_MODULE(self), -1.f, 1.f, 0, 0.f, 3);
1137 dt_bauhaus_widget_set_label(g->point[point].simple_theta, N_("global hue rotation"));
1138 dt_bauhaus_slider_set_factor(g->point[point].simple_theta, 180.f);
1139 dt_bauhaus_slider_set_format(g->point[point].simple_theta, "\302\260");
1140
1141 g->point[point].simple_psi
1142 = dt_bauhaus_slider_new_with_range(darktable.bauhaus, DT_GUI_MODULE(self), -1.f, 1.f, 0, 0.f, 3);
1143 dt_bauhaus_widget_set_label(g->point[point].simple_psi, N_("chroma (u,v) axes orientation"));
1144 dt_bauhaus_slider_set_factor(g->point[point].simple_psi, 90.f);
1145 dt_bauhaus_slider_set_format(g->point[point].simple_psi, "\302\260");
1146
1147 g->point[point].simple_stretch_1
1148 = dt_bauhaus_slider_new_with_range(darktable.bauhaus, DT_GUI_MODULE(self), -1.5f, 1.5f, 0, 1.f, 3);
1149 dt_bauhaus_widget_set_label(g->point[point].simple_stretch_1, N_("u stretch"));
1150
1151 g->point[point].simple_stretch_2
1152 = dt_bauhaus_slider_new_with_range(darktable.bauhaus, DT_GUI_MODULE(self), -1.5f, 1.5f, 0, 1.f, 3);
1153 dt_bauhaus_widget_set_label(g->point[point].simple_stretch_2, N_("v stretch"));
1154
1155 g->point[point].simple_coupling_2
1156 = dt_bauhaus_slider_new_with_range(darktable.bauhaus, DT_GUI_MODULE(self), -1.f, 1.f, 0, 0.f, 3);
1157 dt_bauhaus_widget_set_label(g->point[point].simple_coupling_2, N_("achromatic coupling hue"));
1158 dt_bauhaus_slider_set_factor(g->point[point].simple_coupling_2, 180.f);
1159 dt_bauhaus_slider_set_format(g->point[point].simple_coupling_2, "\302\260");
1160
1161 g->point[point].simple_coupling_1
1162 = dt_bauhaus_slider_new_with_range(darktable.bauhaus, DT_GUI_MODULE(self), 0.f, 1.f, 0, 0.f, 3);
1163 dt_bauhaus_widget_set_label(g->point[point].simple_coupling_1, N_("achromatic coupling amount"));
1164
1165 GtkWidget *widgets[] = {
1166 g->point[point].simple_theta,
1167 dt_ui_section_label_new(_("chroma")),
1168 g->point[point].simple_psi,
1169 g->point[point].simple_stretch_1,
1170 g->point[point].simple_stretch_2,
1171 dt_ui_section_label_new(_("achromatic coupling")),
1172 g->point[point].simple_coupling_2,
1173 g->point[point].simple_coupling_1,
1174 };
1175
1176 for(size_t i = 0; i < G_N_ELEMENTS(widgets); i++)
1177 {
1178 if(i != 1 && i != 5)
1179 {
1180 _tag_widget(widgets[i], point);
1181 g_signal_connect(G_OBJECT(widgets[i]), "value-changed", G_CALLBACK(_simple_slider_callback), self);
1182 }
1183 gtk_box_pack_start(GTK_BOX(container), widgets[i], FALSE, FALSE, 0);
1184 }
1185}
1186
1189{
1190 g->point[point].primaries_achromatic_hue
1191 = dt_bauhaus_slider_new_with_range(darktable.bauhaus, DT_GUI_MODULE(self), -2.f, 2.f, 0, 0.f, 3);
1192 dt_bauhaus_widget_set_label(g->point[point].primaries_achromatic_hue, N_("white hue"));
1193 dt_bauhaus_slider_set_factor(g->point[point].primaries_achromatic_hue, 90.f);
1194 dt_bauhaus_slider_set_format(g->point[point].primaries_achromatic_hue, "\302\260");
1195
1196 g->point[point].primaries_achromatic_purity
1197 = dt_bauhaus_slider_new_with_range(darktable.bauhaus, DT_GUI_MODULE(self), 0.f, 2.f, 0, 0.f, 3);
1198 dt_bauhaus_widget_set_label(g->point[point].primaries_achromatic_purity, N_("white purity"));
1199
1200 g->point[point].primaries_red_hue
1201 = dt_bauhaus_slider_new_with_range(darktable.bauhaus, DT_GUI_MODULE(self), -1.f, 1.f, 0, 0.f, 3);
1202 dt_bauhaus_widget_set_label(g->point[point].primaries_red_hue, N_("red hue"));
1203 dt_bauhaus_slider_set_factor(g->point[point].primaries_red_hue, 90.f);
1204 dt_bauhaus_slider_set_format(g->point[point].primaries_red_hue, "\302\260");
1205
1206 g->point[point].primaries_red_purity
1207 = dt_bauhaus_slider_new_with_range(darktable.bauhaus, DT_GUI_MODULE(self), 0.f, 2.f, 0, 1.f, 3);
1208 dt_bauhaus_widget_set_label(g->point[point].primaries_red_purity, N_("red purity"));
1209
1210 g->point[point].primaries_green_hue
1211 = dt_bauhaus_slider_new_with_range(darktable.bauhaus, DT_GUI_MODULE(self), -1.f, 1.f, 0, 0.f, 3);
1212 dt_bauhaus_widget_set_label(g->point[point].primaries_green_hue, N_("green hue"));
1213 dt_bauhaus_slider_set_factor(g->point[point].primaries_green_hue, 90.f);
1214 dt_bauhaus_slider_set_format(g->point[point].primaries_green_hue, "\302\260");
1215
1216 g->point[point].primaries_green_purity
1217 = dt_bauhaus_slider_new_with_range(darktable.bauhaus, DT_GUI_MODULE(self), 0.f, 2.f, 0, 1.f, 3);
1218 dt_bauhaus_widget_set_label(g->point[point].primaries_green_purity, N_("green purity"));
1219
1220 g->point[point].primaries_blue_hue
1221 = dt_bauhaus_slider_new_with_range(darktable.bauhaus, DT_GUI_MODULE(self), -1.f, 1.f, 0, 0.f, 3);
1222 dt_bauhaus_widget_set_label(g->point[point].primaries_blue_hue, N_("blue hue"));
1223 dt_bauhaus_slider_set_factor(g->point[point].primaries_blue_hue, 90.f);
1224 dt_bauhaus_slider_set_format(g->point[point].primaries_blue_hue, "\302\260");
1225
1226 g->point[point].primaries_blue_purity
1227 = dt_bauhaus_slider_new_with_range(darktable.bauhaus, DT_GUI_MODULE(self), 0.f, 2.f, 0, 1.f, 3);
1228 dt_bauhaus_widget_set_label(g->point[point].primaries_blue_purity, N_("blue purity"));
1229
1230 g->point[point].primaries_gain
1231 = dt_bauhaus_slider_new_with_range(darktable.bauhaus, DT_GUI_MODULE(self), -8.f, 8.f, 0, 1.f, 3);
1232 dt_bauhaus_widget_set_label(g->point[point].primaries_gain, N_("gain"));
1233
1234 GtkWidget *widgets[] = {
1235 dt_ui_section_label_new(_("achromatic axis")),
1236 g->point[point].primaries_achromatic_hue,
1237 g->point[point].primaries_achromatic_purity,
1238 dt_ui_section_label_new(_("red primary")),
1239 g->point[point].primaries_red_hue,
1240 g->point[point].primaries_red_purity,
1241 dt_ui_section_label_new(_("green primary")),
1242 g->point[point].primaries_green_hue,
1243 g->point[point].primaries_green_purity,
1244 dt_ui_section_label_new(_("blue primary")),
1245 g->point[point].primaries_blue_hue,
1246 g->point[point].primaries_blue_purity,
1247 dt_ui_section_label_new(_("gain correction")),
1248 g->point[point].primaries_gain,
1249 };
1250
1251 for(size_t i = 0; i < G_N_ELEMENTS(widgets); i++)
1252 {
1253 if(i % 3 != 0)
1254 {
1255 _tag_widget(widgets[i], point);
1256 g_signal_connect(G_OBJECT(widgets[i]), "value-changed", G_CALLBACK(_primaries_slider_callback), self);
1257 }
1258 gtk_box_pack_start(GTK_BOX(container), widgets[i], FALSE, FALSE, 0);
1259 }
1260}
1261
1262void gui_init(struct dt_iop_module_t *self)
1263{
1265 self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1266 g->preview_surface = NULL;
1267 g->preview_width = 0;
1268 g->preview_height = 0;
1269
1270 g->preview = GTK_WIDGET(gtk_drawing_area_new());
1271 gtk_widget_set_size_request(g->preview, -1, DT_PIXEL_APPLY_DPI(DT_SPLITTONING_RGB_PREVIEW_HEIGHT));
1272 g_signal_connect(G_OBJECT(g->preview), "draw", G_CALLBACK(_preview_draw), self);
1273 gtk_box_pack_start(GTK_BOX(self->widget), g->preview, FALSE, FALSE, 0);
1274
1275 g->tabs = dt_ui_notebook_new();
1276 gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(g->tabs), FALSE, FALSE, 0);
1277
1279 {
1280 GtkWidget *page = dt_ui_notebook_page(g->tabs, _point_label[point], _(point == 0 ? "shadows" : "highlights"));
1281 g->point[point].page = page;
1282
1283 g->point[point].ev = dt_color_picker_new(
1286 point == 0 ? -16.f : 0.f, 2));
1287 dt_bauhaus_widget_set_label(g->point[point].ev, N_("brightness"));
1288 dt_bauhaus_slider_set_format(g->point[point].ev, " EV");
1289 gtk_widget_set_tooltip_text(g->point[point].ev, _("sample average luminance from an area to set this keyframe"));
1290 _tag_widget(g->point[point].ev, point);
1291 g_signal_connect(G_OBJECT(g->point[point].ev), "value-changed", G_CALLBACK(_general_callback), self);
1292 gtk_box_pack_start(GTK_BOX(page), g->point[point].ev, FALSE, FALSE, 0);
1293
1294 g->point[point].temperature
1295 = dt_bauhaus_slider_new_with_range(darktable.bauhaus, DT_GUI_MODULE(self), 1667.f, 25000.f, 0, 5003.f, 0);
1296 dt_bauhaus_widget_set_label(g->point[point].temperature, N_("temperature"));
1297 dt_bauhaus_slider_set_soft_range(g->point[point].temperature, 3000.f, 7000.f);
1298 dt_bauhaus_slider_set_format(g->point[point].temperature, " K");
1299 _tag_widget(g->point[point].temperature, point);
1300 g_signal_connect(G_OBJECT(g->point[point].temperature), "value-changed", G_CALLBACK(_general_callback), self);
1301 gtk_box_pack_start(GTK_BOX(page), g->point[point].temperature, FALSE, FALSE, 0);
1302
1303 g->point[point].mixer_mode = dt_bauhaus_combobox_new(darktable.bauhaus, DT_GUI_MODULE(self));
1304 dt_bauhaus_widget_set_label(g->point[point].mixer_mode, N_("mode"));
1305 dt_bauhaus_combobox_add(g->point[point].mixer_mode, _("Complete"));
1306 dt_bauhaus_combobox_add(g->point[point].mixer_mode, _("Simple"));
1307 dt_bauhaus_combobox_add(g->point[point].mixer_mode, _("Primaries"));
1308 _tag_widget(g->point[point].mixer_mode, point);
1309 g_signal_connect(G_OBJECT(g->point[point].mixer_mode), "value-changed", G_CALLBACK(_mixer_mode_callback), self);
1310 gtk_box_pack_start(GTK_BOX(page), g->point[point].mixer_mode, FALSE, FALSE, 0);
1311
1312 g->point[point].mixer_stack = gtk_stack_new();
1313 gtk_stack_set_hhomogeneous(GTK_STACK(g->point[point].mixer_stack), FALSE);
1314 gtk_stack_set_vhomogeneous(GTK_STACK(g->point[point].mixer_stack), FALSE);
1315 gtk_stack_set_transition_type(GTK_STACK(g->point[point].mixer_stack), GTK_STACK_TRANSITION_TYPE_NONE);
1316 gtk_box_pack_start(GTK_BOX(page), g->point[point].mixer_stack, FALSE, FALSE, 0);
1317
1318 GtkWidget *complete = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1319 GtkWidget *simple = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1320 GtkWidget *primaries = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1321 gtk_stack_add_named(GTK_STACK(g->point[point].mixer_stack), complete, "complete");
1322 gtk_stack_add_named(GTK_STACK(g->point[point].mixer_stack), simple, "simple");
1323 gtk_stack_add_named(GTK_STACK(g->point[point].mixer_stack), primaries, "primaries");
1324
1325 _build_complete_ui(self, g, point, complete);
1326 _build_simple_ui(self, g, point, simple);
1327 _build_primaries_ui(self, g, point, primaries);
1328 }
1329
1331 G_CALLBACK(_pipe_finished_callback), self);
1333 G_CALLBACK(_pipe_finished_callback), self);
1334
1335 gui_update(self);
1336}
1337
1339{
1342 if(g->preview_surface) cairo_surface_destroy(g->preview_surface);
1344}
1345
1347{
1348 dt_iop_default_init(module);
1349
1351 memset(d, 0, sizeof(*d));
1352
1353 d->ev[DT_SPLITTONING_RGB_POINT_DARK] = -16.f;
1355 d->temperature[DT_SPLITTONING_RGB_POINT_DARK] = 5003.f;
1356 d->temperature[DT_SPLITTONING_RGB_POINT_BRIGHT] = 5003.f;
1357
1359 for(int row = 0; row < 3; row++)
1360 {
1361 d->normalize[point][row] = TRUE;
1362 d->red[point][row] = row == 0 ? 1.f : 0.f;
1363 d->green[point][row] = row == 1 ? 1.f : 0.f;
1364 d->blue[point][row] = row == 2 ? 1.f : 0.f;
1365 }
1366
1367 module->default_enabled = FALSE;
1368}
static void error(char *msg)
Definition ashift_lsd.c:202
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
uint32_t container(dt_lib_module_t *self)
void dt_bauhaus_slider_set_soft_range(GtkWidget *widget, float soft_min, float soft_max)
Definition bauhaus.c:1647
float dt_bauhaus_slider_get(GtkWidget *widget)
Definition bauhaus.c:3483
int dt_bauhaus_combobox_get(GtkWidget *widget)
Definition bauhaus.c:2347
void dt_bauhaus_slider_set(GtkWidget *widget, float pos)
Definition bauhaus.c:3506
void dt_bauhaus_combobox_set(GtkWidget *widget, const int pos)
Definition bauhaus.c:2301
void dt_bauhaus_widget_set_label(GtkWidget *widget, const char *label)
Definition bauhaus.c:1653
GtkWidget * dt_bauhaus_slider_new_with_range(dt_bauhaus_t *bh, dt_gui_module_t *self, float min, float max, float step, float defval, int digits)
Definition bauhaus.c:1780
GtkWidget * dt_bauhaus_combobox_new(dt_bauhaus_t *bh, dt_gui_module_t *self)
Definition bauhaus.c:1842
void dt_bauhaus_slider_set_format(GtkWidget *widget, const char *format)
Definition bauhaus.c:3598
void dt_bauhaus_combobox_add(GtkWidget *widget, const char *text)
Definition bauhaus.c:2016
void dt_bauhaus_slider_set_factor(GtkWidget *widget, float factor)
Definition bauhaus.c:3611
int width
Definition bilateral.h:1
int height
Definition bilateral.h:1
static __DT_CLONE_TARGETS__ void normalize(float *const buffer, const size_t width, const size_t height, const float norm)
Definition blurs.c:347
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])
void dt_iop_channelmixer_shared_simple_from_sliders(GtkWidget *const widgets[6], dt_iop_channelmixer_shared_simple_params_t *simple)
void dt_iop_channelmixer_shared_paint_temperature_slider(GtkWidget *const widget, const float temperature_min, const float temperature_max)
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_to_matrix(const dt_iop_channelmixer_shared_simple_params_t *const simple, float M[3][3])
void dt_iop_channelmixer_shared_simple_to_sliders(const dt_iop_channelmixer_shared_simple_params_t *const simple, GtkWidget *const widgets[6])
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)
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)
void dt_iop_channelmixer_shared_simple_from_matrix(const float M[3][3], dt_iop_channelmixer_shared_simple_params_t *simple)
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_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])
void dt_iop_channelmixer_shared_primaries_from_sliders(GtkWidget *const widgets[9], dt_iop_channelmixer_shared_primaries_params_t *primaries)
#define DT_IOP_CHANNELMIXER_SHARED_SIMPLE_EPS
@ DT_IOP_CHANNELMIXER_SHARED_PRIMARIES_BASIS_RGB
@ DT_ADAPTATION_CAT16
@ DT_ADAPTATION_RGB
static const dt_aligned_pixel_simd_t const dt_adaptation_t const float p
static float dt_camera_rgb_luminance(const float4 rgb)
@ IOP_CS_RGB
GtkWidget * dt_color_picker_new(dt_iop_module_t *module, dt_iop_color_picker_kind_t kind, GtkWidget *w)
@ DT_COLOR_PICKER_AREA
dt_apply_transposed_color_matrix(XYZ, xyz_to_srgb_matrix_transposed, sRGB)
static dt_aligned_pixel_t XYZ
const dt_colormatrix_t dt_aligned_pixel_t out
dt_store_simd_aligned(out, dt_mat3x4_mul_vec4(vin, dt_colormatrix_row_to_simd(matrix, 0), dt_colormatrix_row_to_simd(matrix, 1), dt_colormatrix_row_to_simd(matrix, 2)))
static const int row
const dt_colormatrix_t matrix
static const dt_colormatrix_t M
int dt_conf_key_exists(const char *key)
void dt_conf_set_int(const char *name, int val)
int dt_conf_get_int(const char *name)
void dt_control_log(const char *msg,...)
Definition control.c:761
darktable_t darktable
Definition darktable.c:181
void dt_print(dt_debug_thread_t thread, const char *msg,...)
Definition darktable.c:1542
#define dt_free_align(ptr)
Definition darktable.h:481
static void * dt_calloc_align(size_t size)
Definition darktable.h:488
@ DT_DEBUG_OPENCL
Definition darktable.h:722
float dt_aligned_pixel_simd_t __attribute__((vector_size(16), aligned(16)))
Enable aggressive floating-point arithmetic optimizations, in denormals handling. Set through user pr...
Definition darktable.h:524
#define dt_free(ptr)
Definition darktable.h:456
#define DT_MODULE_INTROSPECTION(MODVER, PARAMSTYPE)
Definition darktable.h:151
#define __DT_CLONE_TARGETS__
Definition darktable.h:367
#define __OMP_PARALLEL_FOR__(...)
Definition darktable.h:258
#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 darktable.h:281
#define dt_dev_add_history_item(dev, module, enable, redraw)
void dt_iop_params_t
Definition dev_history.h:41
void default_input_format(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece, dt_iop_buffer_dsc_t *dsc)
Definition format.c:57
@ TYPE_FLOAT
Definition format.h:46
GtkWidget * dt_ui_notebook_page(GtkNotebook *notebook, const char *text, const char *tooltip)
Definition gtk.c:2259
GtkNotebook * dt_ui_notebook_new()
Definition gtk.c:2254
static cairo_surface_t * dt_cairo_image_surface_create(cairo_format_t format, int width, int height)
Definition gtk.h:316
static GtkWidget * dt_ui_section_label_new(const gchar *str)
Definition gtk.h:451
static int dt_cairo_image_surface_get_width(cairo_surface_t *surface)
Definition gtk.h:334
#define DT_GUI_BOX_SPACING
Definition gtk.h:109
#define DT_PIXEL_APPLY_DPI(value)
Definition gtk.h:90
static int dt_cairo_image_surface_get_height(cairo_surface_t *surface)
Definition gtk.h:338
#define DT_GUI_MODULE(x)
static void illuminant_xy_to_XYZ(const float x, const float y, dt_aligned_pixel_t XYZ)
static void CCT_to_xy_daylight(const float t, float *x, float *y)
static void CCT_to_xy_blackbody(const float t, float *x, float *y)
void dt_iop_default_init(dt_iop_module_t *module)
Definition imageop.c:316
const char ** dt_iop_set_description(dt_iop_module_t *module, const char *main_text, const char *purpose, const char *input, const char *process, const char *output)
Definition imageop.c:3141
#define dt_omploop_sfence()
Definition imageop.h:702
#define IOP_GUI_FREE
Definition imageop.h:602
@ IOP_FLAGS_INCLUDE_IN_STYLES
Definition imageop.h:166
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:167
@ IOP_FLAGS_ALLOW_TILING
Definition imageop.h:169
@ IOP_GROUP_COLOR
Definition imageop.h:139
#define IOP_GUI_ALLOC(module)
Definition imageop.h:599
void *const ovoid
dt_iop_order_iccprofile_info_t * dt_ioppr_get_pipe_output_profile_info(const struct dt_dev_pixelpipe_t *pipe)
dt_iop_order_iccprofile_info_t * dt_ioppr_get_pipe_work_profile_info(const struct dt_dev_pixelpipe_t *pipe)
dt_iop_order_iccprofile_info_t * dt_ioppr_get_pipe_current_profile_info(dt_iop_module_t *module, const struct dt_dev_pixelpipe_t *pipe)
dt_iop_order_iccprofile_info_t * dt_ioppr_get_iop_work_profile_info(struct dt_iop_module_t *module, GList *iop_list)
static const float x
float *const restrict luminance
float *const restrict const size_t k
#define NORM_MIN
Definition math.h:35
float DT_ALIGNED_ARRAY dt_colormatrix_t[4][4]
Definition matrices.h:33
static void transpose_3xSSE(const dt_colormatrix_t input, dt_colormatrix_t output)
Definition matrices.h:68
static void pack_3xSSE_to_3x4(const dt_colormatrix_t input, float output[12])
Definition matrices.h:149
float dt_aligned_pixel_t[4]
int dt_opencl_enqueue_kernel_2d(const int dev, const int kernel, const size_t *sizes)
Definition opencl.c:2136
int dt_opencl_create_kernel(const int prog, const char *name)
Definition opencl.c:2030
void * dt_opencl_copy_host_to_device_constant(const int devid, const size_t size, void *host)
Definition opencl.c:2332
void dt_opencl_free_kernel(const int kernel)
Definition opencl.c:2073
int dt_opencl_set_kernel_arg(const int dev, const int kernel, const int num, const size_t size, const void *arg)
Definition opencl.c:2127
void dt_opencl_release_mem_object(cl_mem mem)
Definition opencl.c:2383
#define ROUNDUPDHT(a, b)
Definition opencl.h:82
#define ROUNDUPDWD(a, b)
Definition opencl.h:81
#define DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(ctlsig, cb, user_data)
Definition signal.h:368
@ DT_SIGNAL_DEVELOP_PREVIEW_PIPE_FINISHED
This signal is raised when develop preview pipe process is finished no param, no returned value.
Definition signal.h:174
@ DT_SIGNAL_DEVELOP_UI_PIPE_FINISHED
This signal is raised when pipe is finished and the gui is attached no param, no returned value.
Definition signal.h:179
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
Definition signal.h:357
struct _GtkWidget GtkWidget
Definition splash.h:29
void init(dt_iop_module_t *module)
__DT_CLONE_TARGETS__ int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const restrict ivoid, void *const restrict ovoid)
static void _set_point_complete_widgets(dt_iop_splittoning_rgb_gui_data_t *g, const dt_iop_splittoning_rgb_params_t *p, const int point)
const char ** description(struct dt_iop_module_t *self)
static const char *const _mode_conf[2]
int default_group()
#define DT_SPLITTONING_RGB_PREVIEW_HEIGHT
static void _build_cat16_rgb_matrix(const dt_iop_order_iccprofile_info_t *work_profile, const float temperature, float CAT[3][3])
static void _set_point_rows(dt_iop_splittoning_rgb_params_t *p, const int point, const float M[3][3])
static void _queue_preview_redraw(dt_iop_module_t *self)
static gboolean _sync_simple_from_params(dt_iop_module_t *self, const int point, float *error)
static void _simple_slider_callback(GtkWidget *widget, gpointer user_data)
void commit_params(struct dt_iop_module_t *self, dt_iop_params_t *params, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
static void _primaries_slider_callback(GtkWidget *widget, gpointer user_data)
static void _pipe_finished_callback(gpointer instance, gpointer user_data)
Refresh cached GUI colors after the darkroom pipe has converged.
static void _mixer_mode_callback(GtkWidget *widget, gpointer user_data)
const char * aliases()
static void _general_callback(GtkWidget *widget, gpointer user_data)
void init_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
dt_iop_splittoning_rgb_point_t
@ DT_SPLITTONING_RGB_POINT_BRIGHT
@ DT_SPLITTONING_RGB_POINT_DARK
const char * name()
static void _update_point_slider_colors(dt_iop_module_t *self, int point)
Repaint every slider background for one split-toning keyframe.
static void _commit_gui_change(dt_iop_module_t *self, GtkWidget *changed)
static void _tag_widget(GtkWidget *widget, const int point)
void gui_update(struct dt_iop_module_t *self)
Refresh GUI controls from current params and configuration.
#define DT_SPLITTONING_RGB_ROW_COUNT
dt_iop_splittoning_rgb_mixer_mode_t
@ DT_SPLITTONING_RGB_MIXER_PRIMARIES
@ DT_SPLITTONING_RGB_MIXER_COMPLETE
@ DT_SPLITTONING_RGB_MIXER_SIMPLE
static gboolean _sync_primaries_from_params(dt_iop_module_t *self, const int point, float *error)
void gui_init(struct dt_iop_module_t *self)
static float _ev_to_grey(const float ev)
static float _ev_to_luminance(const float ev)
static int _build_point_transform(const dt_iop_splittoning_rgb_params_t *p, const int point, const dt_iop_order_iccprofile_info_t *work_profile, dt_colormatrix_t point_matrix)
#define DT_SPLITTONING_RGB_COMPLETE_COUNT
void cleanup_global(dt_iop_module_so_t *module)
static void _render_preview_surface(dt_iop_module_t *self, cairo_surface_t *surface)
Render the cached split-toning preview as a simple keyed gradient.
int default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece)
void input_format(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece, dt_iop_buffer_dsc_t *dsc)
static gboolean _preview_draw(GtkWidget *widget, cairo_t *cr, gpointer user_data)
int flags()
void gui_cleanup(struct dt_iop_module_t *self)
static void _temperature_to_xy(const float temperature, float *x, float *y)
static void _get_point_rows(const dt_iop_splittoning_rgb_params_t *p, const int point, float rows[3][3], gboolean normalize[3])
static void _build_complete_ui(dt_iop_module_t *self, dt_iop_splittoning_rgb_gui_data_t *g, const int point, GtkWidget *container)
void cleanup_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
void init_global(dt_iop_module_so_t *module)
static void _set_point_mixer_mode(dt_iop_splittoning_rgb_gui_data_t *g, const int point, const dt_iop_splittoning_rgb_mixer_mode_t mode)
int process_cl(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, cl_mem dev_in, cl_mem dev_out)
static void _update_point_gui(dt_iop_module_t *self, const int point, GtkWidget *changed)
static void _build_simple_ui(dt_iop_module_t *self, dt_iop_splittoning_rgb_gui_data_t *g, const int point, GtkWidget *container)
#define DT_SPLITTONING_RGB_POINT_COUNT
void color_picker_apply(dt_iop_module_t *self, GtkWidget *picker, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Set a brightness keyframe from the average luminance of a picked area.
static const char *const _point_label[2]
static void _build_primaries_ui(dt_iop_module_t *self, dt_iop_splittoning_rgb_gui_data_t *g, const int point, GtkWidget *container)
const float uint32_t state[4]
struct dt_gui_gtk_t * gui
Definition darktable.h:775
struct dt_control_signal_t * signals
Definition darktable.h:774
struct dt_bauhaus_t * bauhaus
Definition darktable.h:778
struct dt_develop_t * develop
Definition darktable.h:770
struct dt_iop_module_t *void * data
GList * iop
Definition develop.h:279
struct dt_dev_pixelpipe_t * preview_pipe
Definition develop.h:247
struct dt_dev_pixelpipe_t * pipe
Definition develop.h:247
int32_t reset
Definition gtk.h:172
unsigned int channels
Definition format.h:54
dt_iop_buffer_type_t datatype
Definition format.h:56
dt_iop_global_data_t * data
Definition imageop.h:233
dt_iop_params_t * default_params
Definition imageop.h:307
GtkWidget * widget
Definition imageop.h:337
struct dt_develop_t * dev
Definition imageop.h:296
dt_iop_gui_data_t * gui_data
Definition imageop.h:311
dt_iop_global_data_t * global_data
Definition imageop.h:314
dt_aligned_pixel_t picked_color_min
Definition imageop.h:272
dt_aligned_pixel_t picked_color_max
Definition imageop.h:272
dt_aligned_pixel_t picked_color
Definition imageop.h:272
dt_iop_params_t * params
Definition imageop.h:307
dt_colormatrix_t matrix_out_transposed
Definition iop_profile.h:66
dt_colormatrix_t matrix_in_transposed
Definition iop_profile.h:65
Region of interest passed through the pixelpipe.
Definition imageop.h:72
dt_colormatrix_t rgb_to_xyz_transposed
dt_colormatrix_t point_matrix[2]