Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
lowlight.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2011 Brian Teague.
4 Copyright (C) 2011-2012 Henrik Andersson.
5 Copyright (C) 2011-2013, 2016 johannes hanika.
6 Copyright (C) 2011 Robert Bieber.
7 Copyright (C) 2011 Rostyslav Pidgornyi.
8 Copyright (C) 2011-2014, 2016-2017, 2019 Tobias Ellinghaus.
9 Copyright (C) 2012 Richard Wonka.
10 Copyright (C) 2012 Terry Jeffress.
11 Copyright (C) 2012, 2014, 2016, 2019 Ulrich Pegelow.
12 Copyright (C) 2013-2016 Roman Lebedev.
13 Copyright (C) 2013 Simon Spannagel.
14 Copyright (C) 2014 parafin.
15 Copyright (C) 2015 Pedro Côrte-Real.
16 Copyright (C) 2016 Asma.
17 Copyright (C) 2017-2018, 2021 Dan Torop.
18 Copyright (C) 2017 Heiko Bauke.
19 Copyright (C) 2018-2020, 2022-2023, 2025-2026 Aurélien PIERRE.
20 Copyright (C) 2018 Edgardo Hoszowski.
21 Copyright (C) 2018 Maurizio Paglia.
22 Copyright (C) 2018, 2020-2022 Pascal Obry.
23 Copyright (C) 2018 rawfiner.
24 Copyright (C) 2019 Andreas Schneider.
25 Copyright (C) 2019 emeikei.
26 Copyright (C) 2020 Aldric Renaudin.
27 Copyright (C) 2020-2022 Diederik Ter Rahe.
28 Copyright (C) 2020-2021 Ralf Brown.
29 Copyright (C) 2021 Chris Elston.
30 Copyright (C) 2022 Hanno Schwalm.
31 Copyright (C) 2022 Martin Bařinka.
32 Copyright (C) 2022 Philipp Lutz.
33
34 darktable is free software: you can redistribute it and/or modify
35 it under the terms of the GNU General Public License as published by
36 the Free Software Foundation, either version 3 of the License, or
37 (at your option) any later version.
38
39 darktable is distributed in the hope that it will be useful,
40 but WITHOUT ANY WARRANTY; without even the implied warranty of
41 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
42 GNU General Public License for more details.
43
44 You should have received a copy of the GNU General Public License
45 along with darktable. If not, see <http://www.gnu.org/licenses/>.
46*/
47#ifdef HAVE_CONFIG_H
48#include "config.h"
50#endif
51#include "widgets/bauhaus.h"
53#include "system/openmp.h"
55#include "system/mem_alloc.h"
56#include "system/simd.h"
58#include "database/database.h"
59#include "develop/develop.h"
60#include "develop/imageop_gui.h"
61
62#include "widgets/draw.h"
63#include "gui/presets.h"
64#include "iop/iop_api.h"
65#include <inttypes.h>
66#include <math.h>
67#include <stdlib.h>
68#include <string.h>
69#include "widgets/scroll_wrap.h"
70#include "gui/screen_metrics.h"
71
73
74#define DT_IOP_LOWLIGHT_INSET DT_PIXEL_APPLY_DPI(5)
75#define DT_IOP_LOWLIGHT_RES 64
76#define DT_IOP_LOWLIGHT_BANDS 6
77#define DT_IOP_LOWLIGHT_LUT_RES 0x10000
78
80{
81 float blueness; // $MIN: 0.0 $MAX: 100.0 $DEFAULT: 0.0 $DESCRIPTION: "blue shift"
83 float transition_y[DT_IOP_LOWLIGHT_BANDS]; // $DEFAULT: 0.5
85
101
108
109const char *name()
110{
111 return _("lowlight vision");
112}
113
114const char **description(struct dt_iop_module_t *self)
115{
116 return dt_iop_set_description(self, _("simulate human night vision"),
117 _("creative"),
118 _("non-linear, Lab, display-referred"),
119 _("linear, XYZ"),
120 _("non-linear, Lab, display-referred"));
121}
122
127
129{
130 return IOP_GROUP_EFFECTS;
131}
132
134{
135 return IOP_CS_LAB;
136}
137
140{
141 default_input_format(self, pipe, piece, dsc);
142 dsc->channels = 4;
143 dsc->datatype = TYPE_FLOAT;
144}
145
146static inline __attribute__((always_inline)) float lookup(const float *lut, const float i)
147{
148 const int bin0 = MIN(0xffff, MAX(0, DT_IOP_LOWLIGHT_LUT_RES * i));
149 const int bin1 = MIN(0xffff, MAX(0, DT_IOP_LOWLIGHT_LUT_RES * i + 1));
150 const float f = DT_IOP_LOWLIGHT_LUT_RES * i - bin0;
151 return lut[bin1] * f + lut[bin0] * (1. - f);
152}
153
155int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const i, void *const o)
156{
157 const dt_iop_roi_t *const roi_out = &piece->roi_out;
159 const int ch = 4;
160
161 // empiric coefficient
162 const float c = 0.5f;
163 const float threshold = 0.01f;
164
165 // scotopic white, blue saturated
166 dt_aligned_pixel_t Lab_sw = { 100.0f, 0, -d->blueness };
167 dt_aligned_pixel_t XYZ_sw;
168
169 dt_Lab_to_XYZ(Lab_sw, XYZ_sw);
171 for(size_t k = 0; k < (size_t)roi_out->width * roi_out->height; k++)
172 {
173 float *in = (float *)i + ch * k;
174 float *out = (float *)o + ch * k;
175 dt_aligned_pixel_t XYZ, XYZ_s;
176 float V;
177 float w;
178
179 dt_Lab_to_XYZ(in, XYZ);
180
181 // calculate scotopic luminance
182 if(XYZ[0] > threshold)
183 {
184 // normal flow
185 V = XYZ[1] * (1.33f * (1.0f + (XYZ[1] + XYZ[2]) / XYZ[0]) - 1.68f);
186 }
187 else
188 {
189 // low red flow, avoids "snow" on dark noisy areas
190 V = XYZ[1] * (1.33f * (1.0f + (XYZ[1] + XYZ[2]) / threshold) - 1.68f);
191 }
192
193 // scale using empiric coefficient and fit inside limits
194 V = fminf(1.0f, fmaxf(0.0f, c * V));
195
196 // blending coefficient from curve
197 w = lookup(d->lut, in[0] / 100.f);
198
199 XYZ_s[0] = V * XYZ_sw[0];
200 XYZ_s[1] = V * XYZ_sw[1];
201 XYZ_s[2] = V * XYZ_sw[2];
202
203 XYZ[0] = w * XYZ[0] + (1.0f - w) * XYZ_s[0];
204 XYZ[1] = w * XYZ[1] + (1.0f - w) * XYZ_s[1];
205 XYZ[2] = w * XYZ[2] + (1.0f - w) * XYZ_s[2];
206
208
209 out[3] = in[3];
210 }
211 return 0;
212}
213
216{
219 dt_draw_curve_set_point(d->curve, 0, p->transition_x[DT_IOP_LOWLIGHT_BANDS - 2] - 1.0, p->transition_y[0]);
220 for(int k = 0; k < DT_IOP_LOWLIGHT_BANDS; k++)
221 dt_draw_curve_set_point(d->curve, k + 1, p->transition_x[k], p->transition_y[k]);
222 dt_draw_curve_set_point(d->curve, DT_IOP_LOWLIGHT_BANDS + 1, p->transition_x[1] + 1.0,
223 p->transition_y[DT_IOP_LOWLIGHT_BANDS - 1]);
224 dt_draw_curve_calc_values(d->curve, 0.0, 1.0, DT_IOP_LOWLIGHT_LUT_RES, NULL, d->lut);
225 d->blueness = p->blueness;
226}
227
229{
232 piece->data = (void *)d;
233 piece->data_size = sizeof(dt_iop_lowlight_data_t);
234 // Checked AFTER both piece->data and piece->data_size are set, deliberately.
235 // dt_iop_init_pipe() disables the node for us when it sees data_size > 0 with a NULL
236 // data -- returning before data_size is assigned skips that and leaves the node enabled
237 // with no storage. dt_calloc_align() returns NULL on failure and pipe nodes are built
238 // exactly when memory is tightest (Sentry 134134395 faulted in atrous's init_pipe).
239 if(IS_NULL_PTR(piece->data)) return;
240 d->curve = dt_draw_curve_new(0.0, 1.0, CATMULL_ROM);
241 (void)dt_draw_curve_add_point(d->curve, default_params->transition_x[DT_IOP_LOWLIGHT_BANDS - 2] - 1.0,
242 default_params->transition_y[DT_IOP_LOWLIGHT_BANDS - 2]);
243 for(int k = 0; k < DT_IOP_LOWLIGHT_BANDS; k++)
244 (void)dt_draw_curve_add_point(d->curve, default_params->transition_x[k], default_params->transition_y[k]);
245 (void)dt_draw_curve_add_point(d->curve, default_params->transition_x[1] + 1.0,
246 default_params->transition_y[1]);
247}
248
250{
251 /* init_pipe() may have failed to allocate, and cleanup runs regardless. */
252 if(IS_NULL_PTR(piece->data)) return;
253 // clean up everything again.
255 dt_draw_curve_destroy(d->curve);
256 dt_free_align(piece->data);
257 piece->data = NULL;
258}
259
260void gui_update(struct dt_iop_module_t *self)
261{
264 dt_bauhaus_slider_set(g->scale_blueness, p->blueness);
265 gtk_widget_queue_draw(self->gui->widget);
266}
267
269{
270 dt_iop_default_init(module);
271
272 dt_iop_lowlight_params_t *d = module->default_params;
273
274 for(int k = 0; k < DT_IOP_LOWLIGHT_BANDS; k++) d->transition_x[k] = k / (DT_IOP_LOWLIGHT_BANDS - 1.0);
275}
276
278{
280
282
283 p.transition_x[0] = 0.000000;
284 p.transition_x[1] = 0.200000;
285 p.transition_x[2] = 0.400000;
286 p.transition_x[3] = 0.600000;
287 p.transition_x[4] = 0.800000;
288 p.transition_x[5] = 1.000000;
289
290 p.transition_y[0] = 1.000000;
291 p.transition_y[1] = 1.000000;
292 p.transition_y[2] = 1.000000;
293 p.transition_y[3] = 1.000000;
294 p.transition_y[4] = 1.000000;
295 p.transition_y[5] = 1.000000;
296
297 p.blueness = 0.0f;
298 dt_gui_presets_add_generic(_("daylight"), self->op,
299 self->version(), &p, sizeof(p), 1);
300
301 p.transition_x[0] = 0.000000;
302 p.transition_x[1] = 0.200000;
303 p.transition_x[2] = 0.400000;
304 p.transition_x[3] = 0.600000;
305 p.transition_x[4] = 0.800000;
306 p.transition_x[5] = 1.000000;
307
308 p.transition_y[0] = 0.600000;
309 p.transition_y[1] = 0.800000;
310 p.transition_y[2] = 0.950000;
311 p.transition_y[3] = 0.980000;
312 p.transition_y[4] = 1.000000;
313 p.transition_y[5] = 1.000000;
314
315 p.blueness = 30.0f;
316 dt_gui_presets_add_generic(_("indoor bright"), self->op,
317 self->version(), &p, sizeof(p), 1);
318
319 p.transition_x[0] = 0.000000;
320 p.transition_x[1] = 0.200000;
321 p.transition_x[2] = 0.400000;
322 p.transition_x[3] = 0.600000;
323 p.transition_x[4] = 0.800000;
324 p.transition_x[5] = 1.000000;
325
326 p.transition_y[0] = 0.300000;
327 p.transition_y[1] = 0.500000;
328 p.transition_y[2] = 0.700000;
329 p.transition_y[3] = 0.850000;
330 p.transition_y[4] = 0.970000;
331 p.transition_y[5] = 1.000000;
332
333 p.blueness = 30.0f;
334 dt_gui_presets_add_generic(_("indoor dim"), self->op,
335 self->version(), &p, sizeof(p), 1);
336
337 p.transition_x[0] = 0.000000;
338 p.transition_x[1] = 0.200000;
339 p.transition_x[2] = 0.400000;
340 p.transition_x[3] = 0.600000;
341 p.transition_x[4] = 0.800000;
342 p.transition_x[5] = 1.000000;
343
344 p.transition_y[0] = 0.050000;
345 p.transition_y[1] = 0.200000;
346 p.transition_y[2] = 0.400000;
347 p.transition_y[3] = 0.700000;
348 p.transition_y[4] = 0.920000;
349 p.transition_y[5] = 1.000000;
350
351 p.blueness = 40.0f;
352 dt_gui_presets_add_generic(_("indoor dark"), self->op,
353 self->version(), &p, sizeof(p), 1);
354
355 p.transition_x[0] = 0.000000;
356 p.transition_x[1] = 0.200000;
357 p.transition_x[2] = 0.400000;
358 p.transition_x[3] = 0.600000;
359 p.transition_x[4] = 0.800000;
360 p.transition_x[5] = 1.000000;
361
362 p.transition_y[0] = 0.070000;
363 p.transition_y[1] = 0.100000;
364 p.transition_y[2] = 0.180000;
365 p.transition_y[3] = 0.350000;
366 p.transition_y[4] = 0.750000;
367 p.transition_y[5] = 1.000000;
368
369 p.blueness = 50.0f;
370 dt_gui_presets_add_generic(_("twilight"), self->op,
371 self->version(), &p, sizeof(p), 1);
372
373 p.transition_x[0] = 0.000000;
374 p.transition_x[1] = 0.200000;
375 p.transition_x[2] = 0.400000;
376 p.transition_x[3] = 0.600000;
377 p.transition_x[4] = 0.800000;
378 p.transition_x[5] = 1.000000;
379
380 p.transition_y[0] = 0.000000;
381 p.transition_y[1] = 0.450000;
382 p.transition_y[2] = 0.750000;
383 p.transition_y[3] = 0.930000;
384 p.transition_y[4] = 0.990000;
385 p.transition_y[5] = 1.000000;
386
387 p.blueness = 30.0f;
388 dt_gui_presets_add_generic(_("night street lit"), self->op,
389 self->version(), &p, sizeof(p), 1);
390
391 p.transition_x[0] = 0.000000;
392 p.transition_x[1] = 0.200000;
393 p.transition_x[2] = 0.400000;
394 p.transition_x[3] = 0.600000;
395 p.transition_x[4] = 0.800000;
396 p.transition_x[5] = 1.000000;
397
398 p.transition_y[0] = 0.000000;
399 p.transition_y[1] = 0.150000;
400 p.transition_y[2] = 0.350000;
401 p.transition_y[3] = 0.800000;
402 p.transition_y[4] = 0.970000;
403 p.transition_y[5] = 1.000000;
404
405 p.blueness = 30.0f;
406 dt_gui_presets_add_generic(_("night street"), self->op,
407 self->version(), &p, sizeof(p), 1);
408
409 p.transition_x[0] = 0.000000;
410 p.transition_x[1] = 0.150000;
411 p.transition_x[2] = 0.400000;
412 p.transition_x[3] = 0.600000;
413 p.transition_x[4] = 0.800000;
414 p.transition_x[5] = 1.000000;
415
416 p.transition_y[0] = 0.000000;
417 p.transition_y[1] = 0.020000;
418 p.transition_y[2] = 0.050000;
419 p.transition_y[3] = 0.200000;
420 p.transition_y[4] = 0.550000;
421 p.transition_y[5] = 1.000000;
422
423 p.blueness = 40.0f;
424 dt_gui_presets_add_generic(_("night street dark"), self->op,
425 self->version(), &p, sizeof(p), 1);
426
427 p.transition_x[0] = 0.000000;
428 p.transition_x[1] = 0.200000;
429 p.transition_x[2] = 0.400000;
430 p.transition_x[3] = 0.600000;
431 p.transition_x[4] = 0.800000;
432 p.transition_x[5] = 1.000000;
433
434 p.transition_y[0] = 0.000000;
435 p.transition_y[1] = 0.000000;
436 p.transition_y[2] = 0.000000;
437 p.transition_y[3] = 0.000000;
438 p.transition_y[4] = 0.000000;
439 p.transition_y[5] = 0.000000;
440
441
442 p.blueness = 50.0f;
443 dt_gui_presets_add_generic(_("night"), self->op,
444 self->version(), &p, sizeof(p), 1);
445
447}
448
449// fills in new parameters based on mouse position (in 0,1)
450static void dt_iop_lowlight_get_params(dt_iop_lowlight_params_t *p, const double mouse_x,
451 const double mouse_y, const float rad)
452{
453 for(int k = 0; k < DT_IOP_LOWLIGHT_BANDS; k++)
454 {
455 const float f = expf(-(mouse_x - p->transition_x[k]) * (mouse_x - p->transition_x[k]) / (rad * rad));
456 p->transition_y[k] = (1 - f) * p->transition_y[k] + f * mouse_y;
457 }
458}
459
460static gboolean lowlight_draw(GtkWidget *widget, cairo_t *crf, gpointer user_data)
461{
462 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
465
466 dt_draw_curve_set_point(c->transition_curve, 0, p.transition_x[DT_IOP_LOWLIGHT_BANDS - 2] - 1.0,
467 p.transition_y[0]);
468 for(int k = 0; k < DT_IOP_LOWLIGHT_BANDS; k++)
469 dt_draw_curve_set_point(c->transition_curve, k + 1, p.transition_x[k], p.transition_y[k]);
470 dt_draw_curve_set_point(c->transition_curve, DT_IOP_LOWLIGHT_BANDS + 1, p.transition_x[1] + 1.0,
471 p.transition_y[DT_IOP_LOWLIGHT_BANDS - 1]);
472
473 const int inset = DT_IOP_LOWLIGHT_INSET;
474 GtkAllocation allocation;
475 gtk_widget_get_allocation(widget, &allocation);
476 int width = allocation.width, height = allocation.height;
477 cairo_surface_t *cst = dt_cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
478 cairo_t *cr = cairo_create(cst);
479
480 cairo_set_source_rgb(cr, .2, .2, .2);
481 cairo_paint(cr);
482
483 cairo_translate(cr, inset, inset);
484 width -= 2 * inset;
485 height -= 2 * inset;
486
487 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.0));
488 cairo_set_source_rgb(cr, .1, .1, .1);
489 cairo_rectangle(cr, 0, 0, width, height);
490 cairo_stroke(cr);
491
492 cairo_set_source_rgb(cr, .3, .3, .3);
493 cairo_rectangle(cr, 0, 0, width, height);
494 cairo_fill(cr);
495
496 // draw grid
497 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(.4));
498 cairo_set_source_rgb(cr, .1, .1, .1);
499 dt_draw_grid(cr, 8, 0, 0, width, height);
500
501
502 if(c->mouse_y > 0 || c->dragging)
503 {
504 // draw min/max curves:
505 dt_iop_lowlight_get_params(&p, c->mouse_x, 1., c->mouse_radius);
506 dt_draw_curve_set_point(c->transition_curve, 0, p.transition_x[DT_IOP_LOWLIGHT_BANDS - 2] - 1.0,
507 p.transition_y[0]);
508 for(int k = 0; k < DT_IOP_LOWLIGHT_BANDS; k++)
509 dt_draw_curve_set_point(c->transition_curve, k + 1, p.transition_x[k], p.transition_y[k]);
510 dt_draw_curve_set_point(c->transition_curve, DT_IOP_LOWLIGHT_BANDS + 1, p.transition_x[1] + 1.0,
511 p.transition_y[DT_IOP_LOWLIGHT_BANDS - 1]);
512 dt_draw_curve_calc_values(c->transition_curve, 0.0, 1.0, DT_IOP_LOWLIGHT_RES, c->draw_min_xs,
513 c->draw_min_ys);
514
516 dt_iop_lowlight_get_params(&p, c->mouse_x, .0, c->mouse_radius);
517 dt_draw_curve_set_point(c->transition_curve, 0, p.transition_x[DT_IOP_LOWLIGHT_BANDS - 2] - 1.0,
518 p.transition_y[0]);
519 for(int k = 0; k < DT_IOP_LOWLIGHT_BANDS; k++)
520 dt_draw_curve_set_point(c->transition_curve, k + 1, p.transition_x[k], p.transition_y[k]);
521 dt_draw_curve_set_point(c->transition_curve, DT_IOP_LOWLIGHT_BANDS + 1, p.transition_x[1] + 1.0,
522 p.transition_y[DT_IOP_LOWLIGHT_BANDS - 1]);
523 dt_draw_curve_calc_values(c->transition_curve, 0.0, 1.0, DT_IOP_LOWLIGHT_RES, c->draw_max_xs,
524 c->draw_max_ys);
525 }
526
527 cairo_save(cr);
528
529 // draw x positions
530 cairo_set_source_rgb(cr, 0.6, 0.6, 0.6);
531 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.));
532 const float arrw = DT_PIXEL_APPLY_DPI(7.0f);
533 for(int k = 0; k < DT_IOP_LOWLIGHT_BANDS; k++)
534 {
535 cairo_move_to(cr, width * p.transition_x[k], height + inset - DT_PIXEL_APPLY_DPI(1));
536 cairo_rel_line_to(cr, -arrw * .5f, 0);
537 cairo_rel_line_to(cr, arrw * .5f, -arrw);
538 cairo_rel_line_to(cr, arrw * .5f, arrw);
539 cairo_close_path(cr);
540 if(c->x_move == k)
541 cairo_fill(cr);
542 else
543 cairo_stroke(cr);
544 }
545
546 // draw selected cursor
547 cairo_translate(cr, 0, height);
548
549 // cairo_set_operator(cr, CAIRO_OPERATOR_ADD);
550 // cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
551 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2.));
552 cairo_set_source_rgba(cr, .7, .7, .7, 1.0);
553
555 dt_draw_curve_set_point(c->transition_curve, 0, p.transition_x[DT_IOP_LOWLIGHT_BANDS - 2] - 1.0,
556 p.transition_y[0]);
557 for(int k = 0; k < DT_IOP_LOWLIGHT_BANDS; k++)
558 dt_draw_curve_set_point(c->transition_curve, k + 1, p.transition_x[k], p.transition_y[k]);
559 dt_draw_curve_set_point(c->transition_curve, DT_IOP_LOWLIGHT_BANDS + 1, p.transition_x[1] + 1.0,
560 p.transition_y[DT_IOP_LOWLIGHT_BANDS - 1]);
561 dt_draw_curve_calc_values(c->transition_curve, 0.0, 1.0, DT_IOP_LOWLIGHT_RES, c->draw_xs, c->draw_ys);
562 cairo_move_to(cr, 0 * width / (float)(DT_IOP_LOWLIGHT_RES - 1), -height * c->draw_ys[0]);
563 for(int k = 1; k < DT_IOP_LOWLIGHT_RES; k++)
564 cairo_line_to(cr, k * width / (float)(DT_IOP_LOWLIGHT_RES - 1), -height * c->draw_ys[k]);
565 cairo_stroke(cr);
566
567 // draw dots on knots
568 cairo_set_source_rgb(cr, 0.7, 0.7, 0.7);
569 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.));
570 for(int k = 0; k < DT_IOP_LOWLIGHT_BANDS; k++)
571 {
572 cairo_arc(cr, width * p.transition_x[k], -height * p.transition_y[k], DT_PIXEL_APPLY_DPI(3.0), 0.0,
573 2.0 * M_PI);
574 if(c->x_move == k)
575 cairo_fill(cr);
576 else
577 cairo_stroke(cr);
578 }
579
580 if(c->mouse_y > 0 || c->dragging)
581 {
582 // draw min/max, if selected
583 cairo_set_source_rgba(cr, .7, .7, .7, .6);
584 cairo_move_to(cr, 0, -height * c->draw_min_ys[0]);
585 for(int k = 1; k < DT_IOP_LOWLIGHT_RES; k++)
586 cairo_line_to(cr, k * width / (float)(DT_IOP_LOWLIGHT_RES - 1), -height * c->draw_min_ys[k]);
587 for(int k = DT_IOP_LOWLIGHT_RES - 1; k >= 0; k--)
588 cairo_line_to(cr, k * width / (float)(DT_IOP_LOWLIGHT_RES - 1), -height * c->draw_max_ys[k]);
589 cairo_close_path(cr);
590 cairo_fill(cr);
591 // draw mouse focus circle
592 cairo_set_source_rgba(cr, .9, .9, .9, .5);
593 const float pos = DT_IOP_LOWLIGHT_RES * c->mouse_x;
594 int k = (int)pos;
595 const float f = k - pos;
597 float ht = -height * (f * c->draw_ys[k] + (1 - f) * c->draw_ys[k + 1]);
598 cairo_arc(cr, c->mouse_x * width, ht, c->mouse_radius * width, 0, 2. * M_PI);
599 cairo_stroke(cr);
600 }
601
602 cairo_restore(cr);
603
604 cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
605
606 // draw labels:
607 PangoLayout *layout;
608 PangoRectangle ink;
609 PangoFontDescription *desc = pango_font_description_copy_static(dt_bauhaus_get_global()->pango_font_desc);
610 pango_font_description_set_weight(desc, PANGO_WEIGHT_BOLD);
611 pango_font_description_set_absolute_size(desc,(.06 * height) * PANGO_SCALE);
612 layout = pango_cairo_create_layout(cr);
613 pango_layout_set_font_description(layout, desc);
614 cairo_set_source_rgb(cr, .1, .1, .1);
615
616 pango_layout_set_text(layout, _("dark"), -1);
617 pango_layout_get_pixel_extents(layout, &ink, NULL);
618 cairo_move_to(cr, .02 * width - ink.y, .5 * (height + ink.width));
619 cairo_save(cr);
620 cairo_rotate(cr, -M_PI * .5f);
621 pango_cairo_show_layout(cr, layout);
622 cairo_restore(cr);
623
624 pango_layout_set_text(layout, _("bright"), -1);
625 pango_layout_get_pixel_extents(layout, &ink, NULL);
626 cairo_move_to(cr, .98 * width - ink.height, .5 * (height + ink.width));
627 cairo_save(cr);
628 cairo_rotate(cr, -M_PI * .5f);
629 pango_cairo_show_layout(cr, layout);
630 cairo_restore(cr);
631
632
633 pango_layout_set_text(layout, _("day vision"), -1);
634 pango_layout_get_pixel_extents(layout, &ink, NULL);
635 cairo_move_to(cr, .5 * (width - ink.width), .08 * height - ink.height);
636 pango_cairo_show_layout(cr, layout);
637
638 pango_layout_set_text(layout, _("night vision"), -1);
639 pango_layout_get_pixel_extents(layout, &ink, NULL);
640 cairo_move_to(cr, .5 * (width - ink.width), .97 * height - ink.height);
641 pango_cairo_show_layout(cr, layout);
642
643 pango_font_description_free(desc);
644 g_object_unref(layout);
645 cairo_destroy(cr);
646 cairo_set_source_surface(crf, cst, 0, 0);
647 cairo_paint(crf);
648 cairo_surface_destroy(cst);
649 return TRUE;
650}
651
652static gboolean lowlight_motion_notify(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
653{
654 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
657 const int inset = DT_IOP_LOWLIGHT_INSET;
658 GtkAllocation allocation;
659 gtk_widget_get_allocation(widget, &allocation);
660 int height = allocation.height - 2 * inset, width = allocation.width - 2 * inset;
661 if(!c->dragging) c->mouse_x = CLAMP(event->x - inset, 0, width) / (float)width;
662 c->mouse_y = 1.0 - CLAMP(event->y - inset, 0, height) / (float)height;
663 if(c->dragging)
664 {
665 *p = c->drag_params;
666 if(c->x_move >= 0)
667 {
668 const float mx = CLAMP(event->x - inset, 0, width) / (float)width;
669 if(c->x_move > 0 && c->x_move < DT_IOP_LOWLIGHT_BANDS - 1)
670 {
671 const float minx = p->transition_x[c->x_move - 1] + 0.001f;
672 const float maxx = p->transition_x[c->x_move + 1] - 0.001f;
673 p->transition_x[c->x_move] = fminf(maxx, fmaxf(minx, mx));
674 }
675 }
676 else
677 {
678 dt_iop_lowlight_get_params(p, c->mouse_x, c->mouse_y + c->mouse_pick, c->mouse_radius);
679 }
680 gtk_widget_queue_draw(widget);
681 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
682 }
683 else if(event->y > height)
684 {
685 c->x_move = 0;
686 float dist = fabs(p->transition_x[0] - c->mouse_x);
687 for(int k = 1; k < DT_IOP_LOWLIGHT_BANDS; k++)
688 {
689 float d2 = fabs(p->transition_x[k] - c->mouse_x);
690 if(d2 < dist)
691 {
692 c->x_move = k;
693 dist = d2;
694 }
695 }
696 gtk_widget_queue_draw(widget);
697 }
698 else
699 {
700 c->x_move = -1;
701 gtk_widget_queue_draw(widget);
702 }
703 return TRUE;
704}
705
706static gboolean lowlight_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
707{
708 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
709 if(event->button == 1 && event->type == GDK_2BUTTON_PRESS)
710 {
711 // reset current curve
714 for(int k = 0; k < DT_IOP_LOWLIGHT_BANDS; k++)
715 {
716 p->transition_x[k] = d->transition_x[k];
717 p->transition_y[k] = d->transition_y[k];
718 }
719 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
720 gtk_widget_queue_draw(self->gui->widget);
721 }
722 else if(event->button == 1)
723 {
725 c->drag_params = *(dt_iop_lowlight_params_t *)self->params;
726 const int inset = DT_IOP_LOWLIGHT_INSET;
727 GtkAllocation allocation;
728 gtk_widget_get_allocation(widget, &allocation);
729 int height = allocation.height - 2 * inset, width = allocation.width - 2 * inset;
730 c->mouse_pick
731 = dt_draw_curve_calc_value(c->transition_curve, CLAMP(event->x - inset, 0, width) / (float)width);
732 c->mouse_pick -= 1.0 - CLAMP(event->y - inset, 0, height) / (float)height;
733 c->dragging = 1;
734 return TRUE;
735 }
736 return FALSE;
737}
738
739static gboolean lowlight_button_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
740{
741 if(event->button == 1)
742 {
743 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
745 c->dragging = 0;
746 return TRUE;
747 }
748 return FALSE;
749}
750
751static gboolean lowlight_leave_notify(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
752{
753 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
755 if(!c->dragging) c->mouse_y = -1.0;
756 gtk_widget_queue_draw(widget);
757 return TRUE;
758}
759
760static gboolean lowlight_scrolled(GtkWidget *widget, GdkEventScroll *event, gpointer user_data)
761{
762 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
764
765 int delta_y;
766 if(dt_gui_get_scroll_unit_deltas(event, NULL, &delta_y))
767 {
768 c->mouse_radius = CLAMP(c->mouse_radius * (1.0 + 0.1 * delta_y), 0.2 / DT_IOP_LOWLIGHT_BANDS, 1.0);
769 gtk_widget_queue_draw(widget);
770 }
771
772 return TRUE;
773}
774
775void gui_init(struct dt_iop_module_t *self)
776{
779
780 c->transition_curve = dt_draw_curve_new(0.0, 1.0, CATMULL_ROM);
781 (void)dt_draw_curve_add_point(c->transition_curve, p->transition_x[DT_IOP_LOWLIGHT_BANDS - 2] - 1.0,
782 p->transition_y[DT_IOP_LOWLIGHT_BANDS - 2]);
783 for(int k = 0; k < DT_IOP_LOWLIGHT_BANDS; k++)
784 (void)dt_draw_curve_add_point(c->transition_curve, p->transition_x[k], p->transition_y[k]);
785 (void)dt_draw_curve_add_point(c->transition_curve, p->transition_x[1] + 1.0, p->transition_y[1]);
786
787 c->mouse_x = c->mouse_y = c->mouse_pick = -1.0;
788 c->dragging = 0;
789 c->x_move = -1;
790 self->gui->timeout_handle = 0;
791 c->mouse_radius = 1.0 / DT_IOP_LOWLIGHT_BANDS;
792
793 self->gui->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
794
795 c->area = GTK_DRAWING_AREA(gtk_drawing_area_new());
796 gtk_widget_set_hexpand(GTK_WIDGET(c->area), TRUE);
797 g_object_set_data(G_OBJECT(c->area), "iop-instance", self);
798 gtk_box_pack_start(GTK_BOX(self->gui->widget),
799 dt_ui_resizable_drawing_area(GTK_WIDGET(c->area),
800 "plugins/darkroom/lowlight/graphheight", 280, 100),
801 FALSE, FALSE, 0);
802
803 gtk_widget_add_events(GTK_WIDGET(c->area), GDK_POINTER_MOTION_MASK | dt_widget_scroll_mask()
804 | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
805 | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK);
806 g_signal_connect(G_OBJECT(c->area), "draw", G_CALLBACK(lowlight_draw), self);
807 g_signal_connect(G_OBJECT(c->area), "button-press-event", G_CALLBACK(lowlight_button_press), self);
808 g_signal_connect(G_OBJECT(c->area), "button-release-event", G_CALLBACK(lowlight_button_release), self);
809 g_signal_connect(G_OBJECT(c->area), "motion-notify-event", G_CALLBACK(lowlight_motion_notify), self);
810 g_signal_connect(G_OBJECT(c->area), "leave-notify-event", G_CALLBACK(lowlight_leave_notify), self);
811 g_signal_connect(G_OBJECT(c->area), "scroll-event", G_CALLBACK(lowlight_scrolled), self);
812
813 c->scale_blueness = dt_bauhaus_slider_from_params(self, "blueness");
814 dt_bauhaus_slider_set_format(c->scale_blueness, "%");
815 gtk_widget_set_tooltip_text(c->scale_blueness, _("blueness in shadows"));
816}
817
818void gui_cleanup(struct dt_iop_module_t *self)
819{
821 dt_draw_curve_destroy(c->transition_curve);
822
824}
825
826// clang-format off
827// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
828// vim: shiftwidth=2 expandtab tabstop=2 cindent
829// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
830// clang-format on
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
void dt_bauhaus_slider_set(GtkWidget *widget, float pos)
Definition bauhaus.c:3331
void dt_bauhaus_slider_set_format(GtkWidget *widget, const char *format)
Definition bauhaus.c:3407
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
static float lookup(read_only image2d_t lut, const float x)
@ IOP_CS_LAB
const float f
const float *const lut
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
dt_Lab_to_XYZ(Lab, XYZ)
const float threshold
static dt_aligned_pixel_t XYZ
const dt_colormatrix_t dt_aligned_pixel_t out
dt_XYZ_to_Lab(XYZ, Lab)
#define CATMULL_ROM
Definition curve_tools.h:35
struct dt_bauhaus_t * dt_bauhaus_get_global(void)
Definition darktable.c:646
#define dt_database_start_transaction()
Definition database.h:290
#define dt_database_release_transaction()
Definition database.h:291
#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)
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_grid(cairo_t *cr, const int num, const int left, const int top, const int right, const int bottom)
Definition draw.h:158
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
@ TYPE_FLOAT
Definition format.h:56
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)
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_DEPRECATED
Definition imageop.h:187
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:186
@ IOP_FLAGS_ALLOW_TILING
Definition imageop.h:188
@ IOP_GROUP_EFFECTS
Definition imageop.h:161
GtkWidget * dt_bauhaus_slider_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 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 lowlight.c:214
void init(dt_iop_module_t *module)
Definition lowlight.c:268
const char ** description(struct dt_iop_module_t *self)
Definition lowlight.c:114
int default_group()
Definition lowlight.c:128
static gboolean lowlight_draw(GtkWidget *widget, cairo_t *crf, gpointer user_data)
Definition lowlight.c:460
static gboolean lowlight_scrolled(GtkWidget *widget, GdkEventScroll *event, gpointer user_data)
Definition lowlight.c:760
static gboolean lowlight_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
Definition lowlight.c:706
static gboolean lowlight_motion_notify(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
Definition lowlight.c:652
#define DT_IOP_LOWLIGHT_BANDS
Definition lowlight.c:76
#define DT_IOP_LOWLIGHT_INSET
Definition lowlight.c:74
void init_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition lowlight.c:228
const char * name()
Definition lowlight.c:109
static gboolean lowlight_button_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
Definition lowlight.c:739
#define DT_IOP_LOWLIGHT_RES
Definition lowlight.c:75
void gui_update(struct dt_iop_module_t *self)
Refresh GUI controls from current params and configuration.
Definition lowlight.c:260
void gui_init(struct dt_iop_module_t *self)
Definition lowlight.c:775
static gboolean lowlight_leave_notify(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
Definition lowlight.c:751
int default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece)
Definition lowlight.c:133
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)
Definition lowlight.c:138
int flags()
Definition lowlight.c:123
void gui_cleanup(struct dt_iop_module_t *self)
Definition lowlight.c:818
void init_presets(dt_iop_module_so_t *self)
Definition lowlight.c:277
static void dt_iop_lowlight_get_params(dt_iop_lowlight_params_t *p, const double mouse_x, const double mouse_y, const float rad)
Definition lowlight.c:450
#define DT_IOP_LOWLIGHT_LUT_RES
Definition lowlight.c:77
__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 i, void *const o)
Definition lowlight.c:155
void cleanup_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition lowlight.c:249
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
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.
#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)
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
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
struct dt_iop_module_t *void * data
unsigned int channels
Definition format.h:83
dt_iop_buffer_type_t datatype
Definition format.h:85
float lut[0x10000]
Definition lowlight.c:106
dt_draw_curve_t * curve
Definition lowlight.c:105
GtkWidget * scale_blueness
Definition lowlight.c:90
dt_iop_lowlight_params_t drag_params
Definition lowlight.c:94
dt_draw_curve_t * transition_curve
Definition lowlight.c:88
GtkDrawingArea * area
Definition lowlight.c:91
GtkWidget * widget
Definition imageop_gui.h:47
dt_dev_operation_t op
Definition imageop.h:235
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_params_t * params
Definition imageop.h:333
Region of interest passed through the pixelpipe.
Definition format.h:49
int width
Definition format.h:50
int height
Definition format.h:50
#define __DT_CLONE_TARGETS__
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29
gboolean dt_gui_get_scroll_unit_deltas(const GdkEventScroll *event, int *delta_x, int *delta_y)
GdkEventMask dt_widget_scroll_mask(void)
#define DT_GUI_BOX_SPACING
#define DT_PIXEL_APPLY_DPI(value)