Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
rgbcurve.c
Go to the documentation of this file.
1/*
2 This file is part of the Ansel project.
3 Copyright (C) 2019 Andreas Schneider.
4 Copyright (C) 2019, 2022-2026 Aurélien PIERRE.
5 Copyright (C) 2019 Bill Ferguson.
6 Copyright (C) 2019-2022 Diederik Ter Rahe.
7 Copyright (C) 2019 Edgardo Hoszowski.
8 Copyright (C) 2019 emeikei.
9 Copyright (C) 2019 Jacques Le Clerc.
10 Copyright (C) 2019 parafin.
11 Copyright (C) 2019-2022 Pascal Obry.
12 Copyright (C) 2019 Philippe Weyland.
13 Copyright (C) 2019 Tobias Ellinghaus.
14 Copyright (C) 2019 Ulrich Pegelow.
15 Copyright (C) 2020, 2022 Aldric Renaudin.
16 Copyright (C) 2020-2021 Chris Elston.
17 Copyright (C) 2020-2021 Dan Torop.
18 Copyright (C) 2020 Hubert Kowalski.
19 Copyright (C) 2020 Marco.
20 Copyright (C) 2020-2021 Ralf Brown.
21 Copyright (C) 2021 lhietal.
22 Copyright (C) 2021 luzpaz.
23 Copyright (C) 2021 Marco Carrarini.
24 Copyright (C) 2022 Hanno Schwalm.
25 Copyright (C) 2022 Martin Bařinka.
26 Copyright (C) 2022 Philipp Lutz.
27 Copyright (C) 2023 Luca Zulberti.
28
29 Ansel is free software: you can redistribute it and/or modify
30 it under the terms of the GNU General Public License as published by
31 the Free Software Foundation, either version 3 of the License, or
32 (at your option) any later version.
33
34 Ansel is distributed in the hope that it will be useful,
35 but WITHOUT ANY WARRANTY; without even the implied warranty of
36 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
37 GNU General Public License for more details.
38
39 You should have received a copy of the GNU General Public License
40 along with Ansel. If not, see <http://www.gnu.org/licenses/>.
41*/
42#ifdef HAVE_CONFIG_H
43#include "config.h"
47#endif
48
49#include "widgets/bauhaus.h"
50#include "develop/iop_profile.h"
51#include "pixel/rgb_norms.h"
52#include "system/macros.h"
53#include "system/openmp.h"
55#include "system/mem_alloc.h"
56#include "system/simd.h"
58#include "develop/imageop.h"
60#include "develop/imageop_gui.h"
62#include "widgets/gdkkeys.h"
63#include "gui/presets.h"
64#include "widgets/draw.h"
65#include "control/control.h"
66
67#include "libs/colorpicker.h"
68#include "widgets/notebook.h"
69#include "widgets/scroll_wrap.h"
70#include "gui/screen_metrics.h"
72
73#define DT_GUI_CURVE_EDITOR_INSET DT_PIXEL_APPLY_DPI(1)
74#define DT_IOP_RGBCURVE_RES 256
75#define DT_IOP_RGBCURVE_MAXNODES 20
76#define DT_IOP_RGBCURVE_MIN_X_DISTANCE 0.0025f
77// max iccprofile file name length
78// must be in synch with filename in dt_colorspaces_color_profile_t in colorspaces.h
79#define DT_IOP_COLOR_ICC_LEN 512
80
82
90
92{
93 DT_S_SCALE_AUTOMATIC_RGB = 0, // $DESCRIPTION: "RGB, linked channels"
94 DT_S_SCALE_MANUAL_RGB = 1 // $DESCRIPTION: "RGB, independent channels"
96
98{
99 float x; // $MIN: 0.0 $MAX: 1.0 $DEFAULT: 0.0
100 float y; // $MIN: 0.0 $MAX: 1.0 $DEFAULT: 0.0
102
104{
106 [DT_IOP_RGBCURVE_MAXNODES]; // actual nodes for each curve
107 int curve_num_nodes[DT_IOP_RGBCURVE_MAX_CHANNELS]; // $DEFAULT: 2 number of nodes per curve
108 int curve_type[DT_IOP_RGBCURVE_MAX_CHANNELS]; // $DEFAULT: MONOTONE_HERMITE (CATMULL_ROM, MONOTONE_HERMITE, CUBIC_SPLINE)
109 dt_iop_rgbcurve_autoscale_t curve_autoscale; // $DEFAULT: DT_S_SCALE_AUTOMATIC_RGB $DESCRIPTION: "mode"
110 gboolean compensate_middle_grey; // $DEFAULT: 0 $DESCRIPTION: "compensate middle gray" scale the curve and histogram so middle gray is at .5
111 dt_iop_rgb_norms_t preserve_colors; // $DEFAULT: DT_RGB_NORM_LUMINANCE $DESCRIPTION: "preserve colors"
113
137
139{
141 dt_draw_curve_t *curve[DT_IOP_RGBCURVE_MAX_CHANNELS]; // curves for pipe piece and pixel processing
142 float table[DT_IOP_RGBCURVE_MAX_CHANNELS][0x10000]; // precomputed look-up tables for tone curve
143 float unbounded_coeffs[DT_IOP_RGBCURVE_MAX_CHANNELS][3]; // approximation for extrapolation
144 int curve_changed[DT_IOP_RGBCURVE_MAX_CHANNELS]; // curve type or number of nodes changed?
148
149typedef float (*_curve_table_ptr)[0x10000];
150typedef float (*_coeffs_table_ptr)[3];
151
152const char *name()
153{
154 return _("curve");
155}
156
158{
159 return IOP_GROUP_COLOR;
160}
161
166
168{
169 return IOP_CS_RGB;
170}
171
172const char **description(struct dt_iop_module_t *self)
173{
174 return dt_iop_set_description(self, _("alter an image's tones using curves in RGB color space"),
175 _("corrective and creative"),
176 _("linear, RGB, display-referred"),
177 _("non-linear, RGB"),
178 _("linear, RGB, display-referred"));
179}
180
182{
184 memset(&p, 0, sizeof(p));
185 p.curve_num_nodes[DT_IOP_RGBCURVE_R] = 6;
186 p.curve_num_nodes[DT_IOP_RGBCURVE_G] = 7;
187 p.curve_num_nodes[DT_IOP_RGBCURVE_B] = 7;
188 p.curve_type[DT_IOP_RGBCURVE_R] = CUBIC_SPLINE;
189 p.curve_type[DT_IOP_RGBCURVE_G] = CUBIC_SPLINE;
190 p.curve_type[DT_IOP_RGBCURVE_B] = CUBIC_SPLINE;
191 p.curve_autoscale = DT_S_SCALE_AUTOMATIC_RGB;
192 p.compensate_middle_grey = 1;
193 p.preserve_colors = 1;
194
195 float linear_ab[7] = { 0.0, 0.08, 0.3, 0.5, 0.7, 0.92, 1.0 };
196
197 // linear a, b curves for presets
198 for(int k = 0; k < 7; k++) p.curve_nodes[DT_IOP_RGBCURVE_G][k].x = linear_ab[k];
199 for(int k = 0; k < 7; k++) p.curve_nodes[DT_IOP_RGBCURVE_G][k].y = linear_ab[k];
200 for(int k = 0; k < 7; k++) p.curve_nodes[DT_IOP_RGBCURVE_B][k].x = linear_ab[k];
201 for(int k = 0; k < 7; k++) p.curve_nodes[DT_IOP_RGBCURVE_B][k].y = linear_ab[k];
202
203 // More useful low contrast curve (based on Samsung NX -2 Contrast)
204 p.curve_nodes[DT_IOP_RGBCURVE_R][0].x = 0.000000;
205 p.curve_nodes[DT_IOP_RGBCURVE_R][1].x = 0.003862;
206 p.curve_nodes[DT_IOP_RGBCURVE_R][2].x = 0.076613;
207 p.curve_nodes[DT_IOP_RGBCURVE_R][3].x = 0.169355;
208 p.curve_nodes[DT_IOP_RGBCURVE_R][4].x = 0.774194;
209 p.curve_nodes[DT_IOP_RGBCURVE_R][5].x = 1.000000;
210 p.curve_nodes[DT_IOP_RGBCURVE_R][0].y = 0.000000;
211 p.curve_nodes[DT_IOP_RGBCURVE_R][1].y = 0.007782;
212 p.curve_nodes[DT_IOP_RGBCURVE_R][2].y = 0.156182;
213 p.curve_nodes[DT_IOP_RGBCURVE_R][3].y = 0.290352;
214 p.curve_nodes[DT_IOP_RGBCURVE_R][4].y = 0.773852;
215 p.curve_nodes[DT_IOP_RGBCURVE_R][5].y = 1.000000;
216 dt_gui_presets_add_generic(_("contrast compression"), self->op,
217 self->version(), &p, sizeof(p), 1);
218
219 p.curve_num_nodes[DT_IOP_RGBCURVE_R] = 7;
220 float linear_L[7] = { 0.0, 0.08, 0.17, 0.50, 0.83, 0.92, 1.0 };
221
222 // Linear - no contrast
223 for(int k = 0; k < 7; k++) p.curve_nodes[DT_IOP_RGBCURVE_R][k].x = linear_L[k];
224 for(int k = 0; k < 7; k++) p.curve_nodes[DT_IOP_RGBCURVE_R][k].y = linear_L[k];
225 dt_gui_presets_add_generic(_("gamma 1.0 (linear)"), self->op,
226 self->version(), &p, sizeof(p), 1);
227
228 // Linear contrast
229 for(int k = 0; k < 7; k++) p.curve_nodes[DT_IOP_RGBCURVE_R][k].x = linear_L[k];
230 for(int k = 0; k < 7; k++) p.curve_nodes[DT_IOP_RGBCURVE_R][k].y = linear_L[k];
231 p.curve_nodes[DT_IOP_RGBCURVE_R][1].y -= 0.020;
232 p.curve_nodes[DT_IOP_RGBCURVE_R][2].y -= 0.030;
233 p.curve_nodes[DT_IOP_RGBCURVE_R][4].y += 0.030;
234 p.curve_nodes[DT_IOP_RGBCURVE_R][5].y += 0.020;
235 dt_gui_presets_add_generic(_("contrast - med (linear)"), self->op,
236 self->version(), &p, sizeof(p), 1);
237
238 for(int k = 0; k < 7; k++) p.curve_nodes[DT_IOP_RGBCURVE_R][k].x = linear_L[k];
239 for(int k = 0; k < 7; k++) p.curve_nodes[DT_IOP_RGBCURVE_R][k].y = linear_L[k];
240 p.curve_nodes[DT_IOP_RGBCURVE_R][1].y -= 0.040;
241 p.curve_nodes[DT_IOP_RGBCURVE_R][2].y -= 0.060;
242 p.curve_nodes[DT_IOP_RGBCURVE_R][4].y += 0.060;
243 p.curve_nodes[DT_IOP_RGBCURVE_R][5].y += 0.040;
244 dt_gui_presets_add_generic(_("contrast - high (linear)"), self->op,
245 self->version(), &p, sizeof(p), 1);
246
247 // Gamma contrast
248 for(int k = 0; k < 7; k++) p.curve_nodes[DT_IOP_RGBCURVE_R][k].x = linear_L[k];
249 for(int k = 0; k < 7; k++) p.curve_nodes[DT_IOP_RGBCURVE_R][k].y = linear_L[k];
250 p.curve_nodes[DT_IOP_RGBCURVE_R][1].y -= 0.020;
251 p.curve_nodes[DT_IOP_RGBCURVE_R][2].y -= 0.030;
252 p.curve_nodes[DT_IOP_RGBCURVE_R][4].y += 0.030;
253 p.curve_nodes[DT_IOP_RGBCURVE_R][5].y += 0.020;
254 for(int k = 1; k < 6; k++)
255 p.curve_nodes[DT_IOP_RGBCURVE_R][k].x = powf(p.curve_nodes[DT_IOP_RGBCURVE_R][k].x, 2.2f);
256 for(int k = 1; k < 6; k++)
257 p.curve_nodes[DT_IOP_RGBCURVE_R][k].y = powf(p.curve_nodes[DT_IOP_RGBCURVE_R][k].y, 2.2f);
258 dt_gui_presets_add_generic(_("contrast - med (gamma 2.2)"), self->op,
259 self->version(), &p, sizeof(p), 1);
260
261 for(int k = 0; k < 7; k++) p.curve_nodes[DT_IOP_RGBCURVE_R][k].x = linear_L[k];
262 for(int k = 0; k < 7; k++) p.curve_nodes[DT_IOP_RGBCURVE_R][k].y = linear_L[k];
263 p.curve_nodes[DT_IOP_RGBCURVE_R][1].y -= 0.040;
264 p.curve_nodes[DT_IOP_RGBCURVE_R][2].y -= 0.060;
265 p.curve_nodes[DT_IOP_RGBCURVE_R][4].y += 0.060;
266 p.curve_nodes[DT_IOP_RGBCURVE_R][5].y += 0.040;
267 for(int k = 1; k < 6; k++)
268 p.curve_nodes[DT_IOP_RGBCURVE_R][k].x = powf(p.curve_nodes[DT_IOP_RGBCURVE_R][k].x, 2.2f);
269 for(int k = 1; k < 6; k++)
270 p.curve_nodes[DT_IOP_RGBCURVE_R][k].y = powf(p.curve_nodes[DT_IOP_RGBCURVE_R][k].y, 2.2f);
271 dt_gui_presets_add_generic(_("contrast - high (gamma 2.2)"), self->op,
272 self->version(), &p, sizeof(p), 1);
273
277
278 for(int k = 0; k < 7; k++) p.curve_nodes[DT_IOP_RGBCURVE_R][k].x = linear_L[k];
279 for(int k = 0; k < 7; k++) p.curve_nodes[DT_IOP_RGBCURVE_R][k].y = linear_L[k];
280
281 // Gamma 2.0 - no contrast
282 for(int k = 1; k < 6; k++) p.curve_nodes[DT_IOP_RGBCURVE_R][k].y = powf(linear_L[k], 2.0f);
283 dt_gui_presets_add_generic(_("gamma 2.0"), self->op,
284 self->version(), &p, sizeof(p), 1);
285
286 // Gamma 0.5 - no contrast
287 for(int k = 1; k < 6; k++) p.curve_nodes[DT_IOP_RGBCURVE_R][k].y = powf(linear_L[k], 0.5f);
288 dt_gui_presets_add_generic(_("gamma 0.5"), self->op,
289 self->version(), &p, sizeof(p), 1);
290
291 // Log2 - no contrast
292 for(int k = 1; k < 6; k++) p.curve_nodes[DT_IOP_RGBCURVE_R][k].y = logf(linear_L[k] + 1.0f) / logf(2.0f);
293 dt_gui_presets_add_generic(_("logarithm (base 2)"), self->op,
294 self->version(), &p, sizeof(p), 1);
295
296 // Exp2 - no contrast
297 for(int k = 1; k < 6; k++) p.curve_nodes[DT_IOP_RGBCURVE_R][k].y = powf(2.0f, linear_L[k]) - 1.0f;
298 dt_gui_presets_add_generic(_("exponential (base 2)"), self->op,
299 self->version(), &p, sizeof(p), 1);
300}
301
302static float _curve_to_mouse(const float x, const float zoom_factor, const float offset)
303{
304 return (x - offset) * zoom_factor;
305}
306
307static float _mouse_to_curve(const float x, const float zoom_factor, const float offset)
308{
309 return (x / zoom_factor) + offset;
310}
311
312static void picker_scale(const float *const in, float *out, dt_iop_rgbcurve_params_t *p,
313 const dt_iop_order_iccprofile_info_t *const work_profile)
314{
315 switch(p->curve_autoscale)
316 {
318 if(p->compensate_middle_grey && !IS_NULL_PTR(work_profile))
319 {
320 for(int c = 0; c < 3; c++) out[c] = dt_ioppr_compensate_middle_grey(in[c], work_profile);
321 }
322 else
323 {
324 for(int c = 0; c < 3; c++) out[c] = in[c];
325 }
326 break;
328 {
329 const float val
330 = (work_profile) ? dt_ioppr_get_rgb_matrix_luminance(in,
331 work_profile->matrix_in,
332 work_profile->lut_in,
333 work_profile->unbounded_coeffs_in,
334 work_profile->lutsize,
335 work_profile->nonlinearlut)
337 if(p->compensate_middle_grey && !IS_NULL_PTR(work_profile))
338 {
339 out[0] = dt_ioppr_compensate_middle_grey(val, work_profile);
340 }
341 else
342 {
343 out[0] = val;
344 }
345 out[1] = out[2] = 0.f;
346 }
347 break;
348 }
349
350 for(int c = 0; c < 3; c++) out[c] = CLAMP(out[c], 0.0f, 1.0f);
351}
352
354{
355 gtk_notebook_set_show_tabs(g->channel_tabs, p->curve_autoscale == DT_S_SCALE_MANUAL_RGB);
356
357 gtk_widget_set_visible(g->cmb_preserve_colors, p->curve_autoscale == DT_S_SCALE_AUTOMATIC_RGB);
358}
359
361{
362 for(int k=0; k<p->curve_num_nodes[channel]; k++)
363 if(p->curve_nodes[channel][k].x != p->curve_nodes[channel][k].y) return FALSE;
364
365 return TRUE;
366}
367
368void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
369{
372
373 if(w == g->autoscale)
374 {
375 g->channel = DT_IOP_RGBCURVE_R;
376 gtk_notebook_set_current_page(GTK_NOTEBOOK(g->channel_tabs), DT_IOP_RGBCURVE_R);
377
379
380 // switching to manual scale, if G and B not touched yet, just make them identical to global setting (R)
381 if(p->curve_autoscale == DT_S_SCALE_MANUAL_RGB
384 {
385 for(int k=0; k<DT_IOP_RGBCURVE_MAXNODES; k++)
386 p->curve_nodes[DT_IOP_RGBCURVE_G][k]
387 = p->curve_nodes[DT_IOP_RGBCURVE_B][k] = p->curve_nodes[DT_IOP_RGBCURVE_R][k];
388
389 p->curve_num_nodes[DT_IOP_RGBCURVE_G] = p->curve_num_nodes[DT_IOP_RGBCURVE_B]
390 = p->curve_num_nodes[DT_IOP_RGBCURVE_R];
391 p->curve_type[DT_IOP_RGBCURVE_G] = p->curve_type[DT_IOP_RGBCURVE_B]
392 = p->curve_type[DT_IOP_RGBCURVE_R];
393 }
394 }
395 else if(w == g->chk_compensate_middle_grey)
396 {
397 const dt_iop_order_iccprofile_info_t *const work_profile
399 if(IS_NULL_PTR(work_profile)) return;
400
401 for(int ch = 0; ch < DT_IOP_RGBCURVE_MAX_CHANNELS; ch++)
402 {
403 for(int k = 0; k < p->curve_num_nodes[ch]; k++)
404 {
405 if(p->compensate_middle_grey)
406 {
407 // we transform the curve nodes from the image colorspace to lab
408 p->curve_nodes[ch][k].x = dt_ioppr_compensate_middle_grey(p->curve_nodes[ch][k].x, work_profile);
409 p->curve_nodes[ch][k].y = dt_ioppr_compensate_middle_grey(p->curve_nodes[ch][k].y, work_profile);
410 }
411 else
412 {
413 // we transform the curve nodes from lab to the image colorspace
414 p->curve_nodes[ch][k].x = dt_ioppr_uncompensate_middle_grey(p->curve_nodes[ch][k].x, work_profile);
415 p->curve_nodes[ch][k].y = dt_ioppr_uncompensate_middle_grey(p->curve_nodes[ch][k].y, work_profile);
416 }
417 }
418 }
419
420 self->histogram_middle_grey = p->compensate_middle_grey;
421 }
422}
423
425{
426 if(dt_gui_widgets_suppressed()) return;
429
430 const int combo = dt_bauhaus_combobox_get(widget);
431
432 if(combo == 0)
433 p->curve_type[DT_IOP_RGBCURVE_R] = p->curve_type[DT_IOP_RGBCURVE_G] = p->curve_type[DT_IOP_RGBCURVE_B]
434 = CUBIC_SPLINE;
435 else if(combo == 1)
436 p->curve_type[DT_IOP_RGBCURVE_R] = p->curve_type[DT_IOP_RGBCURVE_G] = p->curve_type[DT_IOP_RGBCURVE_B]
437 = CATMULL_ROM;
438 else if(combo == 2)
439 p->curve_type[DT_IOP_RGBCURVE_R] = p->curve_type[DT_IOP_RGBCURVE_G] = p->curve_type[DT_IOP_RGBCURVE_B]
441
442 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
443 gtk_widget_queue_draw(GTK_WIDGET(g->area));
444}
445
446static void tab_switch_callback(GtkNotebook *notebook, GtkWidget *page, guint page_num, gpointer user_data)
447{
448 if(dt_gui_widgets_suppressed()) return;
449 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
451
452 g->channel = (rgbcurve_channel_t)page_num;
453
454 gtk_widget_queue_draw(self->gui->widget);
455}
456
457static gboolean _area_resized_callback(GtkWidget *widget, GdkEvent *event, gpointer user_data)
458{
459 GtkRequisition r;
460 GtkAllocation allocation;
461 gtk_widget_get_allocation(widget, &allocation);
462 r.width = allocation.width;
463 r.height = allocation.width;
464 gtk_widget_get_preferred_size(widget, &r, NULL);
465 return TRUE;
466}
467
468static inline int _add_node(dt_iop_rgbcurve_node_t *curve_nodes, int *nodes, float x, float y)
469{
470 int selected = -1;
471 if(curve_nodes[0].x > x)
472 selected = 0;
473 else
474 {
475 for(int k = 1; k < *nodes; k++)
476 {
477 if(curve_nodes[k].x > x)
478 {
479 selected = k;
480 break;
481 }
482 }
483 }
484 if(selected == -1) selected = *nodes;
485 for(int i = *nodes; i > selected; i--)
486 {
487 curve_nodes[i].x = curve_nodes[i - 1].x;
488 curve_nodes[i].y = curve_nodes[i - 1].y;
489 }
490 // found a new point
491 curve_nodes[selected].x = x;
492 curve_nodes[selected].y = y;
493 (*nodes)++;
494 return selected;
495}
496
497static inline int _add_node_from_picker(dt_iop_rgbcurve_params_t *p, const float *const in, const float increment,
498 const int ch, const dt_iop_order_iccprofile_info_t *const work_profile)
499{
500 float x = 0.f;
501 float y = 0.f;
502 float val = 0.f;
503
504 if(p->curve_autoscale == DT_S_SCALE_AUTOMATIC_RGB)
505 val = (work_profile) ? dt_ioppr_get_rgb_matrix_luminance(in,
506 work_profile->matrix_in,
507 work_profile->lut_in,
508 work_profile->unbounded_coeffs_in,
509 work_profile->lutsize,
510 work_profile->nonlinearlut)
512 else
513 val = in[ch];
514
515 if(p->compensate_middle_grey && !IS_NULL_PTR(work_profile))
516 y = x = dt_ioppr_compensate_middle_grey(val, work_profile);
517 else
518 y = x = val;
519
520 x -= increment;
521 y += increment;
522
523 CLAMP(x, 0.f, 1.f);
524 CLAMP(y, 0.f, 1.f);
525
526 return _add_node(p->curve_nodes[ch], &p->curve_num_nodes[ch], x, y);
527}
528
530{
531 (void)piece;
533 if(picker == g->colorpicker_set_values)
534 {
537
538 const int ch = g->channel;
540
541 // reset current curve
542 p->curve_num_nodes[ch] = d->curve_num_nodes[ch];
543 p->curve_type[ch] = d->curve_type[ch];
544 for(int k = 0; k < DT_IOP_RGBCURVE_MAXNODES; k++)
545 {
546 p->curve_nodes[ch][k].x = d->curve_nodes[ch][k].x;
547 p->curve_nodes[ch][k].y = d->curve_nodes[ch][k].y;
548 }
549
550 const GdkModifierType state = dt_key_modifier_state();
551 int picker_set_upper_lower; // flat=0, lower=-1, upper=1
553 picker_set_upper_lower = 1;
554 else if(dt_modifier_is(state, GDK_SHIFT_MASK))
555 picker_set_upper_lower = -1;
556 else
557 picker_set_upper_lower = 0;
558
559 // now add 4 nodes: min, avg, center, max
560 const float increment = 0.05f * picker_set_upper_lower;
561
562 _add_node_from_picker(p, self->picked_color_min, 0.f, ch, work_profile);
563 _add_node_from_picker(p, self->picked_color, increment, ch, work_profile);
564 _add_node_from_picker(p, self->picked_color_max, 0.f, ch, work_profile);
565
566 if(p->curve_num_nodes[ch] == 5)
567 _add_node(p->curve_nodes[ch], &p->curve_num_nodes[ch],
568 p->curve_nodes[ch][1].x - increment + (p->curve_nodes[ch][3].x - p->curve_nodes[ch][1].x) / 2.f,
569 p->curve_nodes[ch][1].y + increment + (p->curve_nodes[ch][3].y - p->curve_nodes[ch][1].y) / 2.f);
570
571 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
572 }
573
575}
576
577static gboolean _sanity_check(const float x, const int selected, const int nodes,
578 const dt_iop_rgbcurve_node_t *curve)
579{
580 gboolean point_valid = TRUE;
581
582 // check if it is not too close to other node
583 const float min_dist = DT_IOP_RGBCURVE_MIN_X_DISTANCE; // in curve coordinates
584 if((selected > 0 && x - curve[selected - 1].x <= min_dist)
585 || (selected < nodes - 1 && curve[selected + 1].x - x <= min_dist))
586 point_valid = FALSE;
587
588 // for all points, x coordinate of point must be strictly larger than
589 // the x coordinate of the previous point
590 if((selected > 0 && (curve[selected - 1].x >= x)) || (selected < nodes - 1 && (curve[selected + 1].x <= x)))
591 {
592 point_valid = FALSE;
593 }
594
595 return point_valid;
596}
597
598static gboolean _move_point_internal(dt_iop_module_t *self, GtkWidget *widget, float dx, float dy, guint state)
599{
602
603 const int ch = g->channel;
604 dt_iop_rgbcurve_node_t *curve = p->curve_nodes[ch];
605
606 const float new_x = CLAMP(curve[g->selected].x + dx, 0.0f, 1.0f);
607 const float new_y = CLAMP(curve[g->selected].y + dy, 0.0f, 1.0f);
608
609 gtk_widget_queue_draw(widget);
610
611 if(_sanity_check(new_x, g->selected, p->curve_num_nodes[ch], p->curve_nodes[ch]))
612 {
613 curve[g->selected].x = new_x;
614 curve[g->selected].y = new_y;
615
616 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
617 }
618
619 return TRUE;
620}
621
622#define RGBCURVE_DEFAULT_STEP (0.001f)
623
624static gboolean _area_scrolled_callback(GtkWidget *widget, GdkEventScroll *event, dt_iop_module_t *self)
625{
628
629 gdouble delta_y;
630
631 // if autoscale is on: do not modify g and b curves
632 if((p->curve_autoscale != DT_S_SCALE_MANUAL_RGB) && g->channel != DT_IOP_RGBCURVE_R) return TRUE;
633
634 if(g->selected < 0) return TRUE;
635
637
638 if(dt_gui_get_scroll_delta(event, &delta_y))
639 {
640 delta_y *= -RGBCURVE_DEFAULT_STEP;
641 return _move_point_internal(self, widget, 0.0, delta_y, event->state);
642 }
643
644 return TRUE;
645}
646
647static gboolean _area_key_press_callback(GtkWidget *widget, GdkEventKey *event, dt_iop_module_t *self)
648{
651
652 // if autoscale is on: do not modify g and b curves
653 if((p->curve_autoscale != DT_S_SCALE_MANUAL_RGB) && g->channel != DT_IOP_RGBCURVE_R) return TRUE;
654
655 if(g->selected < 0) return FALSE;
656
657 guint key = dt_keys_mainpad_alternatives(event->keyval);
658 int handled = 0;
659 float dx = 0.0f, dy = 0.0f;
660 if(key == GDK_KEY_Up)
661 {
662 handled = 1;
664 }
665 else if(key == GDK_KEY_Down)
666 {
667 handled = 1;
669 }
670 else if(key == GDK_KEY_Right)
671 {
672 handled = 1;
674 }
675 else if(key == GDK_KEY_Left)
676 {
677 handled = 1;
679 }
680
681 if(!handled) return FALSE;
682
684 return _move_point_internal(self, widget, dx, dy, event->state);
685}
686
687#undef RGBCURVE_DEFAULT_STEP
688
689static gboolean _area_enter_notify_callback(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
690{
691 gtk_widget_queue_draw(widget);
692 return TRUE;
693}
694
695static gboolean _area_leave_notify_callback(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
696{
697 gtk_widget_queue_draw(widget);
698 return TRUE;
699}
700
701static gboolean _area_draw_callback(GtkWidget *widget, cairo_t *crf, dt_iop_module_t *self)
702{
705 dt_develop_t *dev = self->dev;
706
707 const int ch = g->channel;
708 const int nodes = p->curve_num_nodes[ch];
709 const int autoscale = p->curve_autoscale;
710 dt_iop_rgbcurve_node_t *curve_nodes = p->curve_nodes[ch];
711
712 if(g->minmax_curve_type[ch] != p->curve_type[ch] || g->minmax_curve_nodes[ch] != p->curve_num_nodes[ch])
713 {
714 dt_draw_curve_destroy(g->minmax_curve[ch]);
715 g->minmax_curve[ch] = dt_draw_curve_new(0.0, 1.0, p->curve_type[ch]);
716 g->minmax_curve_nodes[ch] = p->curve_num_nodes[ch];
717 g->minmax_curve_type[ch] = p->curve_type[ch];
718 for(int k = 0; k < p->curve_num_nodes[ch]; k++)
719 (void)dt_draw_curve_add_point(g->minmax_curve[ch], p->curve_nodes[ch][k].x, p->curve_nodes[ch][k].y);
720 }
721 else
722 {
723 for(int k = 0; k < p->curve_num_nodes[ch]; k++)
724 dt_draw_curve_set_point(g->minmax_curve[ch], k, p->curve_nodes[ch][k].x, p->curve_nodes[ch][k].y);
725 }
726 dt_draw_curve_t *minmax_curve = g->minmax_curve[ch];
727 dt_draw_curve_calc_values(minmax_curve, 0.0, 1.0, DT_IOP_RGBCURVE_RES, NULL, g->draw_ys);
728
729 float unbounded_coeffs[3];
730 const float xm = curve_nodes[nodes - 1].x;
731 {
732 const float x[4] = { 0.7f * xm, 0.8f * xm, 0.9f * xm, 1.0f * xm };
733 const float y[4] = { g->draw_ys[CLAMP((int)(x[0] * DT_IOP_RGBCURVE_RES), 0, DT_IOP_RGBCURVE_RES - 1)],
734 g->draw_ys[CLAMP((int)(x[1] * DT_IOP_RGBCURVE_RES), 0, DT_IOP_RGBCURVE_RES - 1)],
735 g->draw_ys[CLAMP((int)(x[2] * DT_IOP_RGBCURVE_RES), 0, DT_IOP_RGBCURVE_RES - 1)],
736 g->draw_ys[CLAMP((int)(x[3] * DT_IOP_RGBCURVE_RES), 0, DT_IOP_RGBCURVE_RES - 1)] };
738 }
739
740 const int inset = DT_GUI_CURVE_EDITOR_INSET;
741 GtkAllocation allocation;
742 gtk_widget_get_allocation(widget, &allocation);
743 int width = allocation.width, height = allocation.height;
744 cairo_surface_t *cst = dt_cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
745 cairo_t *cr = cairo_create(cst);
746
747 // clear bg
748 cairo_set_source_rgb(cr, .2, .2, .2);
749 cairo_paint(cr);
750
751 cairo_translate(cr, inset, inset);
752 width -= 2 * inset;
753 height -= 2 * inset;
754 char text[256];
755
756 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.0));
757 cairo_set_source_rgb(cr, .1, .1, .1);
758 cairo_rectangle(cr, 0, 0, width, height);
759 cairo_stroke(cr);
760
761 cairo_set_source_rgb(cr, .3, .3, .3);
762 cairo_rectangle(cr, 0, 0, width, height);
763 cairo_fill(cr);
764
765 // draw grid
766 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(.4));
767 cairo_set_source_rgb(cr, .1, .1, .1);
768
769 cairo_translate(cr, 0, height);
770
771 dt_draw_grid_zoomed(cr, 4, 0.f, 0.f, 1.f, 1.f, width, height, g->zoom_factor, g->offset_x, g->offset_y);
772
773 const double dashed[] = { 4.0, 4.0 };
774 const int len = sizeof(dashed) / sizeof(dashed[0]);
775 cairo_set_dash(cr, dashed, len, 0);
776 dt_draw_grid_zoomed(cr, 8, 0.f, 0.f, 1.f, 1.f, width, height, g->zoom_factor, g->offset_x, g->offset_y);
777 cairo_set_dash(cr, dashed, 0, 0);
778
779 // draw identity line
780 cairo_move_to(cr, _curve_to_mouse(0.f, g->zoom_factor, g->offset_x) * width,
781 _curve_to_mouse(0.f, g->zoom_factor, g->offset_y) * -height);
782 cairo_line_to(cr, _curve_to_mouse(1.f, g->zoom_factor, g->offset_x) * width,
783 _curve_to_mouse(1.f, g->zoom_factor, g->offset_y) * -height);
784 cairo_stroke(cr);
785
786 // if autoscale is on: do not display g and b curves
787 if((autoscale != DT_S_SCALE_MANUAL_RGB) && ch != DT_IOP_RGBCURVE_R) goto finally;
788
789 // draw nodes positions
790 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.));
791 cairo_set_source_rgb(cr, 0.6, 0.6, 0.6);
792
793 for(int k = 0; k < nodes; k++)
794 {
795 const float x = _curve_to_mouse(curve_nodes[k].x, g->zoom_factor, g->offset_x),
796 y = _curve_to_mouse(curve_nodes[k].y, g->zoom_factor, g->offset_y);
797
798 cairo_arc(cr, x * width, -y * height, DT_PIXEL_APPLY_DPI(3), 0, 2. * M_PI);
799 cairo_stroke(cr);
800 }
801
802 // draw selected cursor
803 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.));
804
805 if(g->selected >= 0)
806 {
807 cairo_set_source_rgb(cr, .9, .9, .9);
808 const float x = _curve_to_mouse(curve_nodes[g->selected].x, g->zoom_factor, g->offset_x),
809 y = _curve_to_mouse(curve_nodes[g->selected].y, g->zoom_factor, g->offset_y);
810
811 cairo_arc(cr, x * width, -y * height, DT_PIXEL_APPLY_DPI(4), 0, 2. * M_PI);
812 cairo_stroke(cr);
813 }
814
815 // draw histogram in background
816 // only if module is enabled
817 if(self->enabled)
818 {
819 const uint32_t *hist = self->histogram;
820 const gboolean is_linear = FALSE;
821 float hist_max;
822
823 if(autoscale == DT_S_SCALE_AUTOMATIC_RGB)
825 else
826 hist_max = self->histogram_max[ch];
827
828 if (!is_linear)
829 hist_max = logf(1.0 + hist_max);
830
831 if(!IS_NULL_PTR(hist) && hist_max > 0.0f)
832 {
833 cairo_push_group_with_content(cr, CAIRO_CONTENT_COLOR);
834 cairo_scale(cr, width / 255.0, -(height - DT_PIXEL_APPLY_DPI(5)) / hist_max);
835
836 if(autoscale == DT_S_SCALE_AUTOMATIC_RGB)
837 {
838 cairo_set_operator(cr, CAIRO_OPERATOR_ADD);
840 {
841 set_color(cr, dt_bauhaus_get_global()->graph_colors[k]);
842 dt_draw_histogram_8_zoomed(cr, hist, 4, k, g->zoom_factor, g->offset_x * 255.0, g->offset_y * hist_max,
843 is_linear);
844 }
845 }
846 else if(autoscale == DT_S_SCALE_MANUAL_RGB)
847 {
848 set_color(cr, dt_bauhaus_get_global()->graph_colors[ch]);
849 dt_draw_histogram_8_zoomed(cr, hist, 4, ch, g->zoom_factor, g->offset_x * 255.0, g->offset_y * hist_max,
850 is_linear);
851 }
852
853 cairo_pop_group_to_source(cr);
854 cairo_paint_with_alpha(cr, 0.2);
855 }
856
858 {
859 const dt_iop_order_iccprofile_info_t *const work_profile
861
862 dt_aligned_pixel_t picker_mean, picker_min, picker_max;
863
864 // the global live samples ...
865 GSList *samples = self->dev->color_picker.samples;
866 if(samples)
867 {
869 if(!IS_NULL_PTR(work_profile) && !IS_NULL_PTR(display_profile))
870 {
871 for(; samples; samples = g_slist_next(samples))
872 {
873 dt_colorpicker_sample_t *sample = samples->data;
874
875 // this functions need a 4c image
876 for(int k = 0; k < 3; k++)
877 {
878 picker_mean[k] = sample->scope[DT_LIB_COLORPICKER_STATISTIC_MEAN][k];
879 picker_min[k] = sample->scope[DT_LIB_COLORPICKER_STATISTIC_MIN][k];
880 picker_max[k] = sample->scope[DT_LIB_COLORPICKER_STATISTIC_MAX][k];
881 }
882 picker_mean[3] = picker_min[3] = picker_max[3] = 1.f;
883
884 dt_ioppr_transform_image_colorspace_rgb(picker_mean, picker_mean, 1, 1, display_profile,
885 work_profile, "rgb curve");
886 dt_ioppr_transform_image_colorspace_rgb(picker_min, picker_min, 1, 1, display_profile, work_profile,
887 "rgb curve");
888 dt_ioppr_transform_image_colorspace_rgb(picker_max, picker_max, 1, 1, display_profile, work_profile,
889 "rgb curve");
890
891 picker_scale(picker_mean, picker_mean, p, work_profile);
892 picker_scale(picker_min, picker_min, p, work_profile);
893 picker_scale(picker_max, picker_max, p, work_profile);
894
895 // Convert abcissa to log coordinates if needed
896 picker_min[ch] = _curve_to_mouse(picker_min[ch], g->zoom_factor, g->offset_x);
897 picker_max[ch] = _curve_to_mouse(picker_max[ch], g->zoom_factor, g->offset_x);
898 picker_mean[ch] = _curve_to_mouse(picker_mean[ch], g->zoom_factor, g->offset_x);
899
900 cairo_set_source_rgba(cr, 0.5, 0.7, 0.5, 0.15);
901 cairo_rectangle(cr, width * picker_min[ch], 0, width * fmax(picker_max[ch] - picker_min[ch], 0.0f),
902 -height);
903 cairo_fill(cr);
904 cairo_set_source_rgba(cr, 0.5, 0.7, 0.5, 0.5);
905 cairo_move_to(cr, width * picker_mean[ch], 0);
906 cairo_line_to(cr, width * picker_mean[ch], -height);
907 cairo_stroke(cr);
908 }
909 }
910 }
911
912 // ... and the local sample
913 if(self->picked_color_max[ch] >= 0.0f)
914 {
915 PangoLayout *layout;
916 PangoRectangle ink;
917 PangoFontDescription *desc = pango_font_description_copy_static(dt_bauhaus_get_global()->pango_font_desc);
918 pango_font_description_set_weight(desc, PANGO_WEIGHT_BOLD);
919 pango_font_description_set_absolute_size(desc, PANGO_SCALE);
920 layout = pango_cairo_create_layout(cr);
921 pango_layout_set_font_description(layout, desc);
922
923 picker_scale(self->picked_color, picker_mean, p, work_profile);
924 picker_scale(self->picked_color_min, picker_min, p, work_profile);
925 picker_scale(self->picked_color_max, picker_max, p, work_profile);
926
927 // scale conservatively to 100% of width:
928 snprintf(text, sizeof(text), "100.00 / 100.00 ( +100.00)");
929 pango_layout_set_text(layout, text, -1);
930 pango_layout_get_pixel_extents(layout, &ink, NULL);
931 pango_font_description_set_absolute_size(desc, width * 1.0 / ink.width * PANGO_SCALE);
932 pango_layout_set_font_description(layout, desc);
933
934 picker_min[ch] = _curve_to_mouse(picker_min[ch], g->zoom_factor, g->offset_x);
935 picker_max[ch] = _curve_to_mouse(picker_max[ch], g->zoom_factor, g->offset_x);
936 picker_mean[ch] = _curve_to_mouse(picker_mean[ch], g->zoom_factor, g->offset_x);
937
938 cairo_set_source_rgba(cr, 0.7, 0.5, 0.5, 0.33);
939 cairo_rectangle(cr, width * picker_min[ch], 0, width * fmax(picker_max[ch] - picker_min[ch], 0.0f),
940 -height);
941 cairo_fill(cr);
942 cairo_set_source_rgba(cr, 0.9, 0.7, 0.7, 0.5);
943 cairo_move_to(cr, width * picker_mean[ch], 0);
944 cairo_line_to(cr, width * picker_mean[ch], -height);
945 cairo_stroke(cr);
946
947 picker_scale(self->picked_color, picker_mean, p, work_profile);
948 picker_scale(self->picked_output_color, picker_min, p, work_profile);
949 snprintf(text, sizeof(text), "%.1f \342\206\222 %.1f", picker_mean[ch] * 255.f, picker_min[ch] * 255.f);
950
951 cairo_set_source_rgb(cr, 0.1, 0.1, 0.1);
952 cairo_set_font_size(cr, DT_PIXEL_APPLY_DPI(0.04) * height);
953 pango_layout_set_text(layout, text, -1);
954 pango_layout_get_pixel_extents(layout, &ink, NULL);
955 cairo_move_to(cr, 0.02f * width, -0.94 * height - ink.height - ink.y);
956 pango_cairo_show_layout(cr, layout);
957 cairo_stroke(cr);
958 pango_font_description_free(desc);
959 g_object_unref(layout);
960 }
961 }
962 }
963
964 // draw zoom info
965 if(g->selected >= 0)
966 {
967 // draw information about current selected node
968 PangoLayout *layout;
969 PangoRectangle ink;
970 PangoFontDescription *desc = pango_font_description_copy_static(dt_bauhaus_get_global()->pango_font_desc);
971 pango_font_description_set_weight(desc, PANGO_WEIGHT_BOLD);
972 pango_font_description_set_absolute_size(desc, PANGO_SCALE);
973 layout = pango_cairo_create_layout(cr);
974 pango_layout_set_font_description(layout, desc);
975
976 // scale conservatively to 100% of width:
977 snprintf(text, sizeof(text), "100.00 / 100.00 ( +100.00)");
978 pango_layout_set_text(layout, text, -1);
979 pango_layout_get_pixel_extents(layout, &ink, NULL);
980 pango_font_description_set_absolute_size(desc, width * 1.0 / ink.width * PANGO_SCALE);
981 pango_layout_set_font_description(layout, desc);
982
983 const float min_scale_value = 0.0f;
984 const float max_scale_value = 255.0f;
985
986 const float x_node_value = curve_nodes[g->selected].x * (max_scale_value - min_scale_value) + min_scale_value;
987 const float y_node_value = curve_nodes[g->selected].y * (max_scale_value - min_scale_value) + min_scale_value;
988 const float d_node_value = y_node_value - x_node_value;
989 snprintf(text, sizeof(text), "%.1f / %.1f ( %+.1f)", x_node_value, y_node_value, d_node_value);
990
991 cairo_set_source_rgb(cr, 0.1, 0.1, 0.1);
992 pango_layout_set_text(layout, text, -1);
993 pango_layout_get_pixel_extents(layout, &ink, NULL);
994 cairo_move_to(cr, 0.98f * width - ink.width - ink.x, -0.02 * height - ink.height - ink.y);
995 pango_cairo_show_layout(cr, layout);
996 cairo_stroke(cr);
997 pango_font_description_free(desc);
998 g_object_unref(layout);
999
1000 // enlarge selected node
1001 cairo_set_source_rgb(cr, .9, .9, .9);
1002 const float x = _curve_to_mouse(curve_nodes[g->selected].x, g->zoom_factor, g->offset_x),
1003 y = _curve_to_mouse(curve_nodes[g->selected].y, g->zoom_factor, g->offset_y);
1004
1005 cairo_arc(cr, x * width, -y * height, DT_PIXEL_APPLY_DPI(4), 0, 2. * M_PI);
1006 cairo_stroke(cr);
1007 }
1008
1009 // draw curve
1010 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2.));
1011 cairo_set_source_rgb(cr, .9, .9, .9);
1012
1013 const float y_offset = _curve_to_mouse(g->draw_ys[0], g->zoom_factor, g->offset_y);
1014 cairo_move_to(cr, 0, -height * y_offset);
1015
1016 for(int k = 1; k < DT_IOP_RGBCURVE_RES; k++)
1017 {
1018 const float xx = k / (DT_IOP_RGBCURVE_RES - 1.0f);
1019 float yy;
1020
1021 if(xx > xm)
1022 {
1024 }
1025 else
1026 {
1027 yy = g->draw_ys[k];
1028 }
1029
1030 const float x = _curve_to_mouse(xx, g->zoom_factor, g->offset_x),
1031 y = _curve_to_mouse(yy, g->zoom_factor, g->offset_y);
1032
1033 cairo_line_to(cr, x * width, -height * y);
1034 }
1035 cairo_stroke(cr);
1036
1037finally:
1038 cairo_destroy(cr);
1039 cairo_set_source_surface(crf, cst, 0, 0);
1040 cairo_paint(crf);
1041 cairo_surface_destroy(cst);
1042 return TRUE;
1043}
1044
1045static gboolean _area_motion_notify_callback(GtkWidget *widget, GdkEventMotion *event, dt_iop_module_t *self)
1046{
1049
1050 const int inset = DT_GUI_CURVE_EDITOR_INSET;
1051
1052 const int ch = g->channel;
1053 const int nodes = p->curve_num_nodes[ch];
1054 dt_iop_rgbcurve_node_t *curve_nodes = p->curve_nodes[ch];
1055
1056 // if autoscale is on: do not modify g and b curves
1057 if((p->curve_autoscale != DT_S_SCALE_MANUAL_RGB) && g->channel != DT_IOP_RGBCURVE_R) goto finally;
1058
1059 GtkAllocation allocation;
1060 gtk_widget_get_allocation(widget, &allocation);
1061 const int height = allocation.height - 2 * inset, width = allocation.width - 2 * inset;
1062
1063 const double old_m_x = g->mouse_x;
1064 const double old_m_y = g->mouse_y;
1065
1066 g->mouse_x = CLAMP(event->x - inset, 0, width) / (float)width;
1067 g->mouse_y = 1.0 - CLAMP(event->y - inset, 0, height) / (float)height;
1068
1069 const float mx = g->mouse_x;
1070 const float my = g->mouse_y;
1071 const float linx = _mouse_to_curve(mx, g->zoom_factor, g->offset_x),
1072 liny = _mouse_to_curve(my, g->zoom_factor, g->offset_y);
1073
1074 if(event->state & GDK_BUTTON1_MASK)
1075 {
1076 // got a vertex selected:
1077 if(g->selected >= 0)
1078 {
1079 // this is used to translate mause position in loglogscale to make this behavior unified with linear scale.
1080 const float translate_mouse_x
1081 = old_m_x - _curve_to_mouse(curve_nodes[g->selected].x, g->zoom_factor, g->offset_x);
1082 const float translate_mouse_y
1083 = old_m_y - _curve_to_mouse(curve_nodes[g->selected].y, g->zoom_factor, g->offset_y);
1084 // dx & dy are in linear coordinates
1085 const float dx = _mouse_to_curve(g->mouse_x - translate_mouse_x, g->zoom_factor, g->offset_x)
1086 - _mouse_to_curve(old_m_x - translate_mouse_x, g->zoom_factor, g->offset_x);
1087 const float dy = _mouse_to_curve(g->mouse_y - translate_mouse_y, g->zoom_factor, g->offset_y)
1088 - _mouse_to_curve(old_m_y - translate_mouse_y, g->zoom_factor, g->offset_y);
1089
1091 return _move_point_internal(self, widget, dx, dy, event->state);
1092 }
1093 else if(nodes < DT_IOP_RGBCURVE_MAXNODES && g->selected >= -1)
1094 {
1096 // no vertex was close, create a new one!
1097 g->selected = _add_node(curve_nodes, &p->curve_num_nodes[ch], linx, liny);
1098 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1099 }
1100 }
1101 else
1102 {
1103 // minimum area around the node to select it:
1104 float min = .04f * .04f; // comparing against square
1105 int nearest = -1;
1106 for(int k = 0; k < nodes; k++)
1107 {
1108 const float dist = (my - _curve_to_mouse(curve_nodes[k].y, g->zoom_factor, g->offset_y))
1109 * (my - _curve_to_mouse(curve_nodes[k].y, g->zoom_factor, g->offset_y))
1110 + (mx - _curve_to_mouse(curve_nodes[k].x, g->zoom_factor, g->offset_x))
1111 * (mx - _curve_to_mouse(curve_nodes[k].x, g->zoom_factor, g->offset_x));
1112 if(dist < min)
1113 {
1114 min = dist;
1115 nearest = k;
1116 }
1117 }
1118 g->selected = nearest;
1119 }
1120finally:
1121 if(g->selected >= 0) gtk_widget_grab_focus(widget);
1122 gtk_widget_queue_draw(widget);
1123 return TRUE;
1124}
1125
1126static gboolean _area_button_press_callback(GtkWidget *widget, GdkEventButton *event, dt_iop_module_t *self)
1127{
1131
1132 const int ch = g->channel;
1133 const int autoscale = p->curve_autoscale;
1134 const int nodes = p->curve_num_nodes[ch];
1135 dt_iop_rgbcurve_node_t *curve_nodes = p->curve_nodes[ch];
1136
1137 if(event->button == 1)
1138 {
1139 if(event->type == GDK_BUTTON_PRESS && dt_modifier_is(event->state, DT_PRIMARY_MASK)
1140 && nodes < DT_IOP_RGBCURVE_MAXNODES && g->selected == -1)
1141 {
1142 // if we are not on a node -> add a new node at the current x of the pointer and y of the curve at that x
1143 const int inset = DT_GUI_CURVE_EDITOR_INSET;
1144 GtkAllocation allocation;
1145 gtk_widget_get_allocation(widget, &allocation);
1146 const int width = allocation.width - 2 * inset;
1147 const int height = allocation.height - 2 * inset;
1148
1149 g->mouse_x = CLAMP(event->x - inset, 0, width) / (float)width;
1150 g->mouse_y = 1.0 - CLAMP(event->y - inset, 0, height) / (float)height;
1151
1152 const float mx = g->mouse_x;
1153 const float linx = _mouse_to_curve(mx, g->zoom_factor, g->offset_x);
1154
1155 // don't add a node too close to others in x direction, it can crash dt
1156 int selected = -1;
1157 if(curve_nodes[0].x > mx)
1158 selected = 0;
1159 else
1160 {
1161 for(int k = 1; k < nodes; k++)
1162 {
1163 if(curve_nodes[k].x > mx)
1164 {
1165 selected = k;
1166 break;
1167 }
1168 }
1169 }
1170 if(selected == -1) selected = nodes;
1171
1172 // evaluate the curve at the current x position
1173 const float y = dt_draw_curve_calc_value(g->minmax_curve[ch], linx);
1174
1175 if(y >= 0.0f && y <= 1.0f) // never add something outside the viewport, you couldn't change it afterwards
1176 {
1177 // create a new node
1178 selected = _add_node(curve_nodes, &p->curve_num_nodes[ch], linx, y);
1179
1180 // maybe set the new one as being selected
1181 const float min = .04f * .04f; // comparing against square
1182 for(int k = 0; k < nodes; k++)
1183 {
1184 const float other_y = _curve_to_mouse(curve_nodes[k].y, g->zoom_factor, g->offset_y);
1185 const float dist = (y - other_y) * (y - other_y);
1186 if(dist < min) g->selected = selected;
1187 }
1188
1190 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1191 gtk_widget_queue_draw(self->gui->widget);
1192 }
1193
1194 return TRUE;
1195 }
1196 else if(event->type == GDK_2BUTTON_PRESS)
1197 {
1198 // reset current curve
1199 // if autoscale is on: allow only reset of L curve
1200 if(!((autoscale != DT_S_SCALE_MANUAL_RGB) && ch != DT_IOP_RGBCURVE_R))
1201 {
1202 p->curve_num_nodes[ch] = d->curve_num_nodes[ch];
1203 p->curve_type[ch] = d->curve_type[ch];
1204 for(int k = 0; k < d->curve_num_nodes[ch]; k++)
1205 {
1206 p->curve_nodes[ch][k].x = d->curve_nodes[ch][k].x;
1207 p->curve_nodes[ch][k].y = d->curve_nodes[ch][k].y;
1208 }
1209 g->selected = -2; // avoid motion notify re-inserting immediately.
1210 dt_bauhaus_combobox_set(g->interpolator, p->curve_type[DT_IOP_RGBCURVE_R]);
1212 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1213 gtk_widget_queue_draw(self->gui->widget);
1214 }
1215 else
1216 {
1217 if(ch != DT_IOP_RGBCURVE_R)
1218 {
1219 p->curve_autoscale = DT_S_SCALE_MANUAL_RGB;
1220 g->selected = -2; // avoid motion notify re-inserting immediately.
1221 dt_bauhaus_combobox_set(g->autoscale, 1);
1223 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1224 gtk_widget_queue_draw(self->gui->widget);
1225 }
1226 }
1227 return TRUE;
1228 }
1229 }
1230 else if(event->button == 3 && g->selected >= 0)
1231 {
1232 if(g->selected == 0 || g->selected == nodes - 1)
1233 {
1234 const float reset_value = g->selected == 0 ? 0.f : 1.f;
1235 curve_nodes[g->selected].y = curve_nodes[g->selected].x = reset_value;
1237 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1238 gtk_widget_queue_draw(self->gui->widget);
1239 return TRUE;
1240 }
1241
1242 for(int k = g->selected; k < nodes - 1; k++)
1243 {
1244 curve_nodes[k].x = curve_nodes[k + 1].x;
1245 curve_nodes[k].y = curve_nodes[k + 1].y;
1246 }
1247 curve_nodes[nodes - 1].x = curve_nodes[nodes - 1].y = 0;
1248 g->selected = -2; // avoid re-insertion of that point immediately after this
1249 p->curve_num_nodes[ch]--;
1251 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1252 gtk_widget_queue_draw(self->gui->widget);
1253 return TRUE;
1254 }
1255 return FALSE;
1256}
1257
1258void gui_reset(struct dt_iop_module_t *self)
1259{
1262
1263 g->channel = DT_IOP_RGBCURVE_R;
1264 g->selected = -1;
1265 g->offset_x = g->offset_y = 0.f;
1266 g->zoom_factor = 1.f;
1267
1268 dt_bauhaus_combobox_set(g->interpolator, p->curve_type[DT_IOP_RGBCURVE_R]);
1269
1270 gtk_widget_queue_draw(self->gui->widget);
1271}
1272
1274{
1276 if(!IS_NULL_PTR(g))
1277 {
1278 if(!g->channel)
1279 g->channel = DT_IOP_RGBCURVE_R;
1280 g->mouse_x = g->mouse_y = -1.0;
1281 g->selected = -1;
1282 g->offset_x = g->offset_y = 0.f;
1283 g->zoom_factor = 1.f;
1284 }
1285}
1286
1287void gui_init(struct dt_iop_module_t *self)
1288{
1291
1292 for(int ch = 0; ch < DT_IOP_RGBCURVE_MAX_CHANNELS; ch++)
1293 {
1294 g->minmax_curve[ch] = dt_draw_curve_new(0.0, 1.0, p->curve_type[ch]);
1295 g->minmax_curve_nodes[ch] = p->curve_num_nodes[ch];
1296 g->minmax_curve_type[ch] = p->curve_type[ch];
1297 for(int k = 0; k < p->curve_num_nodes[ch]; k++)
1298 (void)dt_draw_curve_add_point(g->minmax_curve[ch], p->curve_nodes[ch][k].x, p->curve_nodes[ch][k].y);
1299 }
1300
1301 g->channel = DT_IOP_RGBCURVE_R;
1302 self->gui->timeout_handle = 0;
1303 change_image(self);
1304
1305 g->autoscale = dt_bauhaus_combobox_from_params(self, "curve_autoscale");
1306 gtk_widget_set_tooltip_text(g->autoscale, _("choose between linked and independent channels."));
1307
1308 GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
1309
1310 g->channel_tabs = GTK_NOTEBOOK(gtk_notebook_new());
1311 dt_ui_notebook_page(g->channel_tabs, N_("R"), _("curve nodes for r channel"));
1312 dt_ui_notebook_page(g->channel_tabs, N_("G"), _("curve nodes for g channel"));
1313 dt_ui_notebook_page(g->channel_tabs, N_("B"), _("curve nodes for b channel"));
1314 g_signal_connect(G_OBJECT(g->channel_tabs), "switch_page", G_CALLBACK(tab_switch_callback), self);
1315 dt_ui_notebook_set_picker_owner(g->channel_tabs, self);
1316 gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(g->channel_tabs), TRUE, TRUE, 0);
1317 gtk_box_pack_start(GTK_BOX(hbox), gtk_grid_new(), TRUE, TRUE, 0);
1318
1319 // color pickers
1320 g->colorpicker = dt_color_picker_new(self, DT_COLOR_PICKER_POINT_AREA, hbox);
1321 gtk_widget_set_tooltip_text(g->colorpicker, _("pick GUI color from image\nctrl+click or right-click to select an area"));
1322 g->colorpicker_set_values = dt_color_picker_new(self, DT_COLOR_PICKER_AREA, hbox);
1323 dtgtk_togglebutton_set_paint(DTGTK_TOGGLEBUTTON(g->colorpicker_set_values),
1325
1326 gtk_widget_set_size_request(g->colorpicker_set_values, DT_PIXEL_APPLY_DPI(14), DT_PIXEL_APPLY_DPI(14));
1327 gtk_widget_set_tooltip_text(g->colorpicker_set_values, _("create a curve based on an area from the image\n"
1328 "drag to create a flat curve\n"
1329 "ctrl+drag to create a positive curve\n"
1330 "shift+drag to create a negative curve"));
1331
1332 GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1333 gtk_box_pack_start(GTK_BOX(self->gui->widget), vbox, FALSE, FALSE, 0);
1334 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(hbox), TRUE, TRUE, 0);
1335
1336 g->area = GTK_DRAWING_AREA(gtk_drawing_area_new());
1337 gtk_widget_set_hexpand(GTK_WIDGET(g->area), TRUE);
1338 g_object_set_data(G_OBJECT(g->area), "iop-instance", self);
1339 gtk_box_pack_start(GTK_BOX(vbox),
1340 dt_ui_resizable_drawing_area(GTK_WIDGET(g->area),
1341 "plugins/darkroom/rgbcurve/graphheight", 280, 100),
1342 FALSE, FALSE, 0);
1343
1344 // FIXME: that tooltip goes in the way of the numbers when you hover a node to get a reading
1345 // gtk_widget_set_tooltip_text(GTK_WIDGET(g->area), _("double click to reset curve"));
1346
1347 gtk_widget_add_events(GTK_WIDGET(g->area), GDK_POINTER_MOTION_MASK | dt_widget_scroll_mask()
1348 | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
1349 | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK);
1350 gtk_widget_set_can_focus(GTK_WIDGET(g->area), TRUE);
1351 g_signal_connect(G_OBJECT(g->area), "draw", G_CALLBACK(_area_draw_callback), self);
1352 g_signal_connect(G_OBJECT(g->area), "button-press-event", G_CALLBACK(_area_button_press_callback), self);
1353 g_signal_connect(G_OBJECT(g->area), "motion-notify-event", G_CALLBACK(_area_motion_notify_callback), self);
1354 g_signal_connect(G_OBJECT(g->area), "leave-notify-event", G_CALLBACK(_area_leave_notify_callback), self);
1355 g_signal_connect(G_OBJECT(g->area), "enter-notify-event", G_CALLBACK(_area_enter_notify_callback), self);
1356 g_signal_connect(G_OBJECT(g->area), "configure-event", G_CALLBACK(_area_resized_callback), self);
1357 g_signal_connect(G_OBJECT(g->area), "scroll-event", G_CALLBACK(_area_scrolled_callback), self);
1358 g_signal_connect(G_OBJECT(g->area), "key-press-event", G_CALLBACK(_area_key_press_callback), self);
1359
1360 /* From src/common/curve_tools.h :
1361 #define CUBIC_SPLINE 0
1362 #define CATMULL_ROM 1
1363 #define MONOTONE_HERMITE 2
1364 */
1366 dt_bauhaus_widget_set_label(g->interpolator, N_("interpolation method"));
1367 dt_bauhaus_combobox_add(g->interpolator, _("cubic spline"));
1368 dt_bauhaus_combobox_add(g->interpolator, _("centripetal spline"));
1369 dt_bauhaus_combobox_add(g->interpolator, _("monotonic spline"));
1370 gtk_box_pack_start(GTK_BOX(self->gui->widget), g->interpolator, TRUE, TRUE, 0);
1371 gtk_widget_set_tooltip_text(g->interpolator,
1372 _("change this method if you see oscillations or cusps in the curve\n"
1373 "- cubic spline is better to produce smooth curves but oscillates when nodes are too close\n"
1374 "- centripetal is better to avoids cusps and oscillations with close nodes but is less smooth\n"
1375 "- monotonic is better for accuracy of pure analytical functions (log, gamma, exp)\n"));
1376 g_signal_connect(G_OBJECT(g->interpolator), "value-changed", G_CALLBACK(interpolator_callback), self);
1377
1378 g->chk_compensate_middle_grey = dt_bauhaus_toggle_from_params(self, "compensate_middle_grey");
1379 gtk_widget_set_tooltip_text(g->chk_compensate_middle_grey, _("compensate middle gray"));
1380
1381 g->cmb_preserve_colors = dt_bauhaus_combobox_from_params(self, "preserve_colors");
1382 gtk_widget_set_tooltip_text(g->cmb_preserve_colors, _("method to preserve colors when applying contrast"));
1383}
1384
1385void gui_update(struct dt_iop_module_t *self)
1386{
1389
1390 dt_bauhaus_combobox_set(g->autoscale, p->curve_autoscale);
1391 dt_bauhaus_combobox_set(g->interpolator, p->curve_type[DT_IOP_RGBCURVE_R]);
1392 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->chk_compensate_middle_grey), p->compensate_middle_grey);
1393 dt_bauhaus_combobox_set(g->cmb_preserve_colors, p->preserve_colors);
1394
1395
1397
1398 // that's all, gui curve is read directly from params during expose event.
1399 gtk_widget_queue_draw(self->gui->widget);
1400}
1401
1403{
1405
1406 for(int k = 0; k < DT_IOP_RGBCURVE_MAX_CHANNELS; k++) dt_draw_curve_destroy(g->minmax_curve[k]);
1407
1408
1410}
1411
1413{
1414 // create part of the pixelpipe
1417 piece->data = (void *)d;
1418 piece->data_size = sizeof(dt_iop_rgbcurve_data_t);
1419 // Checked AFTER both piece->data and piece->data_size are set, deliberately.
1420 // dt_iop_init_pipe() disables the node for us when it sees data_size > 0 with a NULL
1421 // data -- returning before data_size is assigned skips that and leaves the node enabled
1422 // with no storage. dt_calloc_align() returns NULL on failure and pipe nodes are built
1423 // exactly when memory is tightest (Sentry 134134395 faulted in atrous's init_pipe).
1424 if(IS_NULL_PTR(piece->data)) return;
1425 memcpy(&d->params, default_params, sizeof(dt_iop_rgbcurve_params_t));
1426
1427 for(int ch = 0; ch < DT_IOP_RGBCURVE_MAX_CHANNELS; ch++)
1428 {
1429 d->curve[ch] = dt_draw_curve_new(0.0, 1.0, default_params->curve_type[ch]);
1430 d->params.curve_num_nodes[ch] = default_params->curve_num_nodes[ch];
1431 d->params.curve_type[ch] = default_params->curve_type[ch];
1432 for(int k = 0; k < default_params->curve_num_nodes[ch]; k++)
1433 (void)dt_draw_curve_add_point(d->curve[ch], default_params->curve_nodes[ch][k].x,
1434 default_params->curve_nodes[ch][k].y);
1435 }
1436
1437 for(int k = 0; k < 0x10000; k++) d->table[DT_IOP_RGBCURVE_R][k] = k / 0x10000; // identity for r
1438 for(int k = 0; k < 0x10000; k++) d->table[DT_IOP_RGBCURVE_G][k] = k / 0x10000; // identity for g
1439 for(int k = 0; k < 0x10000; k++) d->table[DT_IOP_RGBCURVE_B][k] = k / 0x10000; // identity for b
1440}
1441
1443{
1444 /* init_pipe() may have failed to allocate, and cleanup runs regardless. */
1445 if(IS_NULL_PTR(piece->data)) return;
1446 // clean up everything again.
1448 for(int ch = 0; ch < DT_IOP_RGBCURVE_MAX_CHANNELS; ch++) dt_draw_curve_destroy(d->curve[ch]);
1449 dt_free_align(piece->data);
1450 piece->data = NULL;
1451}
1452
1454{
1455 dt_iop_default_init(module);
1456
1457 module->request_histogram |= (DT_REQUEST_ON);
1458
1459 dt_iop_rgbcurve_params_t *d = module->default_params;
1460
1461 d->curve_nodes[0][1].x = d->curve_nodes[0][1].y =
1462 d->curve_nodes[1][1].x = d->curve_nodes[1][1].y =
1463 d->curve_nodes[2][1].x = d->curve_nodes[2][1].y = 1.0;
1464
1465 module->histogram_middle_grey = d->compensate_middle_grey;
1466}
1467
1468// this will be called from process*()
1469// it must be executed only if profile info has changed
1472{
1474
1476
1477 if(!IS_NULL_PTR(work_profile))
1478 {
1479 if(d->type_work == work_profile->type && strcmp(d->filename_work, work_profile->filename) == 0) return;
1480 }
1481
1482 if(!IS_NULL_PTR(work_profile) && d->params.compensate_middle_grey)
1483 {
1484 d->type_work = work_profile->type;
1485 g_strlcpy(d->filename_work, work_profile->filename, sizeof(d->filename_work));
1486
1487 for(int ch = 0; ch < DT_IOP_RGBCURVE_MAX_CHANNELS; ch++)
1488 {
1489 for(int k = 0; k < d->params.curve_num_nodes[ch]; k++)
1490 {
1491 curve_nodes[ch][k].x = dt_ioppr_uncompensate_middle_grey(d->params.curve_nodes[ch][k].x, work_profile);
1492 curve_nodes[ch][k].y = dt_ioppr_uncompensate_middle_grey(d->params.curve_nodes[ch][k].y, work_profile);
1493 }
1494 }
1495 }
1496 else
1497 {
1498 for(int ch = 0; ch < DT_IOP_RGBCURVE_MAX_CHANNELS; ch++)
1499 {
1500 memcpy(curve_nodes[ch], d->params.curve_nodes[ch], sizeof(dt_iop_rgbcurve_node_t) * DT_IOP_RGBCURVE_MAXNODES);
1501 }
1502 }
1503
1504 for(int ch = 0; ch < DT_IOP_RGBCURVE_MAX_CHANNELS; ch++)
1505 {
1506 // take care of possible change of curve type or number of nodes (not yet implemented in UI)
1507 if(d->curve_changed[ch])
1508 {
1509 dt_draw_curve_destroy(d->curve[ch]);
1510 d->curve[ch] = dt_draw_curve_new(0.0, 1.0, d->params.curve_type[ch]);
1511 for(int k = 0; k < d->params.curve_num_nodes[ch]; k++)
1512 (void)dt_draw_curve_add_point(d->curve[ch], curve_nodes[ch][k].x, curve_nodes[ch][k].y);
1513 }
1514 else
1515 {
1516 for(int k = 0; k < d->params.curve_num_nodes[ch]; k++)
1517 dt_draw_curve_set_point(d->curve[ch], k, curve_nodes[ch][k].x, curve_nodes[ch][k].y);
1518 }
1519
1520 dt_draw_curve_calc_values(d->curve[ch], 0.0f, 1.0f, 0x10000, NULL, d->table[ch]);
1521 }
1522
1523 // extrapolation for each curve (right hand side only):
1524 for(int ch = 0; ch < DT_IOP_RGBCURVE_MAX_CHANNELS; ch++)
1525 {
1526 const float xm_L = curve_nodes[ch][d->params.curve_num_nodes[ch] - 1].x;
1527 const float x_L[4] = { 0.7f * xm_L, 0.8f * xm_L, 0.9f * xm_L, 1.0f * xm_L };
1528 const float y_L[4] = { d->table[ch][CLAMP((int)(x_L[0] * 0x10000ul), 0, 0xffff)],
1529 d->table[ch][CLAMP((int)(x_L[1] * 0x10000ul), 0, 0xffff)],
1530 d->table[ch][CLAMP((int)(x_L[2] * 0x10000ul), 0, 0xffff)],
1531 d->table[ch][CLAMP((int)(x_L[3] * 0x10000ul), 0, 0xffff)] };
1532 dt_iop_estimate_exp(x_L, y_L, 4, d->unbounded_coeffs[ch]);
1533 }
1534}
1535
1538{
1541
1542 if(dt_dev_pixelpipe_has_preview_output(self->dev, pipe, NULL))
1543 piece->request_histogram |= (DT_REQUEST_ON);
1544 else
1545 piece->request_histogram &= ~(DT_REQUEST_ON);
1546
1547 for(int ch = 0; ch < DT_IOP_RGBCURVE_MAX_CHANNELS; ch++)
1548 d->curve_changed[ch]
1549 = (d->params.curve_type[ch] != p->curve_type[ch] || d->params.curve_nodes[ch] != p->curve_nodes[ch]);
1550
1551 memcpy(&d->params, p, sizeof(dt_iop_rgbcurve_params_t));
1552
1553 // working color profile
1554 d->type_work = DT_COLORSPACE_NONE;
1555 d->filename_work[0] = '\0';
1556}
1557
1558
1560int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
1561 void *const ovoid)
1562{
1563 const dt_iop_roi_t *const roi_out = &piece->roi_out;
1565
1566 const float *const restrict in = (float*)ivoid;
1567 float *const restrict out = (float*)ovoid;
1568
1569 dt_iop_rgbcurve_data_t *const restrict d = (dt_iop_rgbcurve_data_t *)(piece->data);
1570
1571 _generate_curve_lut(pipe, d);
1572
1573 const float xm_L = 1.0f / d->unbounded_coeffs[DT_IOP_RGBCURVE_R][0];
1574 const float xm_g = 1.0f / d->unbounded_coeffs[DT_IOP_RGBCURVE_G][0];
1575 const float xm_b = 1.0f / d->unbounded_coeffs[DT_IOP_RGBCURVE_B][0];
1576
1577 const int width = roi_out->width;
1578 const int height = roi_out->height;
1579 const size_t npixels = (size_t)width * height;
1580 const int autoscale = d->params.curve_autoscale;
1581 const _curve_table_ptr restrict table = d->table;
1582 const _coeffs_table_ptr restrict unbounded_coeffs = d->unbounded_coeffs;
1584 for(int y = 0; y < 4*npixels; y += 4)
1585 {
1586 if(autoscale == DT_S_SCALE_MANUAL_RGB)
1587 {
1588 out[y+0] = (in[y+0] < xm_L) ? table[DT_IOP_RGBCURVE_R][CLAMP((int)(in[y+0] * 0x10000ul), 0, 0xffff)]
1590 out[y+1] = (in[y+1] < xm_g) ? table[DT_IOP_RGBCURVE_G][CLAMP((int)(in[y+1] * 0x10000ul), 0, 0xffff)]
1592 out[y+2] = (in[y+2] < xm_b) ? table[DT_IOP_RGBCURVE_B][CLAMP((int)(in[y+2] * 0x10000ul), 0, 0xffff)]
1594 }
1595 else if(autoscale == DT_S_SCALE_AUTOMATIC_RGB)
1596 {
1597 if(d->params.preserve_colors == DT_RGB_NORM_NONE)
1598 {
1599 for(int c = 0; c < 3; c++)
1600 {
1601 out[y+c] = (in[y+c] < xm_L) ? table[DT_IOP_RGBCURVE_R][CLAMP((int)(in[y+c] * 0x10000ul), 0, 0xffff)]
1603 }
1604 }
1605 else
1606 {
1607 float ratio = 1.f;
1608 const float lum = dt_rgb_norm(in + y, d->params.preserve_colors, work_profile);
1609 if(lum > 0.f)
1610 {
1611 const float curve_lum = (lum < xm_L)
1612 ? table[DT_IOP_RGBCURVE_R][CLAMP((int)(lum * 0x10000ul), 0, 0xffff)]
1614 ratio = curve_lum / lum;
1615 }
1616 for(size_t c = 0; c < 3; c++)
1617 {
1618 out[y+c] = (ratio * in[y+c]);
1619 }
1620 }
1621 }
1622 out[y+3] = in[y+3];
1623 }
1624
1625 return 0;
1626}
1627
1628#undef DT_GUI_CURVE_EDITOR_INSET
1629#undef DT_IOP_RGBCURVE_RES
1630#undef DT_IOP_RGBCURVE_MAXNODES
1631#undef DT_IOP_RGBCURVE_MIN_X_DISTANCE
1632#undef DT_IOP_COLOR_ICC_LEN
1633
1634// clang-format off
1635// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
1636// vim: shiftwidth=2 expandtab tabstop=2 cindent
1637// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1638// clang-format on
Handle default and user-set shortcuts (accelerators)
#define DT_PRIMARY_MASK
static double dist(double x1, double y1, double x2, double y2)
Definition ashift_lsd.c:250
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
int dt_bauhaus_combobox_get(GtkWidget *widget)
Definition bauhaus.c:2136
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_combobox_new(dt_bauhaus_t *bh, dt_gui_module_t *self)
Definition bauhaus.c:1698
void dt_bauhaus_combobox_add(GtkWidget *widget, const char *text)
Definition bauhaus.c:1824
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
static float dt_camera_rgb_luminance(const float4 rgb)
@ IOP_CS_RGB
void dt_iop_color_picker_reset(dt_iop_module_t *module, gboolean keep)
GtkWidget * dt_color_picker_new(dt_iop_module_t *module, dt_iop_color_picker_kind_t kind, GtkWidget *w)
@ DT_COLOR_PICKER_AREA
@ DT_COLOR_PICKER_POINT_AREA
@ DT_LIB_COLORPICKER_STATISTIC_MAX
Definition colorpicker.h:46
@ DT_LIB_COLORPICKER_STATISTIC_MIN
Definition colorpicker.h:45
@ DT_LIB_COLORPICKER_STATISTIC_MEAN
Definition colorpicker.h:44
void dt_ioppr_transform_image_colorspace_rgb(const float *const restrict image_in, float *const restrict image_out, const int width, const int height, const dt_iop_order_iccprofile_info_t *const profile_info_from, const dt_iop_order_iccprofile_info_t *const profile_info_to, const char *message)
static const float x
static dt_aligned_pixel_t float *const const float unbounded_coeffs[3][3]
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
static const float const float const float min
const dt_colormatrix_t dt_aligned_pixel_t out
void dt_control_queue_redraw_widget(GtkWidget *widget)
threadsafe request of redraw of specific widget. Use this function if you need to redraw a specific w...
Definition control.c:969
#define CATMULL_ROM
Definition curve_tools.h:35
#define CUBIC_SPLINE
Definition curve_tools.h:34
#define MONOTONE_HERMITE
Definition curve_tools.h:36
struct dt_bauhaus_t * dt_bauhaus_get_global(void)
Definition darktable.c:646
static float dt_rgb_norm(const float4 in, const int norm, const int work_profile, constant dt_colorspaces_iccprofile_info_cl_t *profile_info, read_only image2d_t lut)
@ DT_RGB_NORM_NONE
#define dt_dev_add_history_item(dev, module, enable, redraw)
void dt_iop_params_t
Definition dev_history.h:43
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_CLONE_TARGETS__ dt_iop_order_iccprofile_info_t * dt_ioppr_get_iop_work_profile_info(struct dt_iop_module_t *module, GList *iop_list)
gboolean dt_dev_pixelpipe_has_preview_output(const dt_develop_t *dev, const dt_dev_pixelpipe_t *pipe, const dt_iop_roi_t *roi)
Definition develop.c:402
GHashTable * selected
set of checked row labels, mirrored to conf on every change
static void dt_draw_curve_calc_values(dt_draw_curve_t *c, const float min, const float max, const int res, float *x, float *y)
Definition draw.h:324
static void dt_draw_histogram_8_zoomed(cairo_t *cr, const uint32_t *hist, int32_t channels, int32_t channel, const float zoom_factor, const float zoom_offset_x, const float zoom_offset_y, gboolean linear)
Definition draw.h:398
static void set_color(cairo_t *cr, GdkRGBA color)
Definition draw.h:125
static float dt_draw_curve_calc_value(dt_draw_curve_t *c, const float x)
Definition draw.h:360
static void dt_draw_curve_destroy(dt_draw_curve_t *c)
Definition draw.h:297
static void dt_draw_curve_set_point(dt_draw_curve_t *c, const int num, const float x, const float y)
Definition draw.h:303
static int dt_draw_curve_add_point(dt_draw_curve_t *c, const float x, const float y)
Definition draw.h:379
static dt_draw_curve_t * dt_draw_curve_new(const float min, const float max, unsigned int type)
Definition draw.h:281
static void dt_draw_grid_zoomed(cairo_t *cr, const int num, const float left, const float top, const float right, const float bottom, const float width, const float height, const float zoom_factor, const float zoom_offset_x, const float zoom_offset_y)
Definition draw.h:179
static guint dt_keys_mainpad_alternatives(const guint key_val)
Remap keypad keys to usual mainpad ones.
Definition gdkkeys.h:118
void dt_gui_presets_add_generic(const char *name, dt_dev_operation_t op, const int32_t version, const void *params, const int32_t params_size, const int32_t enabled)
#define DT_GUI_MODULE(x)
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
@ DT_REQUEST_COLORPICK_OFF
Definition imageop.h:215
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:186
@ IOP_FLAGS_ALLOW_TILING
Definition imageop.h:188
@ IOP_GROUP_COLOR
Definition imageop.h:158
GtkWidget * dt_bauhaus_toggle_from_params(dt_iop_module_t *self, const char *param)
GtkWidget * dt_bauhaus_combobox_from_params(dt_iop_module_t *self, const char *param)
#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
static void dt_iop_estimate_exp(const float *const x, const float *const y, const int num, float *coeff)
static float dt_iop_eval_exp(const float *const coeff, const float x)
float *const restrict const size_t k
float *const restrict const size_t const size_t ch
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition macros.h:96
#define M_PI
Definition math.h:47
#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
char * key
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
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
#define __OMP_PARALLEL_FOR__(...)
Definition openmp.h:95
@ DT_REQUEST_ON
Definition pixelpipe.h:52
dt_colorspaces_color_profile_type_t
@ DT_COLORSPACE_NONE
No profile / "take it from the image settings". Never matches a list entry.
static gboolean _area_draw_callback(GtkWidget *widget, cairo_t *crf, dt_iop_module_t *self)
Definition rgbcurve.c:701
void commit_params(struct dt_iop_module_t *self, dt_iop_params_t *p1, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition rgbcurve.c:1536
void init(dt_iop_module_t *module)
Definition rgbcurve.c:1453
const char ** description(struct dt_iop_module_t *self)
Definition rgbcurve.c:172
int default_group()
Definition rgbcurve.c:157
__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 ivoid, void *const ovoid)
Definition rgbcurve.c:1560
static void interpolator_callback(GtkWidget *widget, dt_iop_module_t *self)
Definition rgbcurve.c:424
static void _rgbcurve_show_hide_controls(dt_iop_rgbcurve_params_t *p, dt_iop_rgbcurve_gui_data_t *g)
Definition rgbcurve.c:353
static gboolean _area_key_press_callback(GtkWidget *widget, GdkEventKey *event, dt_iop_module_t *self)
Definition rgbcurve.c:647
#define DT_IOP_RGBCURVE_RES
Definition rgbcurve.c:74
static __DT_CLONE_TARGETS__ void _generate_curve_lut(const dt_dev_pixelpipe_t *pipe, dt_iop_rgbcurve_data_t *d)
Definition rgbcurve.c:1471
#define DT_GUI_CURVE_EDITOR_INSET
Definition rgbcurve.c:73
static gboolean _area_leave_notify_callback(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
Definition rgbcurve.c:695
#define DT_IOP_RGBCURVE_MIN_X_DISTANCE
Definition rgbcurve.c:76
static void picker_scale(const float *const in, float *out, dt_iop_rgbcurve_params_t *p, const dt_iop_order_iccprofile_info_t *const work_profile)
Definition rgbcurve.c:312
void init_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition rgbcurve.c:1412
rgbcurve_channel_t
Definition rgbcurve.c:84
@ DT_IOP_RGBCURVE_MAX_CHANNELS
Definition rgbcurve.c:88
@ DT_IOP_RGBCURVE_G
Definition rgbcurve.c:86
@ DT_IOP_RGBCURVE_B
Definition rgbcurve.c:87
@ DT_IOP_RGBCURVE_R
Definition rgbcurve.c:85
static float _mouse_to_curve(const float x, const float zoom_factor, const float offset)
Definition rgbcurve.c:307
static int _add_node(dt_iop_rgbcurve_node_t *curve_nodes, int *nodes, float x, float y)
Definition rgbcurve.c:468
const char * name()
Definition rgbcurve.c:152
#define DT_IOP_COLOR_ICC_LEN
Definition rgbcurve.c:79
void gui_reset(struct dt_iop_module_t *self)
Definition rgbcurve.c:1258
static gboolean _sanity_check(const float x, const int selected, const int nodes, const dt_iop_rgbcurve_node_t *curve)
Definition rgbcurve.c:577
void gui_update(struct dt_iop_module_t *self)
Refresh GUI controls from current params and configuration.
Definition rgbcurve.c:1385
void change_image(struct dt_iop_module_t *self)
Definition rgbcurve.c:1273
float(* _coeffs_table_ptr)[3]
Definition rgbcurve.c:150
void gui_init(struct dt_iop_module_t *self)
Definition rgbcurve.c:1287
dt_iop_rgbcurve_autoscale_t
Definition rgbcurve.c:92
@ DT_S_SCALE_AUTOMATIC_RGB
Definition rgbcurve.c:93
@ DT_S_SCALE_MANUAL_RGB
Definition rgbcurve.c:94
void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
Definition rgbcurve.c:368
static gboolean _area_enter_notify_callback(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
Definition rgbcurve.c:689
int default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece)
Definition rgbcurve.c:167
static void tab_switch_callback(GtkNotebook *notebook, GtkWidget *page, guint page_num, gpointer user_data)
Definition rgbcurve.c:446
int flags()
Definition rgbcurve.c:162
void gui_cleanup(struct dt_iop_module_t *self)
Definition rgbcurve.c:1402
static gboolean _is_identity(dt_iop_rgbcurve_params_t *p, rgbcurve_channel_t channel)
Definition rgbcurve.c:360
static gboolean _area_button_press_callback(GtkWidget *widget, GdkEventButton *event, dt_iop_module_t *self)
Definition rgbcurve.c:1126
void init_presets(dt_iop_module_so_t *self)
Definition rgbcurve.c:181
static gboolean _area_scrolled_callback(GtkWidget *widget, GdkEventScroll *event, dt_iop_module_t *self)
Definition rgbcurve.c:624
#define RGBCURVE_DEFAULT_STEP
Definition rgbcurve.c:622
void cleanup_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition rgbcurve.c:1442
static gboolean _area_motion_notify_callback(GtkWidget *widget, GdkEventMotion *event, dt_iop_module_t *self)
Definition rgbcurve.c:1045
float(* _curve_table_ptr)[0x10000]
Definition rgbcurve.c:149
static float _curve_to_mouse(const float x, const float zoom_factor, const float offset)
Definition rgbcurve.c:302
static int _add_node_from_picker(dt_iop_rgbcurve_params_t *p, const float *const in, const float increment, const int ch, const dt_iop_order_iccprofile_info_t *const work_profile)
Definition rgbcurve.c:497
static gboolean _area_resized_callback(GtkWidget *widget, GdkEvent *event, gpointer user_data)
Definition rgbcurve.c:457
#define DT_IOP_RGBCURVE_MAXNODES
Definition rgbcurve.c:75
void color_picker_apply(dt_iop_module_t *self, GtkWidget *picker, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition rgbcurve.c:529
static gboolean _move_point_internal(dt_iop_module_t *self, GtkWidget *widget, float dx, float dy, guint state)
Definition rgbcurve.c:598
static cairo_surface_t * dt_cairo_image_surface_create(cairo_format_t format, int width, int height)
GtkWidget * dt_ui_resizable_drawing_area(GtkWidget *area, char *config_str, int default_height, int min_height)
Make a self-drawing widget (typically a GtkDrawingArea graph or scope) vertically resizable.
DT_ALIGNED_PIXEL float dt_aligned_pixel_t[4]
Definition simd.h:53
const float uint32_t state[4]
const float r
lib_colorpicker_sample_statistics scope
Definition colorpicker.h:73
dt_dev_request_flags_t request_histogram
struct dt_iop_module_t *void * data
GList * iop
Definition develop.h:269
struct dt_develop_t::@13 color_picker
Authoritative darkroom color-picker state.
GSList * samples
Definition develop.h:375
struct dt_dev_pixelpipe_t * pipe
Definition develop.h:214
GtkWidget * widget
Definition imageop_gui.h:47
dt_dev_operation_t op
Definition imageop.h:235
dt_dev_request_colorpick_flags_t request_color_pick
Definition imageop.h:272
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
uint32_t histogram_max[4]
Definition imageop.h:295
dt_aligned_pixel_t picked_output_color
Definition imageop.h:289
gboolean enabled
Definition imageop.h:313
dt_aligned_pixel_t picked_color_min
Definition imageop.h:287
dt_aligned_pixel_t picked_color_max
Definition imageop.h:287
int histogram_middle_grey
Definition imageop.h:302
uint32_t * histogram
Definition imageop.h:291
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_...
dt_colorspaces_color_profile_type_t type
Profile identity, half of the memo key. DT_COLORSPACE_NONE means "unset" and makes every apply functi...
float * lut_in[3]
Per-channel encoded -> linear tone curve, lutsize entries each, sampled over [0,1]....
char filename[DT_IOP_COLOR_ICC_LEN]
ICC file name, the other half of the memo key; "" for every built-in. Compared with strcmp,...
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...
int curve_changed[DT_IOP_RGBCURVE_MAX_CHANNELS]
Definition rgbcurve.c:144
dt_iop_rgbcurve_params_t params
Definition rgbcurve.c:140
dt_draw_curve_t * curve[DT_IOP_RGBCURVE_MAX_CHANNELS]
Definition rgbcurve.c:141
float unbounded_coeffs[DT_IOP_RGBCURVE_MAX_CHANNELS][3]
Definition rgbcurve.c:143
char filename_work[512]
Definition rgbcurve.c:146
dt_colorspaces_color_profile_type_t type_work
Definition rgbcurve.c:145
float table[DT_IOP_RGBCURVE_MAX_CHANNELS][0x10000]
Definition rgbcurve.c:142
GtkDrawingArea * area
Definition rgbcurve.c:120
GtkWidget * cmb_preserve_colors
Definition rgbcurve.c:133
GtkWidget * chk_compensate_middle_grey
Definition rgbcurve.c:132
GtkWidget * colorpicker_set_values
Definition rgbcurve.c:124
int minmax_curve_type[DT_IOP_RGBCURVE_MAX_CHANNELS]
Definition rgbcurve.c:118
dt_draw_curve_t * minmax_curve[DT_IOP_RGBCURVE_MAX_CHANNELS]
Definition rgbcurve.c:116
rgbcurve_channel_t channel
Definition rgbcurve.c:126
GtkNotebook * channel_tabs
Definition rgbcurve.c:122
int minmax_curve_nodes[DT_IOP_RGBCURVE_MAX_CHANNELS]
Definition rgbcurve.c:117
int curve_num_nodes[DT_IOP_RGBCURVE_MAX_CHANNELS]
Definition rgbcurve.c:107
dt_iop_rgb_norms_t preserve_colors
Definition rgbcurve.c:111
int curve_type[DT_IOP_RGBCURVE_MAX_CHANNELS]
Definition rgbcurve.c:108
gboolean compensate_middle_grey
Definition rgbcurve.c:110
dt_iop_rgbcurve_autoscale_t curve_autoscale
Definition rgbcurve.c:109
dt_iop_rgbcurve_node_t curve_nodes[DT_IOP_RGBCURVE_MAX_CHANNELS][20]
Definition rgbcurve.c:106
Region of interest passed through the pixelpipe.
Definition format.h:49
int width
Definition format.h:50
int height
Definition format.h:50
GtkWidget * notebook
#define __DT_CLONE_TARGETS__
void dtgtk_togglebutton_set_paint(GtkDarktableToggleButton *button, DTGTKCairoPaintIconFunc paint, gint paintflags, void *paintdata)
#define DTGTK_TOGGLEBUTTON(obj)
gboolean dt_gui_get_scroll_delta(const GdkEventScroll *event, gdouble *delta)
GdkModifierType dt_key_modifier_state()
GdkEventMask dt_widget_scroll_mask(void)
gboolean dt_gui_widgets_suppressed(void)
static gboolean dt_modifier_is(GdkModifierType state, const GdkModifierType desired_modifier_mask)
#define DT_GUI_BOX_SPACING
#define DT_PIXEL_APPLY_DPI(value)
void dtgtk_cairo_paint_colorpicker(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
@ CPF_ALTER