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#include "common/conf.h"
22#endif
23#include "develop/imageop_gui.h"
24
25#include "widgets/bauhaus.h"
27#include "system/macros.h"
28#include "system/openmp.h"
30#include "system/mem_alloc.h"
31#include "system/simd.h"
32#include "common/logging.h"
34#include "develop/iop_profile.h"
35#include "pixel/illuminants.h"
36#include "math/matrices.h"
37#include "common/opencl.h"
39#include "develop/develop.h"
40#include "develop/imageop.h"
41#include "math/openmp_maths.h"
44#include "iop/iop_api.h"
45
46// The shared implementation is its own translation unit, compiled once and linked
47// into the single IOP object set. It used to be textually included here, once per
48// consuming module, because two shared objects could not each carry a copy of its
49// globals; with the modules linked statically there is exactly one link and one copy.
50
51#include <gtk/gtk.h>
52#include <math.h>
53#include <stdlib.h>
54#include <string.h>
55#include "widgets/label.h"
56#include "widgets/notebook.h"
57#include "gui/screen_metrics.h"
58#include "control/signal.h"
59
61
62#define DT_SPLITTONING_RGB_POINT_COUNT 2
63#define DT_SPLITTONING_RGB_ROW_COUNT 3
64#define DT_SPLITTONING_RGB_COMPLETE_COUNT 3
65#define DT_SPLITTONING_RGB_PREVIEW_HEIGHT 60
71
79
89
99
104
136
146
147static const char *const _mode_conf[DT_SPLITTONING_RGB_POINT_COUNT] = {
148 "plugins/darkroom/splittoningrgb/dark_mixer_mode",
149 "plugins/darkroom/splittoningrgb/bright_mixer_mode"
150};
151
152static const char *const _point_label[DT_SPLITTONING_RGB_POINT_COUNT] = {
153 N_("dark"),
154 N_("bright")
155};
156
158
159const char *name()
160{
161 return _("split-toning");
162}
163
164const char *aliases()
165{
166 return _("split toning|split tone RGB");
167}
168
169const char **description(struct dt_iop_module_t *self)
170{
171 return dt_iop_set_description(self,
172 _("blend two CAT16 plus RGB mixer corrections across brightness keyframes"),
173 _("creative or corrective"), _("linear, RGB, scene-referred"),
174 _("linear, RGB"), _("linear, RGB, scene-referred"));
175}
176
178{
179 return IOP_GROUP_COLOR;
180}
181
186
188{
189 return IOP_CS_RGB;
190}
191
194{
195 default_input_format(self, pipe, piece, dsc);
196 dsc->channels = 4;
197 dsc->datatype = TYPE_FLOAT;
198}
199
200static inline float _ev_to_grey(const float ev)
201{
202 return exp2f(ev);
203}
204
205static inline float _ev_to_luminance(const float ev)
206{
207 return _ev_to_grey(ev);
208}
209
210static void _temperature_to_xy(const float temperature, float *x, float *y)
211{
212 if(temperature > 4000.f)
213 CCT_to_xy_daylight(temperature, x, y);
214 else
215 CCT_to_xy_blackbody(temperature, x, y);
216}
217
218static void _get_point_rows(const dt_iop_splittoning_rgb_params_t *p, const int point, float rows[3][3],
219 gboolean normalize[3])
220{
221 for(int col = 0; col < 3; col++)
222 {
223 rows[0][col] = p->red[point][col];
224 rows[1][col] = p->green[point][col];
225 rows[2][col] = p->blue[point][col];
226 }
227
228 for(int row = 0; row < 3; row++) normalize[row] = p->normalize[point][row];
229}
230
231static void _set_point_rows(dt_iop_splittoning_rgb_params_t *p, const int point, const float M[3][3])
232{
233 for(int col = 0; col < 3; col++)
234 {
235 p->red[point][col] = M[0][col];
236 p->green[point][col] = M[1][col];
237 p->blue[point][col] = M[2][col];
238 }
239}
240
242 const int point)
243{
244 const float *const rows[3] = { p->red[point], p->green[point], p->blue[point] };
245
246 for(int row = 0; row < 3; row++)
247 {
248 for(int col = 0; col < 3; col++) dt_bauhaus_slider_set(g->point[point].complete[row][col], rows[row][col]);
249 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->point[point].normalize[row]), p->normalize[point][row]);
250 }
251}
252
255{
256 gtk_stack_set_visible_child_name(GTK_STACK(g->point[point].mixer_stack),
257 mode == DT_SPLITTONING_RGB_MIXER_SIMPLE ? "simple"
258 : mode == DT_SPLITTONING_RGB_MIXER_PRIMARIES ? "primaries"
259 : mode == DT_SPLITTONING_RGB_MIXER_WHITE_PRESERVING ? "white-preserving"
260 : "complete");
261 gtk_widget_queue_resize(g->point[point].mixer_stack);
262 gtk_widget_queue_resize(g->point[point].page);
263 gtk_widget_queue_resize(GTK_WIDGET(g->tabs));
264}
265
266static void _build_cat16_rgb_matrix(const dt_iop_order_iccprofile_info_t *work_profile, const float temperature,
267 float CAT[3][3])
268{
269 for(int row = 0; row < 3; row++)
270 for(int col = 0; col < 3; col++)
271 CAT[row][col] = row == col ? 1.f : 0.f;
272
273 if(IS_NULL_PTR(work_profile)) return;
274
275 float x = 0.f;
276 float y = 0.f;
277 dt_aligned_pixel_t illuminant_XYZ = { 0.f };
278 dt_aligned_pixel_t illuminant_LMS = { 0.f };
279
280 _temperature_to_xy(temperature, &x, &y);
281 illuminant_xy_to_XYZ(x, y, illuminant_XYZ);
282 dt_store_simd_aligned(illuminant_LMS,
283 convert_any_XYZ_to_LMS(dt_load_simd_aligned(illuminant_XYZ), DT_ADAPTATION_CAT16));
284
285 // Build the RGB adaptation by probing the three working-space basis vectors independently.
286 for(int col = 0; col < 3; col++)
287 {
288 dt_aligned_pixel_t RGB_in = { 0.f };
289 dt_aligned_pixel_t XYZ_in = { 0.f };
290 dt_aligned_pixel_t XYZ_out = { 0.f };
291 dt_aligned_pixel_t RGB_out = { 0.f };
292
293 RGB_in[col] = 1.f;
294 dt_apply_transposed_color_matrix(RGB_in, work_profile->matrix_in_transposed, XYZ_in);
295 dt_store_simd_aligned(XYZ_out,
296 chroma_adapt_pixel(dt_load_simd_aligned(XYZ_in), dt_load_simd_aligned(illuminant_LMS),
297 DT_ADAPTATION_CAT16, 1.f));
298 dt_apply_transposed_color_matrix(XYZ_out, work_profile->matrix_out_transposed, RGB_out);
299
300 for(int row = 0; row < 3; row++) CAT[row][col] = RGB_out[row];
301 }
302}
303
305 const dt_iop_order_iccprofile_info_t *work_profile, dt_colormatrix_t point_matrix)
306{
307 float rows[3][3] = { { 0.f } };
308 gboolean normalize[3] = { FALSE, FALSE, FALSE };
309 float mixer[3][3] = { { 0.f } };
310 float CAT[3][3] = { { 0.f } };
311 float combined[3][3] = { { 0.f } };
312
314 if(!dt_iop_channelmixer_shared_get_matrix(rows, normalize, FALSE, mixer)) return 1;
315
316 _build_cat16_rgb_matrix(work_profile, p->temperature[point], CAT);
317 dt_iop_channelmixer_shared_mul3x3(mixer, CAT, combined);
318
319 memset(point_matrix, 0, sizeof(dt_colormatrix_t));
320 for(int row = 0; row < 3; row++)
321 for(int col = 0; col < 3; col++)
322 point_matrix[col][row] = combined[row][col];
323 return 0;
324}
325
339static inline __attribute__((always_inline)) void _interpolate_matrix(const dt_colormatrix_t from,
340 const dt_colormatrix_t to, const float alpha,
341 dt_colormatrix_t interpolated)
342{
343 for(int row = 0; row < 3; row++)
344 for(int col = 0; col < 3; col++)
345 interpolated[row][col] = from[row][col] + alpha * (to[row][col] - from[row][col]);
346
347 interpolated[0][3] = 0.f;
348 interpolated[1][3] = 0.f;
349 interpolated[2][3] = 0.f;
350}
351
364static inline __attribute__((always_inline)) void _get_split_matrix(const float luminance,
367{
368 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 } };
369 const float segment = fmaxf(d->bright_luminance - d->dark_luminance, NORM_MIN);
370
371 if(luminance <= d->dark_luminance)
372 {
373 const float alpha = CLAMP(1.f - (d->dark_luminance - fmaxf(luminance, 0.f)) / segment, 0.f, 1.f);
374 _interpolate_matrix(identity, d->point_matrix[DT_SPLITTONING_RGB_POINT_DARK], alpha, matrix);
375 return;
376 }
377
378 if(luminance >= d->bright_luminance)
379 {
380 const float alpha = CLAMP(1.f - (luminance - d->bright_luminance) / segment, 0.f, 1.f);
381 _interpolate_matrix(identity, d->point_matrix[DT_SPLITTONING_RGB_POINT_BRIGHT], alpha, matrix);
382 return;
383 }
384
385 const float alpha = CLAMP((luminance - d->dark_luminance) / segment, 0.f, 1.f);
386 _interpolate_matrix(d->point_matrix[DT_SPLITTONING_RGB_POINT_DARK],
387 d->point_matrix[DT_SPLITTONING_RGB_POINT_BRIGHT], alpha, matrix);
388}
389
390static gboolean _sync_simple_from_params(dt_iop_module_t *self, const int point, float *error)
391{
394 GtkWidget *const widgets[6]
395 = { g->point[point].simple_theta, g->point[point].simple_psi, g->point[point].simple_stretch_1,
396 g->point[point].simple_stretch_2, g->point[point].simple_coupling_1,
397 g->point[point].simple_coupling_2 };
398 float rows[3][3] = { { 0.f } };
399 gboolean normalize[3] = { FALSE, FALSE, FALSE };
400 float M[3][3] = { { 0.f } };
401 float roundtrip[3][3] = { { 0.f } };
403
404 if(!IS_NULL_PTR(error)) *error = INFINITY;
405
409
412
413 const float roundtrip_error = dt_iop_channelmixer_shared_roundtrip_error(M, roundtrip);
414 if(!IS_NULL_PTR(error)) *error = roundtrip_error;
415
419
420 return isfinite(roundtrip_error) && roundtrip_error <= DT_IOP_CHANNELMIXER_SHARED_SIMPLE_EPS;
421}
422
423static gboolean _sync_primaries_from_params(dt_iop_module_t *self, const int point, float *error)
424{
427 GtkWidget *const widgets[9]
428 = { g->point[point].primaries_achromatic_hue, g->point[point].primaries_achromatic_purity,
429 g->point[point].primaries_red_hue, g->point[point].primaries_red_purity,
430 g->point[point].primaries_green_hue, g->point[point].primaries_green_purity,
431 g->point[point].primaries_blue_hue, g->point[point].primaries_blue_purity,
432 g->point[point].primaries_gain };
433 float rows[3][3] = { { 0.f } };
434 gboolean normalize[3] = { FALSE, FALSE, FALSE };
435 float M[3][3] = { { 0.f } };
436 float roundtrip[3][3] = { { 0.f } };
438
439 if(!IS_NULL_PTR(error)) *error = INFINITY;
440
444 &primaries))
445 return FALSE;
447 roundtrip))
448 return FALSE;
449
450 const float roundtrip_error = dt_iop_channelmixer_shared_roundtrip_error(M, roundtrip);
451 if(!IS_NULL_PTR(error)) *error = roundtrip_error;
452
456
457 return isfinite(roundtrip_error) && roundtrip_error <= DT_IOP_CHANNELMIXER_SHARED_SIMPLE_EPS;
458}
459
469 GtkWidget *widgets[6])
470{
471 widgets[0] = g->point[point].white_preserving_red_rotation;
472 widgets[1] = g->point[point].white_preserving_red_saturation;
473 widgets[2] = g->point[point].white_preserving_green_rotation;
474 widgets[3] = g->point[point].white_preserving_green_saturation;
475 widgets[4] = g->point[point].white_preserving_blue_rotation;
476 widgets[5] = g->point[point].white_preserving_blue_saturation;
477
478 for(int widget = 0; widget < 6; widget++)
479 if(IS_NULL_PTR(widgets[widget])) return FALSE;
480
481 return TRUE;
482}
483
492static gboolean _sync_white_preserving_from_params(dt_iop_module_t *self, const int point, float *error)
493{
496 GtkWidget *widgets[6] = { NULL };
497 float rows[3][3] = { { 0.f } };
498 gboolean normalize[3] = { FALSE, FALSE, FALSE };
499 float M[3][3] = { { 0.f } };
500 float roundtrip[3][3] = { { 0.f } };
502
503 if(!IS_NULL_PTR(error)) *error = INFINITY;
504
505 if(!_white_preserving_widgets(g, point, widgets)) return FALSE;
506
510 &white_preserving))
511 return FALSE;
513 &white_preserving, roundtrip))
514 return FALSE;
515
516 const float roundtrip_error = dt_iop_channelmixer_shared_roundtrip_error_relative(M, roundtrip);
517 if(!IS_NULL_PTR(error)) *error = roundtrip_error;
518
522
523 return isfinite(roundtrip_error) && roundtrip_error <= DT_IOP_CHANNELMIXER_SHARED_SIMPLE_EPS;
524}
525
527{
529 if(g && g->preview) gtk_widget_queue_draw(g->preview);
530}
531
543static void _pipe_finished_callback(gpointer instance, gpointer user_data)
544{
545 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
547
548 if(IS_NULL_PTR(g)) return;
549
551
552 if(g->preview_surface)
553 {
554 cairo_surface_destroy(g->preview_surface);
555 g->preview_surface = NULL;
556 }
557 g->preview_width = 0;
558 g->preview_height = 0;
560}
561
574{
577 const dt_iop_order_iccprofile_info_t *work_profile = self->dev && self->dev->pipe
579 : NULL;
580 const dt_iop_order_iccprofile_info_t *display_profile = self->dev && self->dev->pipe
582 : NULL;
583 const float *const rows[3] = { p->red[point], p->green[point], p->blue[point] };
584 const float basis[3][3] = { { 1.f, 0.f, 0.f }, { 0.f, 1.f, 0.f }, { 0.f, 0.f, 1.f } };
587 GtkWidget *const simple_widgets[6]
588 = { g->point[point].simple_theta, g->point[point].simple_psi, g->point[point].simple_stretch_1,
589 g->point[point].simple_stretch_2, g->point[point].simple_coupling_1, g->point[point].simple_coupling_2 };
590 GtkWidget *const primaries_widgets[9]
591 = { g->point[point].primaries_achromatic_hue, g->point[point].primaries_achromatic_purity,
592 g->point[point].primaries_red_hue, g->point[point].primaries_red_purity,
593 g->point[point].primaries_green_hue, g->point[point].primaries_green_purity,
594 g->point[point].primaries_blue_hue, g->point[point].primaries_blue_purity,
595 g->point[point].primaries_gain };
596
597 dt_iop_channelmixer_shared_paint_temperature_slider(g->point[point].temperature, 1667.f, 25000.f);
598
599 for(int row = 0; row < 3; row++)
600 {
601 GtkWidget *const complete_widgets[3]
602 = { g->point[point].complete[row][0], g->point[point].complete[row][1], g->point[point].complete[row][2] };
603 dt_iop_channelmixer_shared_paint_row_sliders(DT_ADAPTATION_RGB, work_profile, display_profile, basis[row][0],
604 basis[row][1], basis[row][2], p->normalize[point][row], rows[row],
605 complete_widgets);
606 }
607
608 dt_iop_channelmixer_shared_simple_from_sliders(simple_widgets, &simple);
609 dt_iop_channelmixer_shared_paint_simple_sliders(DT_ADAPTATION_RGB, work_profile, display_profile, &simple,
610 simple_widgets);
611
612 dt_iop_channelmixer_shared_primaries_from_sliders(primaries_widgets, &primaries);
615 primaries_widgets);
616
617 GtkWidget *white_preserving_widgets[6] = { NULL };
618 if(_white_preserving_widgets(g, point, white_preserving_widgets))
619 {
621
622 dt_iop_channelmixer_shared_white_preserving_from_sliders(white_preserving_widgets, &white_preserving);
625 &white_preserving, white_preserving_widgets);
626 }
627}
628
629static void _update_point_gui(dt_iop_module_t *self, const int point, GtkWidget *changed)
630{
633
634 const dt_iop_splittoning_rgb_mixer_mode_t active_mode = dt_bauhaus_combobox_get(g->point[point].mixer_mode);
635 gboolean complete_widget = FALSE;
636
637 for(int row = 0; row < 3; row++)
638 {
639 complete_widget = complete_widget || changed == g->point[point].normalize[row];
640 for(int col = 0; col < 3; col++) complete_widget = complete_widget || changed == g->point[point].complete[row][col];
641 }
642
643 if(IS_NULL_PTR(changed) || complete_widget)
644 {
645 float simple_error = INFINITY;
646 float primaries_error = INFINITY;
647 float white_preserving_error = INFINITY;
648 const gboolean simple_ok = _sync_simple_from_params(self, point, &simple_error);
649 const gboolean primaries_ok = _sync_primaries_from_params(self, point, &primaries_error);
650 GtkWidget *white_preserving_widgets[6] = { NULL };
651 // Guard the kick-out below : a sync that only failed because gui_init() has not built this
652 // page yet must not push the user out of a mode they legitimately saved.
653 const gboolean white_preserving_ready = _white_preserving_widgets(g, point, white_preserving_widgets);
654 const gboolean white_preserving_ok = _sync_white_preserving_from_params(self, point, &white_preserving_error);
655
656 if(active_mode == DT_SPLITTONING_RGB_MIXER_SIMPLE && !simple_ok)
657 {
663 dt_control_log(_("simple mixer mode requires normalized rows with non-zero sums."));
664 }
665 else if(active_mode == DT_SPLITTONING_RGB_MIXER_PRIMARIES && !primaries_ok)
666 {
672 dt_control_log(_("primaries mixer mode requires a non-singular 3x3 matrix with non-zero affine sums."));
673 }
674 else if(active_mode == DT_SPLITTONING_RGB_MIXER_WHITE_PRESERVING && white_preserving_ready
675 && !white_preserving_ok)
676 {
682 dt_control_log(_("white-preserving mixer mode requires a matrix that leaves white unchanged."));
683 }
684 }
685
690}
691
692static void _commit_gui_change(dt_iop_module_t *self, GtkWidget *changed)
693{
695 const int point = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(changed), "split-point"));
696
698 if(g->preview_surface)
699 {
700 cairo_surface_destroy(g->preview_surface);
701 g->preview_surface = NULL;
702 }
703 g->preview_width = 0;
704 g->preview_height = 0;
706 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
707}
708
720static void _render_preview_surface(dt_iop_module_t *self, cairo_surface_t *surface)
721{
722 if(IS_NULL_PTR(self->dev) || IS_NULL_PTR(self->dev->preview_pipe)) return;
723
727 if(IS_NULL_PTR(work_profile) || IS_NULL_PTR(display_profile)) return;
728
730 cairo_t *cr = cairo_create(surface);
731 // logical dimensions (the surface is device-scaled by dt_cairo_image_surface_create);
732 // we draw in logical coordinates and Cairo maps them to device pixels.
733 const int width = dt_cairo_image_surface_get_width(surface);
734 const int height = dt_cairo_image_surface_get_height(surface);
735
738 for(int i = 0; i < 3; i++) state.point_matrix[DT_SPLITTONING_RGB_POINT_DARK][i][i] = 1.f;
741 for(int i = 0; i < 3; i++) state.point_matrix[DT_SPLITTONING_RGB_POINT_BRIGHT][i][i] = 1.f;
742
743 memset(state.rgb_to_xyz_transposed, 0, sizeof(dt_colormatrix_t));
744 if(!IS_NULL_PTR(work_profile))
745 memcpy(state.rgb_to_xyz_transposed, work_profile->matrix_in_transposed, sizeof(dt_colormatrix_t));
746 else
747 {
748 for(int i = 0; i < 3; i++) state.rgb_to_xyz_transposed[i][i] = 1.f;
749 }
750
753 if(state.bright_luminance <= state.dark_luminance)
754 state.bright_luminance = state.dark_luminance + fmaxf(state.dark_luminance * 0.01f, 1e-4f);
755 for(int x = 0; x < width; x++)
756 {
757 const float grey = width > 1 ? (float)x / (float)(width - 1) : 0.f;
758 dt_aligned_pixel_t input = { grey, grey, grey, 0.f };
759 dt_aligned_pixel_t XYZ = { 0.f };
760 dt_aligned_pixel_t output = { 0.f };
761 dt_aligned_pixel_t display = { 0.f };
762 dt_colormatrix_t combined_matrix = { { 0.f } };
763 const dt_aligned_pixel_simd_t input_v = dt_load_simd_aligned(input);
764
765 dt_apply_transposed_color_matrix(input, state.rgb_to_xyz_transposed, XYZ);
766 _get_split_matrix(fmaxf(XYZ[1], 0.f), &state, combined_matrix);
767
768 dt_store_simd_aligned(output, dt_mat3x4_mul_vec4(input_v,
769 dt_colormatrix_row_to_simd(combined_matrix, 0),
770 dt_colormatrix_row_to_simd(combined_matrix, 1),
771 dt_colormatrix_row_to_simd(combined_matrix, 2)));
772
773 dt_iop_channelmixer_shared_work_rgb_to_display(output, work_profile, display_profile, display);
774 cairo_set_source_rgb(cr, display[0], display[1], display[2]);
775 cairo_rectangle(cr, x, 0, 1, height);
776 cairo_fill(cr);
777 }
778
779 cairo_destroy(cr);
780 cairo_surface_mark_dirty(surface);
781}
782
783static gboolean _preview_draw(GtkWidget *widget, cairo_t *cr, gpointer user_data)
784{
785 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
787 GtkAllocation allocation;
788
789 gtk_widget_get_allocation(widget, &allocation);
790 if(allocation.width <= 0 || allocation.height <= 0) return TRUE;
791
792 if(IS_NULL_PTR(g->preview_surface) || g->preview_width != allocation.width || g->preview_height != allocation.height)
793 {
794 if(g->preview_surface) cairo_surface_destroy(g->preview_surface);
795 g->preview_surface = dt_cairo_image_surface_create(CAIRO_FORMAT_RGB24, allocation.width, allocation.height);
796 if(IS_NULL_PTR(g->preview_surface)) return FALSE;
797 g->preview_width = allocation.width;
798 g->preview_height = allocation.height;
799 _render_preview_surface(self, g->preview_surface);
800 }
801
802 cairo_set_source_surface(cr, g->preview_surface, 0., 0.);
803 cairo_paint(cr);
804
805 return TRUE;
806}
807
808static void _general_callback(GtkWidget *widget, gpointer user_data)
809{
810 if(dt_gui_widgets_suppressed()) return;
811
812 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
815 const int point = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "split-point"));
816 gboolean changed_complete = FALSE;
817
818 if(widget == g->point[point].ev)
819 p->ev[point] = dt_bauhaus_slider_get(widget);
820 else if(widget == g->point[point].temperature)
821 p->temperature[point] = dt_bauhaus_slider_get(widget);
822
823 for(int row = 0; row < 3; row++)
824 {
825 if(widget == g->point[point].normalize[row])
826 {
827 p->normalize[point][row] = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
828 changed_complete = TRUE;
829 }
830
831 for(int col = 0; col < 3; col++)
832 {
833 if(widget == g->point[point].complete[row][col])
834 {
835 float *target = row == 0 ? &p->red[point][col] : row == 1 ? &p->green[point][col] : &p->blue[point][col];
836 *target = dt_bauhaus_slider_get(widget);
837 changed_complete = TRUE;
838 }
839 }
840 }
841
842 if(changed_complete) _update_point_gui(self, point, widget);
843 _commit_gui_change(self, widget);
844}
845
846static void _mixer_mode_callback(GtkWidget *widget, gpointer user_data)
847{
848 if(dt_gui_widgets_suppressed()) return;
849
850 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
853 const int point = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "split-point"));
855
857
859 {
860 float error = INFINITY;
862 {
863 dt_control_log(_("simple mixer mode requires normalized rows with non-zero sums."));
869 return;
870 }
871 }
873 {
874 float error = INFINITY;
876 {
877 dt_control_log(_("primaries mixer mode requires a non-singular 3x3 matrix with non-zero affine sums."));
883 return;
884 }
885 }
887 {
888 float error = INFINITY;
890 {
891 dt_control_log(_("white-preserving mixer mode requires a matrix that leaves white unchanged."));
897 return;
898 }
899 }
900
902 for(int row = 0; row < 3; row++) p->normalize[point][row] = TRUE;
903
908 _commit_gui_change(self, widget);
909}
910
911static void _simple_slider_callback(GtkWidget *widget, gpointer user_data)
912{
913 if(dt_gui_widgets_suppressed()) return;
914
915 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
918 const int point = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "split-point"));
919 GtkWidget *const widgets[6]
920 = { g->point[point].simple_theta, g->point[point].simple_psi, g->point[point].simple_stretch_1,
921 g->point[point].simple_stretch_2, g->point[point].simple_coupling_1,
922 g->point[point].simple_coupling_2 };
924 float M[3][3] = { { 0.f } };
925
929
930 for(int row = 0; row < 3; row++) p->normalize[point][row] = TRUE;
931
937
938 _commit_gui_change(self, widget);
939}
940
941static void _primaries_slider_callback(GtkWidget *widget, gpointer user_data)
942{
943 if(dt_gui_widgets_suppressed()) return;
944
945 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
948 const int point = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "split-point"));
949 GtkWidget *const widgets[9]
950 = { g->point[point].primaries_achromatic_hue, g->point[point].primaries_achromatic_purity,
951 g->point[point].primaries_red_hue, g->point[point].primaries_red_purity,
952 g->point[point].primaries_green_hue, g->point[point].primaries_green_purity,
953 g->point[point].primaries_blue_hue, g->point[point].primaries_blue_purity,
954 g->point[point].primaries_gain };
956 float M[3][3] = { { 0.f } };
957
960 {
961 dt_control_log(_("primaries mixer mode requires a non-singular 3x3 matrix with non-zero affine sums."));
962 return;
963 }
964
966 for(int row = 0; row < 3; row++) p->normalize[point][row] = FALSE;
967
972
973 _commit_gui_change(self, widget);
974}
975
976static void _white_preserving_slider_callback(GtkWidget *widget, gpointer user_data)
977{
978 if(dt_gui_widgets_suppressed()) return;
979
980 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
983 const int point = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "split-point"));
984 GtkWidget *widgets[6] = { NULL };
986 float M[3][3] = { { 0.f } };
987
988 if(!_white_preserving_widgets(g, point, widgets)) return;
989
992 &white_preserving, M))
993 {
994 // Three primaries collapsed onto the neutral have no transform to describe. Snap the sliders
995 // back to the last representable state rather than leaving the page showing a setting the
996 // params do not hold.
997 dt_control_log(_("white-preserving mixer mode requires non-degenerate primaries."));
999 return;
1000 }
1001
1003 for(int row = 0; row < 3; row++) p->normalize[point][row] = FALSE;
1004
1009
1010 _commit_gui_change(self, widget);
1011}
1012
1015{
1019 dt_colormatrix_t point_matrix = { { 0.f } };
1020 dt_colormatrix_t rgb_to_xyz = { { 0.f } };
1021
1023 d->point_matrix[DT_SPLITTONING_RGB_POINT_DARK]))
1024 for(int i = 0; i < 3; i++) d->point_matrix[DT_SPLITTONING_RGB_POINT_DARK][i][i] = 1.f;
1026 d->point_matrix[DT_SPLITTONING_RGB_POINT_BRIGHT]))
1027 for(int i = 0; i < 3; i++) d->point_matrix[DT_SPLITTONING_RGB_POINT_BRIGHT][i][i] = 1.f;
1028
1029 memset(d->rgb_to_xyz_transposed, 0, sizeof(dt_colormatrix_t));
1030 if(!IS_NULL_PTR(work_profile))
1031 memcpy(d->rgb_to_xyz_transposed, work_profile->matrix_in_transposed, sizeof(dt_colormatrix_t));
1032 else
1033 {
1034 for(int i = 0; i < 3; i++) d->rgb_to_xyz_transposed[i][i] = 1.f;
1035 }
1036
1038 {
1039 transpose_3xSSE(d->point_matrix[point], point_matrix);
1040 pack_3xSSE_to_3x4(point_matrix, d->point_matrix_cl[point]);
1041 }
1042
1043 transpose_3xSSE(d->rgb_to_xyz_transposed, rgb_to_xyz);
1044 pack_3xSSE_to_3x4(rgb_to_xyz, d->rgb_to_xyz_cl);
1045
1046 d->dark_luminance = _ev_to_luminance(p->ev[DT_SPLITTONING_RGB_POINT_DARK]);
1047 d->bright_luminance = _ev_to_luminance(p->ev[DT_SPLITTONING_RGB_POINT_BRIGHT]);
1048 if(d->bright_luminance <= d->dark_luminance)
1049 d->bright_luminance = d->dark_luminance + fmaxf(d->dark_luminance * 0.01f, 1e-4f);
1050}
1051
1057
1059{
1060 dt_free_align(piece->data);
1061 piece->data = NULL;
1062}
1063
1065int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece,
1066 const void *const restrict ivoid, void *const restrict ovoid)
1067{
1069 const float *const restrict in = (const float *const restrict)ivoid;
1070 float *const restrict out = (float *const restrict)ovoid;
1071 const size_t width = piece->roi_out.width;
1072 const size_t height = piece->roi_out.height;
1073 const size_t pixels = width * height;
1075 for(size_t k = 0; k < pixels * 4; k += 4)
1076 {
1077 dt_aligned_pixel_t input = { in[k + 0], in[k + 1], in[k + 2], in[k + 3] };
1078 dt_aligned_pixel_t XYZ = { 0.f };
1079 dt_colormatrix_t combined_matrix = { { 0.f } };
1080 const dt_aligned_pixel_simd_t input_v = dt_load_simd_aligned(input);
1081
1083 dt_mat3x4_mul_vec4(input_v, dt_colormatrix_row_to_simd(d->rgb_to_xyz_transposed, 0),
1084 dt_colormatrix_row_to_simd(d->rgb_to_xyz_transposed, 1),
1085 dt_colormatrix_row_to_simd(d->rgb_to_xyz_transposed, 2)));
1086
1087 _get_split_matrix(fmaxf(XYZ[1], 0.f), d, combined_matrix);
1088
1089 dt_store_simd_aligned(out + k, dt_mat3x4_mul_vec4(input_v,
1090 dt_colormatrix_row_to_simd(combined_matrix, 0),
1091 dt_colormatrix_row_to_simd(combined_matrix, 1),
1092 dt_colormatrix_row_to_simd(combined_matrix, 2)));
1093 out[k + 3] = input[3];
1094 }
1095
1097 return 0;
1098}
1099
1100#if HAVE_OPENCL
1101int process_cl(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece,
1102 cl_mem dev_in, cl_mem dev_out)
1103{
1104 const dt_iop_roi_t *const roi_in = &piece->roi_in;
1107
1108 cl_int err = -999;
1109 const int devid = pipe->devid;
1110 const int width = roi_in->width;
1111 const int height = roi_in->height;
1112 size_t sizes[] = { ROUNDUPDWD(width, devid), ROUNDUPDHT(height, devid), 1 };
1113
1114 cl_mem rgb_to_xyz_cl = NULL;
1115 cl_mem dark_matrix_cl = NULL;
1116 cl_mem bright_matrix_cl = NULL;
1117
1118 rgb_to_xyz_cl
1119 = dt_opencl_copy_host_to_device_constant(devid, sizeof(d->rgb_to_xyz_cl), (void *)d->rgb_to_xyz_cl);
1121 devid, sizeof(d->point_matrix_cl[DT_SPLITTONING_RGB_POINT_DARK]),
1122 (void *)d->point_matrix_cl[DT_SPLITTONING_RGB_POINT_DARK]);
1123 bright_matrix_cl = dt_opencl_copy_host_to_device_constant(
1124 devid, sizeof(d->point_matrix_cl[DT_SPLITTONING_RGB_POINT_BRIGHT]),
1125 (void *)d->point_matrix_cl[DT_SPLITTONING_RGB_POINT_BRIGHT]);
1126
1127 dt_opencl_set_kernel_arg(devid, gd->kernel_splittoningrgb, 0, sizeof(cl_mem), (void *)&dev_in);
1128 dt_opencl_set_kernel_arg(devid, gd->kernel_splittoningrgb, 1, sizeof(cl_mem), (void *)&dev_out);
1129 dt_opencl_set_kernel_arg(devid, gd->kernel_splittoningrgb, 2, sizeof(int), (void *)&width);
1130 dt_opencl_set_kernel_arg(devid, gd->kernel_splittoningrgb, 3, sizeof(int), (void *)&height);
1131 dt_opencl_set_kernel_arg(devid, gd->kernel_splittoningrgb, 4, sizeof(cl_mem), (void *)&rgb_to_xyz_cl);
1132 dt_opencl_set_kernel_arg(devid, gd->kernel_splittoningrgb, 5, sizeof(cl_mem), (void *)&dark_matrix_cl);
1133 dt_opencl_set_kernel_arg(devid, gd->kernel_splittoningrgb, 6, sizeof(cl_mem), (void *)&bright_matrix_cl);
1134 dt_opencl_set_kernel_arg(devid, gd->kernel_splittoningrgb, 7, sizeof(float), (void *)&d->dark_luminance);
1135 dt_opencl_set_kernel_arg(devid, gd->kernel_splittoningrgb, 8, sizeof(float), (void *)&d->bright_luminance);
1136 err = dt_opencl_enqueue_kernel_2d(devid, gd->kernel_splittoningrgb, sizes);
1137 if(err != CL_SUCCESS) goto error;
1138
1139 dt_opencl_release_mem_object(rgb_to_xyz_cl);
1140 dt_opencl_release_mem_object(dark_matrix_cl);
1141 dt_opencl_release_mem_object(bright_matrix_cl);
1142 return TRUE;
1143
1144error:
1145 dt_opencl_release_mem_object(rgb_to_xyz_cl);
1146 dt_opencl_release_mem_object(dark_matrix_cl);
1147 dt_opencl_release_mem_object(bright_matrix_cl);
1148 dt_print(DT_DEBUG_OPENCL, "[opencl_splittoningrgb] couldn't enqueue kernel! %d\n", err);
1149 return FALSE;
1150}
1151
1153{
1154 const int program = 32; // channelmixer.cl, from programs.conf
1157
1158 module->data = gd;
1159 gd->kernel_splittoningrgb = dt_opencl_create_kernel(program, "splittoningrgb");
1160}
1161
1168#endif
1169
1183{
1184 if(dt_gui_widgets_suppressed()) return;
1185
1188 const dt_iop_module_t *const sampled_module = piece && piece->module ? piece->module : self;
1189 const dt_iop_order_iccprofile_info_t *const work_profile
1190 = pipe ? dt_ioppr_get_pipe_current_profile_info(self, pipe)
1192
1193 if(IS_NULL_PTR(g)) return;
1194 if(sampled_module->picked_color_max[0] < sampled_module->picked_color_min[0]) return;
1195
1196 const int point = picker == g->point[DT_SPLITTONING_RGB_POINT_DARK].ev
1198 : picker == g->point[DT_SPLITTONING_RGB_POINT_BRIGHT].ev
1200 : -1;
1201 if(point < 0) return;
1202
1203 const float luminance = work_profile
1204 ? dt_ioppr_get_rgb_matrix_luminance(sampled_module->picked_color, work_profile->matrix_in,
1205 work_profile->lut_in,
1206 work_profile->unbounded_coeffs_in,
1207 work_profile->lutsize, work_profile->nonlinearlut)
1208 : dt_camera_rgb_luminance(sampled_module->picked_color);
1209
1210 p->ev[point] = CLAMP(log2f(fmaxf(luminance, NORM_MIN)), -16.f, 16.f);
1211
1213 dt_bauhaus_slider_set(g->point[point].ev, p->ev[point]);
1215
1216 _commit_gui_change(self, picker);
1217}
1218
1219void gui_update(struct dt_iop_module_t *self)
1220{
1223
1225
1227 {
1228 dt_bauhaus_slider_set(g->point[point].ev, p->ev[point]);
1229 dt_bauhaus_slider_set(g->point[point].temperature, p->temperature[point]);
1231
1232 float simple_error = INFINITY;
1233 float primaries_error = INFINITY;
1234 float white_preserving_error = INFINITY;
1235 const gboolean simple_ok = _sync_simple_from_params(self, point, &simple_error);
1236 const gboolean primaries_ok = _sync_primaries_from_params(self, point, &primaries_error);
1237 const gboolean white_preserving_ok = _sync_white_preserving_from_params(self, point, &white_preserving_error);
1242 = requested_mode == DT_SPLITTONING_RGB_MIXER_SIMPLE && simple_ok
1244 : requested_mode == DT_SPLITTONING_RGB_MIXER_PRIMARIES && primaries_ok
1246 : requested_mode == DT_SPLITTONING_RGB_MIXER_WHITE_PRESERVING && white_preserving_ok
1249
1250 dt_bauhaus_combobox_set(g->point[point].mixer_mode, mode);
1253 }
1254
1256 if(g->preview_surface)
1257 {
1258 cairo_surface_destroy(g->preview_surface);
1259 g->preview_surface = NULL;
1260 }
1261 g->preview_width = 0;
1262 g->preview_height = 0;
1264}
1265
1266static void _tag_widget(GtkWidget *widget, const int point)
1267{
1268 g_object_set_data(G_OBJECT(widget), "split-point", GINT_TO_POINTER(point));
1269}
1270
1273{
1274 static const char *const row_label[3] = { N_("output red"), N_("output green"), N_("output blue") };
1275 static const char *const input_label[3] = { N_("input R"), N_("input G"), N_("input B") };
1276
1277 for(int row = 0; row < 3; row++)
1278 {
1279 gtk_box_pack_start(GTK_BOX(container), dt_ui_section_label_new(_(row_label[row])), FALSE, FALSE, 0);
1280
1281 // Each row scans the three input channels explicitly because the sliders own the output basis.
1282 for(int col = 0; col < 3; col++)
1283 {
1284 g->point[point].complete[row][col]
1285 = dt_bauhaus_slider_new_with_range(dt_bauhaus_get_global(), DT_GUI_MODULE(self), -2.f, 2.f, 0, row == col, 3);
1286 dt_bauhaus_widget_set_label(g->point[point].complete[row][col], input_label[col]);
1287 _tag_widget(g->point[point].complete[row][col], point);
1288 g_signal_connect(G_OBJECT(g->point[point].complete[row][col]), "value-changed", G_CALLBACK(_general_callback),
1289 self);
1290 gtk_box_pack_start(GTK_BOX(container), g->point[point].complete[row][col], FALSE, FALSE, 0);
1291 }
1292
1293 g->point[point].normalize[row] = gtk_check_button_new_with_label(_("normalize"));
1294 _tag_widget(g->point[point].normalize[row], point);
1295 g_signal_connect(G_OBJECT(g->point[point].normalize[row]), "toggled", G_CALLBACK(_general_callback), self);
1296 gtk_box_pack_start(GTK_BOX(container), g->point[point].normalize[row], FALSE, FALSE, 0);
1297 }
1298}
1299
1302{
1303 g->point[point].simple_theta
1305 dt_bauhaus_widget_set_label(g->point[point].simple_theta, N_("global hue rotation"));
1306 dt_bauhaus_slider_set_factor(g->point[point].simple_theta, 180.f);
1307 dt_bauhaus_slider_set_format(g->point[point].simple_theta, "\302\260");
1308
1309 g->point[point].simple_psi
1311 dt_bauhaus_widget_set_label(g->point[point].simple_psi, N_("chroma (u,v) axes orientation"));
1312 dt_bauhaus_slider_set_factor(g->point[point].simple_psi, 90.f);
1313 dt_bauhaus_slider_set_format(g->point[point].simple_psi, "\302\260");
1314
1315 g->point[point].simple_stretch_1
1316 = dt_bauhaus_slider_new_with_range(dt_bauhaus_get_global(), DT_GUI_MODULE(self), -1.5f, 1.5f, 0, 1.f, 3);
1317 dt_bauhaus_widget_set_label(g->point[point].simple_stretch_1, N_("u stretch"));
1318
1319 g->point[point].simple_stretch_2
1320 = dt_bauhaus_slider_new_with_range(dt_bauhaus_get_global(), DT_GUI_MODULE(self), -1.5f, 1.5f, 0, 1.f, 3);
1321 dt_bauhaus_widget_set_label(g->point[point].simple_stretch_2, N_("v stretch"));
1322
1323 g->point[point].simple_coupling_2
1325 dt_bauhaus_widget_set_label(g->point[point].simple_coupling_2, N_("achromatic coupling hue"));
1326 dt_bauhaus_slider_set_factor(g->point[point].simple_coupling_2, 180.f);
1327 dt_bauhaus_slider_set_format(g->point[point].simple_coupling_2, "\302\260");
1328
1329 g->point[point].simple_coupling_1
1331 dt_bauhaus_widget_set_label(g->point[point].simple_coupling_1, N_("achromatic coupling amount"));
1332
1333 GtkWidget *widgets[] = {
1334 g->point[point].simple_theta,
1335 dt_ui_section_label_new(_("chroma")),
1336 g->point[point].simple_psi,
1337 g->point[point].simple_stretch_1,
1338 g->point[point].simple_stretch_2,
1339 dt_ui_section_label_new(_("achromatic coupling")),
1340 g->point[point].simple_coupling_2,
1341 g->point[point].simple_coupling_1,
1342 };
1343
1344 for(size_t i = 0; i < G_N_ELEMENTS(widgets); i++)
1345 {
1346 if(i != 1 && i != 5)
1347 {
1348 _tag_widget(widgets[i], point);
1349 g_signal_connect(G_OBJECT(widgets[i]), "value-changed", G_CALLBACK(_simple_slider_callback), self);
1350 }
1351 gtk_box_pack_start(GTK_BOX(container), widgets[i], FALSE, FALSE, 0);
1352 }
1353}
1354
1357{
1358 g->point[point].primaries_achromatic_hue
1360 dt_bauhaus_widget_set_label(g->point[point].primaries_achromatic_hue, N_("white hue"));
1361 dt_bauhaus_slider_set_factor(g->point[point].primaries_achromatic_hue, 90.f);
1362 dt_bauhaus_slider_set_format(g->point[point].primaries_achromatic_hue, "\302\260");
1363
1364 g->point[point].primaries_achromatic_purity
1366 dt_bauhaus_widget_set_label(g->point[point].primaries_achromatic_purity, N_("white purity"));
1367
1368 g->point[point].primaries_red_hue
1370 dt_bauhaus_widget_set_label(g->point[point].primaries_red_hue, N_("red hue"));
1371 dt_bauhaus_slider_set_factor(g->point[point].primaries_red_hue, 90.f);
1372 dt_bauhaus_slider_set_format(g->point[point].primaries_red_hue, "\302\260");
1373
1374 g->point[point].primaries_red_purity
1376 dt_bauhaus_widget_set_label(g->point[point].primaries_red_purity, N_("red purity"));
1377
1378 g->point[point].primaries_green_hue
1380 dt_bauhaus_widget_set_label(g->point[point].primaries_green_hue, N_("green hue"));
1381 dt_bauhaus_slider_set_factor(g->point[point].primaries_green_hue, 90.f);
1382 dt_bauhaus_slider_set_format(g->point[point].primaries_green_hue, "\302\260");
1383
1384 g->point[point].primaries_green_purity
1386 dt_bauhaus_widget_set_label(g->point[point].primaries_green_purity, N_("green purity"));
1387
1388 g->point[point].primaries_blue_hue
1390 dt_bauhaus_widget_set_label(g->point[point].primaries_blue_hue, N_("blue hue"));
1391 dt_bauhaus_slider_set_factor(g->point[point].primaries_blue_hue, 90.f);
1392 dt_bauhaus_slider_set_format(g->point[point].primaries_blue_hue, "\302\260");
1393
1394 g->point[point].primaries_blue_purity
1396 dt_bauhaus_widget_set_label(g->point[point].primaries_blue_purity, N_("blue purity"));
1397
1398 g->point[point].primaries_gain
1400 dt_bauhaus_widget_set_label(g->point[point].primaries_gain, N_("gain"));
1401
1402 GtkWidget *widgets[] = {
1403 dt_ui_section_label_new(_("achromatic axis")),
1404 g->point[point].primaries_achromatic_hue,
1405 g->point[point].primaries_achromatic_purity,
1406 dt_ui_section_label_new(_("red primary")),
1407 g->point[point].primaries_red_hue,
1408 g->point[point].primaries_red_purity,
1409 dt_ui_section_label_new(_("green primary")),
1410 g->point[point].primaries_green_hue,
1411 g->point[point].primaries_green_purity,
1412 dt_ui_section_label_new(_("blue primary")),
1413 g->point[point].primaries_blue_hue,
1414 g->point[point].primaries_blue_purity,
1415 dt_ui_section_label_new(_("gain correction")),
1416 g->point[point].primaries_gain,
1417 };
1418
1419 for(size_t i = 0; i < G_N_ELEMENTS(widgets); i++)
1420 {
1421 if(i % 3 != 0)
1422 {
1423 _tag_widget(widgets[i], point);
1424 g_signal_connect(G_OBJECT(widgets[i]), "value-changed", G_CALLBACK(_primaries_slider_callback), self);
1425 }
1426 gtk_box_pack_start(GTK_BOX(container), widgets[i], FALSE, FALSE, 0);
1427 }
1428}
1429
1431 const int point, GtkWidget *container)
1432{
1433#define SPLITTONING_WHITE_PRESERVING_PRIMARY(var, rotation_label, saturation_label) \
1434 g->point[point].white_preserving_##var##_rotation \
1435 = dt_bauhaus_slider_new_with_range(dt_bauhaus_get_global(), DT_GUI_MODULE(self), -1.f, 1.f, 0, 0.f, 3); \
1436 dt_bauhaus_widget_set_label(g->point[point].white_preserving_##var##_rotation, rotation_label); \
1437 dt_bauhaus_slider_set_factor(g->point[point].white_preserving_##var##_rotation, 90.f); \
1438 dt_bauhaus_slider_set_format(g->point[point].white_preserving_##var##_rotation, "\302\260"); \
1439 /* -100%/+100% is the intended travel ; the wider hard range lets the page report a matrix */ \
1440 /* hand-built in complete mode that pushes a primary further out, instead of clamping it. */ \
1441 g->point[point].white_preserving_##var##_saturation \
1442 = dt_bauhaus_slider_new_with_range(dt_bauhaus_get_global(), DT_GUI_MODULE(self), -4.f, 4.f, 0, 0.f, 3); \
1443 dt_bauhaus_slider_set_soft_range(g->point[point].white_preserving_##var##_saturation, -1.f, 1.f); \
1444 dt_bauhaus_widget_set_label(g->point[point].white_preserving_##var##_saturation, saturation_label); \
1445 /* A "%" format on a range under 10 applies the 100x factor and takes two digits off by itself. */ \
1446 dt_bauhaus_slider_set_format(g->point[point].white_preserving_##var##_saturation, "%");
1447
1448 SPLITTONING_WHITE_PRESERVING_PRIMARY(red, N_("red rotation"), N_("red saturation"))
1449 SPLITTONING_WHITE_PRESERVING_PRIMARY(green, N_("green rotation"), N_("green saturation"))
1450 SPLITTONING_WHITE_PRESERVING_PRIMARY(blue, N_("blue rotation"), N_("blue saturation"))
1451
1452#undef SPLITTONING_WHITE_PRESERVING_PRIMARY
1453
1454 GtkWidget *widgets[] = {
1455 dt_ui_section_label_new(_("red primary")),
1456 g->point[point].white_preserving_red_rotation,
1457 g->point[point].white_preserving_red_saturation,
1458 dt_ui_section_label_new(_("green primary")),
1459 g->point[point].white_preserving_green_rotation,
1460 g->point[point].white_preserving_green_saturation,
1461 dt_ui_section_label_new(_("blue primary")),
1462 g->point[point].white_preserving_blue_rotation,
1463 g->point[point].white_preserving_blue_saturation,
1464 };
1465
1466 for(size_t i = 0; i < G_N_ELEMENTS(widgets); i++)
1467 {
1468 if(i % 3 != 0)
1469 {
1470 _tag_widget(widgets[i], point);
1471 g_signal_connect(G_OBJECT(widgets[i]), "value-changed", G_CALLBACK(_white_preserving_slider_callback), self);
1472 }
1473 gtk_box_pack_start(GTK_BOX(container), widgets[i], FALSE, FALSE, 0);
1474 }
1475}
1476
1477void gui_init(struct dt_iop_module_t *self)
1478{
1480 self->gui->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1481 g->preview_surface = NULL;
1482 g->preview_width = 0;
1483 g->preview_height = 0;
1484
1485 g->preview = GTK_WIDGET(gtk_drawing_area_new());
1486 gtk_widget_set_size_request(g->preview, -1, DT_PIXEL_APPLY_DPI(DT_SPLITTONING_RGB_PREVIEW_HEIGHT));
1487 g_signal_connect(G_OBJECT(g->preview), "draw", G_CALLBACK(_preview_draw), self);
1488 gtk_box_pack_start(GTK_BOX(self->gui->widget), g->preview, FALSE, FALSE, 0);
1489
1490 g->tabs = dt_ui_notebook_new();
1492 gtk_box_pack_start(GTK_BOX(self->gui->widget), GTK_WIDGET(g->tabs), FALSE, FALSE, 0);
1493
1495 {
1496 GtkWidget *page = dt_ui_notebook_page(g->tabs, _point_label[point], _(point == 0 ? "shadows" : "highlights"));
1497 g->point[point].page = page;
1498
1499 g->point[point].ev = dt_color_picker_new(
1502 point == 0 ? -16.f : 0.f, 2));
1503 dt_bauhaus_widget_set_label(g->point[point].ev, N_("brightness"));
1504 dt_bauhaus_slider_set_format(g->point[point].ev, " EV");
1505 gtk_widget_set_tooltip_text(g->point[point].ev, _("sample average luminance from an area to set this keyframe"));
1506 _tag_widget(g->point[point].ev, point);
1507 g_signal_connect(G_OBJECT(g->point[point].ev), "value-changed", G_CALLBACK(_general_callback), self);
1508 gtk_box_pack_start(GTK_BOX(page), g->point[point].ev, FALSE, FALSE, 0);
1509
1510 g->point[point].temperature
1511 = dt_bauhaus_slider_new_with_range(dt_bauhaus_get_global(), DT_GUI_MODULE(self), 1667.f, 25000.f, 0, 5003.f, 0);
1512 dt_bauhaus_widget_set_label(g->point[point].temperature, N_("temperature"));
1513 dt_bauhaus_slider_set_soft_range(g->point[point].temperature, 3000.f, 7000.f);
1514 dt_bauhaus_slider_set_format(g->point[point].temperature, " K");
1515 _tag_widget(g->point[point].temperature, point);
1516 g_signal_connect(G_OBJECT(g->point[point].temperature), "value-changed", G_CALLBACK(_general_callback), self);
1517 gtk_box_pack_start(GTK_BOX(page), g->point[point].temperature, FALSE, FALSE, 0);
1518
1519 g->point[point].mixer_mode = dt_bauhaus_combobox_new(dt_bauhaus_get_global(), DT_GUI_MODULE(self));
1520 dt_bauhaus_widget_set_label(g->point[point].mixer_mode, N_("mode"));
1521 dt_bauhaus_combobox_add(g->point[point].mixer_mode, _("Complete"));
1522 dt_bauhaus_combobox_add(g->point[point].mixer_mode, _("Simple"));
1523 dt_bauhaus_combobox_add(g->point[point].mixer_mode, _("Primaries"));
1524 dt_bauhaus_combobox_add(g->point[point].mixer_mode, _("White-preserving"));
1525 _tag_widget(g->point[point].mixer_mode, point);
1526 g_signal_connect(G_OBJECT(g->point[point].mixer_mode), "value-changed", G_CALLBACK(_mixer_mode_callback), self);
1527 gtk_box_pack_start(GTK_BOX(page), g->point[point].mixer_mode, FALSE, FALSE, 0);
1528
1529 g->point[point].mixer_stack = gtk_stack_new();
1530 gtk_stack_set_hhomogeneous(GTK_STACK(g->point[point].mixer_stack), FALSE);
1531 gtk_stack_set_vhomogeneous(GTK_STACK(g->point[point].mixer_stack), FALSE);
1532 gtk_stack_set_transition_type(GTK_STACK(g->point[point].mixer_stack), GTK_STACK_TRANSITION_TYPE_NONE);
1533 gtk_box_pack_start(GTK_BOX(page), g->point[point].mixer_stack, FALSE, FALSE, 0);
1534
1535 GtkWidget *complete = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1536 GtkWidget *simple = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1537 GtkWidget *primaries = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1538 GtkWidget *white_preserving = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1539 gtk_stack_add_named(GTK_STACK(g->point[point].mixer_stack), complete, "complete");
1540 gtk_stack_add_named(GTK_STACK(g->point[point].mixer_stack), simple, "simple");
1541 gtk_stack_add_named(GTK_STACK(g->point[point].mixer_stack), primaries, "primaries");
1542 gtk_stack_add_named(GTK_STACK(g->point[point].mixer_stack), white_preserving, "white-preserving");
1543
1544 _build_complete_ui(self, g, point, complete);
1545 _build_simple_ui(self, g, point, simple);
1546 _build_primaries_ui(self, g, point, primaries);
1547 _build_white_preserving_ui(self, g, point, white_preserving);
1548 }
1549
1551 G_CALLBACK(_pipe_finished_callback), self);
1553 G_CALLBACK(_pipe_finished_callback), self);
1554
1555 gui_update(self);
1556}
1557
1559{
1562 if(g->preview_surface) cairo_surface_destroy(g->preview_surface);
1564}
1565
1567{
1568 dt_iop_default_init(module);
1569
1571 memset(d, 0, sizeof(*d));
1572
1573 d->ev[DT_SPLITTONING_RGB_POINT_DARK] = -16.f;
1575 d->temperature[DT_SPLITTONING_RGB_POINT_DARK] = 5003.f;
1576 d->temperature[DT_SPLITTONING_RGB_POINT_BRIGHT] = 5003.f;
1577
1579 for(int row = 0; row < 3; row++)
1580 {
1581 d->normalize[point][row] = TRUE;
1582 d->red[point][row] = row == 0 ? 1.f : 0.f;
1583 d->green[point][row] = row == 1 ? 1.f : 0.f;
1584 d->blue[point][row] = row == 2 ? 1.f : 0.f;
1585 }
1586
1587 module->default_enabled = FALSE;
1588}
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:1498
float dt_bauhaus_slider_get(GtkWidget *widget)
Definition bauhaus.c:3280
int dt_bauhaus_combobox_get(GtkWidget *widget)
Definition bauhaus.c:2136
void dt_bauhaus_slider_set(GtkWidget *widget, float pos)
Definition bauhaus.c:3331
void dt_bauhaus_combobox_set(GtkWidget *widget, const int pos)
Definition bauhaus.c:2090
void dt_bauhaus_widget_set_label(GtkWidget *widget, const char *label)
Definition bauhaus.c:1504
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:1632
GtkWidget * dt_bauhaus_combobox_new(dt_bauhaus_t *bh, dt_gui_module_t *self)
Definition bauhaus.c:1698
void dt_bauhaus_slider_set_format(GtkWidget *widget, const char *format)
Definition bauhaus.c:3407
void dt_bauhaus_combobox_add(GtkWidget *widget, const char *text)
Definition bauhaus.c:1824
void dt_bauhaus_slider_set_factor(GtkWidget *widget, float factor)
Definition bauhaus.c:3423
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_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_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])
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)
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])
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])
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.
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])
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)
#define DT_IOP_CHANNELMIXER_SHARED_SIMPLE_EPS
@ DT_IOP_CHANNELMIXER_SHARED_PRIMARIES_BASIS_RGB
@ DT_ADAPTATION_CAT16
@ DT_ADAPTATION_RGB
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
static const float x
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
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)
Does key have a value?
void dt_conf_set_int(const char *name, int val)
int dt_conf_get_int(const char *name)
Integer for name, clamped to the bounds declared in the XML.
void dt_control_log(const char *msg,...)
Definition control.c:824
struct dt_bauhaus_t * dt_bauhaus_get_global(void)
Definition darktable.c:646
#define dt_dev_add_history_item(dev, module, enable, redraw)
void dt_iop_params_t
Definition dev_history.h:43
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)
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_CLONE_TARGETS__ dt_iop_order_iccprofile_info_t * dt_ioppr_get_iop_work_profile_info(struct dt_iop_module_t *module, GList *iop_list)
@ TYPE_FLOAT
Definition format.h:56
#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:308
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:1893
@ IOP_FLAGS_INCLUDE_IN_STYLES
Definition imageop.h:185
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:186
@ IOP_FLAGS_ALLOW_TILING
Definition imageop.h:188
@ IOP_GROUP_COLOR
Definition imageop.h:158
#define IOP_GUI_FREE
Definition imageop_gui.h:96
static dt_iop_gui_data_t * dt_iop_gui_data(const struct dt_iop_module_t *m)
The module's GUI data blob, NULL-safe for headless callers: IOP process() implementations read it for...
Definition imageop_gui.h:81
#define IOP_GUI_ALLOC(module)
Definition imageop_gui.h:93
void *const ovoid
GtkWidget * dt_ui_section_label_new(const gchar *str)
Definition label.c:114
@ DT_DEBUG_OPENCL
Definition logging.h:57
void dt_print(dt_debug_thread_t thread, const char *msg,...) __attribute__((format(printf
Print to stdout when thread is enabled, prefixed with seconds since startup.
float *const restrict luminance
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 NORM_MIN
Definition math.h:37
float DT_ALIGNED_ARRAY dt_colormatrix_t[4][4]
Definition matrices.h:34
static void transpose_3xSSE(const dt_colormatrix_t input, dt_colormatrix_t output)
Definition matrices.h:69
static void pack_3xSSE_to_3x4(const dt_colormatrix_t input, float output[12])
Definition matrices.h:150
#define dt_free_align(ptr)
Release memory from dt_alloc_align() and set ptr to NULL.
Definition mem_alloc.h:214
static void * dt_calloc_align(size_t size)
dt_alloc_align() followed by a zero fill.
Definition mem_alloc.h:225
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
#define DT_MODULE_INTROSPECTION(MODVER, PARAMSTYPE)
DT_MODULE() for a module whose params struct is introspected.
GtkWidget * dt_ui_notebook_page(GtkNotebook *notebook, const char *text, const char *tooltip)
Definition notebook.c:88
GtkNotebook * dt_ui_notebook_new()
Definition notebook.c:83
void dt_ui_notebook_set_picker_owner(GtkNotebook *notebook, gpointer owner)
Register an opaque owner for a GtkNotebook's page switches, and report every "switch_page" to the hos...
Definition notebook.c:118
int dt_opencl_enqueue_kernel_2d(const int dev, const int kernel, const size_t *sizes)
Definition opencl.c:2554
int dt_opencl_create_kernel(const int prog, const char *name)
Definition opencl.c:2448
void * dt_opencl_copy_host_to_device_constant(const int devid, const size_t size, void *host)
Definition opencl.c:2750
void dt_opencl_free_kernel(const int kernel)
Definition opencl.c:2491
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:2545
void dt_opencl_release_mem_object(cl_mem mem)
Definition opencl.c:2805
#define ROUNDUPDHT(a, b)
Definition opencl.h:86
#define ROUNDUPDWD(a, b)
Definition opencl.h:85
#define dt_omploop_sfence()
Definition openmp.h:164
#define __OMP_PARALLEL_FOR__(...)
Definition openmp.h:95
static cairo_surface_t * dt_cairo_image_surface_create(cairo_format_t format, int width, int height)
static int dt_cairo_image_surface_get_width(cairo_surface_t *surface)
static int dt_cairo_image_surface_get_height(cairo_surface_t *surface)
#define DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(ctlsig, cb, user_data)
Definition signal.h:407
struct dt_control_signal_t * dt_control_signal_get_global(void)
Definition darktable.c:616
@ 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:177
@ 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:182
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
Definition signal.h:396
DT_ALIGNED_PIXEL float dt_aligned_pixel_t[4]
Definition simd.h:53
float dt_aligned_pixel_simd_t __attribute__((vector_size(16), aligned(16)))
Apply one channel's tone curve to each of the three colour channels, or pass the channel through unto...
Definition simd.h:55
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)
static void _build_white_preserving_ui(dt_iop_module_t *self, dt_iop_splittoning_rgb_gui_data_t *g, const int point, GtkWidget *container)
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.
#define SPLITTONING_WHITE_PRESERVING_PRIMARY(var, rotation_label, saturation_label)
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 _white_preserving_slider_callback(GtkWidget *widget, gpointer user_data)
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_WHITE_PRESERVING
@ 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 gboolean _white_preserving_widgets(const dt_iop_splittoning_rgb_gui_data_t *const g, const int point, GtkWidget *widgets[6])
Collect the six white-preserving widgets of one keyframe.
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)
static gboolean _sync_white_preserving_from_params(dt_iop_module_t *self, const int point, float *error)
Synchronize the white-preserving GUI of one keyframe from its effective mixer matrix.
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_iop_module_t *void * data
GList * iop
Definition develop.h:269
struct dt_dev_pixelpipe_t * preview_pipe
Definition develop.h:214
struct dt_dev_pixelpipe_t * pipe
Definition develop.h:214
unsigned int channels
Definition format.h:83
dt_iop_buffer_type_t datatype
Definition format.h:85
White-preserving primaries model : per-primary rotation and saturation, no white freedom.
GtkWidget * widget
Definition imageop_gui.h:47
dt_iop_global_data_t * data
Definition imageop.h:238
dt_iop_params_t * default_params
Definition imageop.h:333
struct dt_iop_module_gui_t * gui
Definition imageop.h:346
struct dt_develop_t * dev
Definition imageop.h:311
dt_iop_global_data_t * global_data
Definition imageop.h:337
dt_aligned_pixel_t picked_color_min
Definition imageop.h:287
dt_aligned_pixel_t picked_color_max
Definition imageop.h:287
dt_aligned_pixel_t picked_color
Definition imageop.h:287
dt_iop_params_t * params
Definition imageop.h:333
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_in[3]
Per-channel encoded -> linear tone curve, lutsize entries each, sampled over [0,1]....
dt_colormatrix_t matrix_in
RGB -> XYZ (D50), row-major. matrix_in[1][*] is the luminance row. NaN in [0][0] marks the whole prof...
Region of interest passed through the pixelpipe.
Definition format.h:49
int width
Definition format.h:50
int height
Definition format.h:50
dt_colormatrix_t rgb_to_xyz_transposed
dt_colormatrix_t point_matrix[2]
#define __DT_CLONE_TARGETS__
Telling the user something happened.
gboolean dt_gui_widgets_suppressed(void)
#define dt_gui_freeze_begin()
#define dt_gui_freeze_end()
#define DT_GUI_BOX_SPACING
#define DT_PIXEL_APPLY_DPI(value)