Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
graduatednd.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2010-2011 Bruce Guenter.
4 Copyright (C) 2010-2012 Henrik Andersson.
5 Copyright (C) 2010-2014, 2016, 2018 johannes hanika.
6 Copyright (C) 2010 Milan Knížek.
7 Copyright (C) 2011 Antony Dovgal.
8 Copyright (C) 2011 Brian Teague.
9 Copyright (C) 2011 Olivier Tribout.
10 Copyright (C) 2011 Robert Bieber.
11 Copyright (C) 2011 Rostyslav Pidgornyi.
12 Copyright (C) 2011-2014, 2016, 2019 Tobias Ellinghaus.
13 Copyright (C) 2012-2013, 2019-2020 Aldric Renaudin.
14 Copyright (C) 2012 Pascal de Bruijn.
15 Copyright (C) 2012 Richard Wonka.
16 Copyright (C) 2012-2014, 2017 Ulrich Pegelow.
17 Copyright (C) 2013 Dennis Gnad.
18 Copyright (C) 2013-2016 Roman Lebedev.
19 Copyright (C) 2013 Simon Spannagel.
20 Copyright (C) 2013 Thomas Pryds.
21 Copyright (C) 2015 Pedro Côrte-Real.
22 Copyright (C) 2017, 2019-2020 Heiko Bauke.
23 Copyright (C) 2018, 2020, 2022-2023, 2025-2026 Aurélien PIERRE.
24 Copyright (C) 2018-2019 Edgardo Hoszowski.
25 Copyright (C) 2018 Maurizio Paglia.
26 Copyright (C) 2018-2022 Pascal Obry.
27 Copyright (C) 2018 rawfiner.
28 Copyright (C) 2019 Andreas Schneider.
29 Copyright (C) 2019-2020, 2022 Diederik Ter Rahe.
30 Copyright (C) 2019 Diederik ter Rahe.
31 Copyright (C) 2020, 2022 Chris Elston.
32 Copyright (C) 2020 Nicolas Auffray.
33 Copyright (C) 2020-2021 Ralf Brown.
34 Copyright (C) 2021 Hubert Kowalski.
35 Copyright (C) 2022 Hanno Schwalm.
36 Copyright (C) 2022 Martin Bařinka.
37 Copyright (C) 2022 Philipp Lutz.
38 Copyright (C) 2023 Luca Zulberti.
39 Copyright (C) 2025, 2026 Guillaume Stutin.
40
41 darktable is free software: you can redistribute it and/or modify
42 it under the terms of the GNU General Public License as published by
43 the Free Software Foundation, either version 3 of the License, or
44 (at your option) any later version.
45
46 darktable is distributed in the hope that it will be useful,
47 but WITHOUT ANY WARRANTY; without even the implied warranty of
48 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
49 GNU General Public License for more details.
50
51 You should have received a copy of the GNU General Public License
52 along with darktable. If not, see <http://www.gnu.org/licenses/>.
53*/
54#ifdef HAVE_CONFIG_H
55#include "config.h"
56#endif
57#include "develop/masks_gui.h"
58#include <assert.h>
59#include "system/macros.h"
60#include "system/openmp.h"
62#include "system/mem_alloc.h"
63#include "system/simd.h"
64#include "common/logging.h"
66#include "database/database.h"
67#include <math.h>
68#include <stdlib.h>
69#include <string.h>
70
71#include "widgets/bauhaus.h"
74#include "math/math.h"
75#include "common/opencl.h"
76#include "control/redraw.h"
77#include "develop/develop.h"
78#include "develop/imageop.h"
80#include "develop/imageop_gui.h"
81#include "develop/masks.h"
82
84#include "widgets/draw.h"
85#include "gui/presets.h"
86#include "iop/iop_api.h"
87
89
91{
92 float density; // $MIN: -8.0 $MAX: 8.0 $DEFAULT: 1.0 $DESCRIPTION: "density" The density of filter 0-8 EV
93 float hardness; // $MIN: 0.0 $MAX: 100.0 $DEFAULT: 0.0 $DESCRIPTION: "hardness" 0% = soft and 100% = hard
94 float rotation; // $MIN: -180.0 $MAX: 180.0 $DEFAULT: 0.0 $DESCRIPTION: "rotation" 2*PI -180 - +180
95 float offset; // $DEFAULT: 50.0 $DESCRIPTION: "offset" centered, can be offsetted...
96 float hue; // $MIN: 0.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "hue"
97 float saturation; // $MIN: 0.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "saturation"
99
105
106
108{
110
111 dt_gui_presets_add_generic(_("neutral gray ND2 (soft)"), self->op, self->version(),
112 &(dt_iop_graduatednd_params_t){ 1, 0, 0, 50, 0, 0 },
113 sizeof(dt_iop_graduatednd_params_t), 1);
114 dt_gui_presets_add_generic(_("neutral gray ND4 (soft)"), self->op, self->version(),
115 &(dt_iop_graduatednd_params_t){ 2, 0, 0, 50, 0, 0 },
116 sizeof(dt_iop_graduatednd_params_t), 1);
117 dt_gui_presets_add_generic(_("neutral gray ND8 (soft)"), self->op, self->version(),
118 &(dt_iop_graduatednd_params_t){ 3, 0, 0, 50, 0, 0 },
119 sizeof(dt_iop_graduatednd_params_t), 1);
120 dt_gui_presets_add_generic(_("neutral gray ND2 (hard)"), self->op, self->version(),
121 &(dt_iop_graduatednd_params_t){ 1, 75, 0, 50, 0, 0 },
122 sizeof(dt_iop_graduatednd_params_t), 1);
123
124 dt_gui_presets_add_generic(_("neutral gray ND4 (hard)"), self->op, self->version(),
125 &(dt_iop_graduatednd_params_t){ 2, 75, 0, 50, 0, 0 },
126 sizeof(dt_iop_graduatednd_params_t), 1);
127 dt_gui_presets_add_generic(_("neutral gray ND8 (hard)"), self->op, self->version(),
128 &(dt_iop_graduatednd_params_t){ 3, 75, 0, 50, 0, 0 },
129 sizeof(dt_iop_graduatednd_params_t), 1);
130
131 dt_gui_presets_add_generic(_("orange ND2 (soft)"), self->op, self->version(),
132 &(dt_iop_graduatednd_params_t){ 1, 0, 0, 50, 0.102439, 0.8 },
133 sizeof(dt_iop_graduatednd_params_t), 1);
134 dt_gui_presets_add_generic(_("yellow ND2 (soft)"), self->op, self->version(),
135 &(dt_iop_graduatednd_params_t){ 1, 0, 0, 50, 0.151220, 0.5 },
136 sizeof(dt_iop_graduatednd_params_t), 1);
137
138 dt_gui_presets_add_generic(_("purple ND2 (soft)"), self->op, self->version(),
139 &(dt_iop_graduatednd_params_t){ 1, 0, 0, 50, 0.824390, 0.5 },
140 sizeof(dt_iop_graduatednd_params_t), 1);
141
142 dt_gui_presets_add_generic(_("green ND2 (soft)"), self->op, self->version(),
143 &(dt_iop_graduatednd_params_t){ 1, 0, 0, 50, 0.302439, 0.5 },
144 sizeof(dt_iop_graduatednd_params_t), 1);
145
146 dt_gui_presets_add_generic(_("red ND2 (soft)"), self->op, self->version(),
147 &(dt_iop_graduatednd_params_t){ 1, 0, 0, 50, 0, 0.5 },
148 sizeof(dt_iop_graduatednd_params_t), 1);
149 dt_gui_presets_add_generic(_("blue ND2 (soft)"), self->op, self->version(),
150 &(dt_iop_graduatednd_params_t){ 1, 0, 0, 50, 0.663415, 0.5 },
151 sizeof(dt_iop_graduatednd_params_t), 1);
152
153 dt_gui_presets_add_generic(_("brown ND4 (soft)"), self->op, self->version(),
154 &(dt_iop_graduatednd_params_t){ 2, 0, 0, 50, 0.082927, 0.25 },
155 sizeof(dt_iop_graduatednd_params_t), 1);
156
158}
159
160typedef struct grad_point_t
161{
162 float x;
163 float y;
178
180{
181 float density; // The density of filter 0-8 EV
182 float hardness; // Default 0% = soft and 100% = hard
183 float rotation; // 2*PI -180 - +180
184 float offset; // Default 50%, centered, can be offsetted...
185 float color[4]; // RGB color of gradient
186 float color1[4]; // inverted color (1 - c)
188
189
190const char *name()
191{
192 return _("graduated density");
193}
194
195const char **description(struct dt_iop_module_t *self)
196{
197 return dt_iop_set_description(self, _("simulate an optical graduated neutral density filter"),
198 _("corrective and creative"),
199 _("linear or non-linear, RGB, scene-referred"),
200 _("non-linear, RGB"),
201 _("non-linear, RGB, display-referred"));
202}
203
209
211{
212 return IOP_GROUP_EFFECTS;
213}
214
216{
217 return IOP_CS_RGB;
218}
219
220static inline float f(const float t, const float c, const float x)
221{
222 return (t / (1.0f + powf(c, -x * 6.0f)) + (1.0f - t) * (x * .5f + .5f));
223}
224
225typedef struct dt_iop_vector_2d_t
226{
227 double x;
228 double y;
230
231// determine the distance between the segment [(ax,ay)(bx,by)] and the point (xc,yc)
233{
234 grad_point_t s = { b.x - a.x, b.y - a.y };
235 grad_point_t u = { c.x - a.x, c.y - a.y };
236 const float sn2 = s.x * s.x + s.y * s.y;
237 if(sn2 <= 0.0f) return u.x * u.x + u.y * u.y;
238
239 const float t = CLAMP((s.x * u.x + s.y * u.y) / sn2, 0.0f, 1.0f);
240 const float dx = u.x - t * s.x;
241 const float dy = u.y - t * s.y;
242 return dx * dx + dy * dy;
243}
244
251static void _draw_end_marker(cairo_t *cr, const grad_point_t endpoint, const grad_point_t opposite, const float zoom_scale,
252 const float normal_sign, const gboolean active)
253{
254 grad_point_t e_1 = { 0.0f, 0.0f };
255 grad_point_t e_2 = { 0.0f, 0.0f };
256 const float dx = opposite.x - endpoint.x;
257 const float dy = opposite.y - endpoint.y;
258 const float length = dt_fast_hypotf(dx, dy);
259 const float x = DT_PIXEL_APPLY_DPI_DPP(15.0f) / zoom_scale;
260 const float inv_len = 1.0f / length;
261 const float ux = dx * inv_len;
262 const float uy = dy * inv_len;
263 const float px = -uy;
264 const float py = ux;
265
266 // e_1 is at distance x from endpoint along [endpoint, opposite].
267 e_1.x = endpoint.x + ux * x;
268 e_1.y = endpoint.y + uy * x;
269 // e_2 is the midpoint of [endpoint, e_1], offset by x * normal_sign on the perpendicular.
270 const float mx = (endpoint.x + e_1.x) * 0.5f;
271 const float my = (endpoint.y + e_1.y) * 0.5f;
272 e_2.x = mx + px * (x * normal_sign);
273 e_2.y = my + py * (x * normal_sign);
274
275 cairo_move_to(cr, endpoint.x, endpoint.y);
276 cairo_line_to(cr, e_1.x, e_1.y);
277 cairo_line_to(cr, e_2.x, e_2.y);
278 cairo_close_path(cr);
279 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.0f) / zoom_scale);
280
281 dt_draw_set_color_overlay(cr, TRUE, active ? 1.0f : 0.5f);
282 cairo_fill_preserve(cr);
283 dt_draw_set_color_overlay(cr, FALSE, active ? 1.0f : 0.5f);
284 cairo_stroke(cr);
285
286 dt_draw_node(cr, TRUE, active, FALSE, zoom_scale, endpoint.x, endpoint.y);
287}
288
289static int set_grad_from_points(struct dt_iop_module_t *self, const grad_point_t *a, const grad_point_t *b,
290 float *rotation, float *offset)
291{
292 // we want absolute preview positions
293 float pts[4] = { a->x, a->y, b->x, b->y };
296 dt_iop_roi_t mod_out;
297 if(!dt_dev_module_geometry_gui(self->dev, self, NULL, &mod_out)) return 0;
298 pts[0] /= (float)mod_out.width;
299 pts[2] /= (float)mod_out.width;
300 pts[1] /= (float)mod_out.height;
301 pts[3] /= (float)mod_out.height;
302
303 // directly compute the line angle from segment AB and keep one representative modulo PI
304 const float eps = .0001f;
305 const float diff_x = pts[2] - pts[0];
306 const float diff_y = pts[3] - pts[1];
307 if(fabsf(diff_x) <= eps && fabsf(diff_y) <= eps) return 9;
308 float v = atan2f(diff_y, diff_x);
309 *rotation = -v * 180.0f / M_PI;
310
311 // and now we go for the offset (more easy)
312 const float sinv = sinf(v);
313 const float cosv = cosf(v);
314 const float ofs = (-2.0f * sinv * pts[0]) + sinv - cosv + 1.0f + (2.0f * cosv * pts[1]);
315
316 *offset = ofs * 50.0f;
317
318 return 1;
319}
320
322 const float rotation, const float offset)
323{
324 // we get the extremities of the line
325 const float v = (-rotation / 180) * M_PI;
326 const float sinv = sinf(v);
327 const float cosv = cosf(v);
328 const float eps = 1e-6f;
330
331 dt_iop_roi_t mod_out;
332 if(!dt_dev_module_geometry_gui(self->dev, self, NULL, &mod_out)) return 0;
333 float wp = mod_out.width, hp = mod_out.height;
334
335 const float off = offset / 100.0f;
336
337 if(fabsf(sinv) <= eps) // horizontal: swap x ends and y offset sign depending on rotation direction
338 {
339 const int fwd = (cosv > 0.0f);
340 pts[0] = wp * (fwd ? 0.1f : 0.9f);
341 pts[2] = wp * (fwd ? 0.9f : 0.1f);
342 pts[1] = pts[3] = hp * (fwd ? off : (1.0f - off));
343 }
344 else if(fabsf(fabsf(sinv) - 1.0f) <= eps) // vertical: swap y ends and x offset sign depending on rotation direction
345 {
346 const int fwd = (sinv < 0.0f);
347 pts[0] = pts[2] = wp * (fwd ? off : (1.0f - off));
348 pts[1] = hp * (fwd ? 0.9f : 0.1f);
349 pts[3] = hp * (fwd ? 0.1f : 0.9f);
350 }
351 else
352 {
353 // otherwise we determine the extremities
354 float xx1 = (sinv - cosv + 1.0f - offset / 50.0f) * wp * 0.5f / sinv;
355 float xx2 = (sinv + cosv + 1.0f - offset / 50.0f) * wp * 0.5f / sinv;
356 float yy1 = 0.0f;
357 float yy2 = hp;
358 const float aa = hp / (xx2 - xx1);
359 const float bb = -xx1 * aa;
360
361 // clamp extremities to image width and recompute y from the line equation y = a*x + b
362 xx1 = CLAMP(xx1, 0.0f, wp);
363 yy1 = aa * xx1 + bb;
364 xx2 = CLAMP(xx2, 0.0f, wp);
365 yy2 = aa * xx2 + bb;
366
367 // inset extremities away from image border by 10%
368 const float dx = xx2 - xx1;
369 const float dy = yy2 - yy1;
370 xx1 += dx * 0.1f;
371 yy1 += dy * 0.1f;
372 xx2 -= dx * 0.1f;
373 yy2 -= dy * 0.1f;
374
375 // near rotation: ax < bx; far rotation: bx < ax — in both cases just pick which end goes first
376 const int first_is_xx1 = (rotation < 90.0f && rotation > -90.0f) ? (xx1 < xx2) : (xx1 > xx2);
377 if(first_is_xx1)
378 {
379 pts[0] = xx1;
380 pts[1] = yy1;
381 pts[2] = xx2;
382 pts[3] = yy2;
383 }
384 else
385 {
386 pts[0] = xx2;
387 pts[1] = yy2;
388 pts[2] = xx1;
389 pts[3] = yy1;
390 }
391 }
392 // now we want that points to take care of distort modules
393
395 return 0;
397 a->x = pts[0];
398 a->y = pts[1];
399 b->x = pts[2];
400 b->y = pts[3];
401 return 1;
402}
403
404static inline void update_saturation_slider_end_color(GtkWidget *slider, float hue)
405{
407 hsl2rgb(rgb, hue, 1.0, 0.5);
408 dt_bauhaus_slider_set_stop(slider, 1.0, rgb[0], rgb[1], rgb[2]);
409}
410
412{
415
416 // convert picker RGB 2 HSL
417 float H = .0f, S = .0f, L = .0f;
418 rgb2hsl(self->picked_color, &H, &S, &L);
419
420 if(fabsf(p->hue - H) < 0.0001f && fabsf(p->saturation - S) < 0.0001f)
421 {
422 // interrupt infinite loops
423 return;
424 }
425
426 p->hue = H;
427 p->saturation = S;
428
430 dt_bauhaus_slider_set(g->hue, p->hue);
431 dt_bauhaus_slider_set(g->saturation, p->saturation);
432 update_saturation_slider_end_color(g->saturation, p->hue);
434
435 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
436}
437
438void gui_reset(struct dt_iop_module_t *self)
439{
441}
442
443void gui_post_expose(struct dt_iop_module_t *self, cairo_t *cr, int32_t width, int32_t height,
444 int32_t pointerx, int32_t pointery)
445{
446 dt_develop_t *dev = self->dev;
449 if(IS_NULL_PTR(g) || IS_NULL_PTR(p)) return;
450
451 const float zoom_scale = dt_dev_viewport_scaling(dev);
453
454 // we get the extremities of the line
455 if(g->define == 0)
456 {
457 if(!set_points_from_grad(self, &g->a, &g->b, p->rotation, p->offset))
458 return;
459 g->define = 1;
460 }
461
462 float line[4] = { g->a.x, g->a.y, g->b.x, g->b.y };
464 grad_point_t a = { line[0], line[1] };
465 grad_point_t b = { line[2], line[3] };
466
467 // the lines
468 cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
469 if(g->selected == 3 || g->dragging == 3)
470 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(5.0) / zoom_scale);
471 else
472 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(3.0) / zoom_scale);
474
475 cairo_move_to(cr, a.x, a.y);
476 cairo_line_to(cr, b.x, b.y);
477 cairo_stroke(cr);
478
479 if(g->selected == 3 || g->dragging == 3)
480 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2.0) / zoom_scale);
481 else
482 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.0) / zoom_scale);
484 cairo_move_to(cr, a.x, a.y);
485 cairo_line_to(cr, b.x, b.y);
486 cairo_stroke(cr);
487
488 // the extremities
489 _draw_end_marker(cr, a, b, zoom_scale, 1.0f, g->selected == 1 || g->dragging == 1);
490 _draw_end_marker(cr, b, a, zoom_scale, -1.0f, g->selected == 2 || g->dragging == 2);
491}
492
493int mouse_moved(struct dt_iop_module_t *self, double x, double y, double pressure, int which)
494{
496 float pzxpy[2] = { (float)x, (float)y };
498 float pzx = pzxpy[0];
499 float pzy = pzxpy[1];
500
501 // are we dragging something ?
502 if(g->dragging > 0)
503 {
504 if(g->dragging == 1)
505 {
506 // we are dragging a
507 g->a.x = pzx;
508 g->a.y = pzy;
509 }
510 else if(g->dragging == 2)
511 {
512 // we are dragging b
513 g->b.x = pzx;
514 g->b.y = pzy;
515 }
516 else if(g->dragging == 3)
517 {
518 // we are dragging the entire line
519 g->a.x += pzx - g->oldx;
520 g->b.x += pzx - g->oldx;
521 g->a.y += pzy - g->oldy;
522 g->b.y += pzy - g->oldy;
523 g->oldx = pzx;
524 g->oldy = pzy;
525 }
526 }
527 else
528 {
529 g->selected = 0;
530 float ext[2] = { DT_GUI_MOUSE_EFFECT_RADIUS, 0 };
532
533 const float ext2 = ext[0] * ext[0];
534
535 const grad_point_t pz = { pzx, pzy };
536 const float da_x = pz.x - g->a.x;
537 const float da_y = pz.y - g->a.y;
538 const float db_x = pz.x - g->b.x;
539 const float db_y = pz.y - g->b.y;
540
541 // are we near extermity ?
542 if(da_x * da_x + da_y * da_y < ext2)
543 {
544 g->selected = 1;
545 }
546 else if(db_x * db_x + db_y * db_y < ext2)
547 {
548 g->selected = 2;
549 }
550 else if(_dist_seg(g->a, g->b, pz) < ext2 * 0.5f)
551 g->selected = 3;
552 }
553
554 if(g->selected > 0 || g->dragging > 0)
556
557 return 1;
558}
559
560int button_pressed(struct dt_iop_module_t *self, double x, double y, double pressure, int which, int type,
561 uint32_t state)
562{
564 float pzxpy[2] = { (float)x, (float)y };
566 float pzx = pzxpy[0];
567 float pzy = pzxpy[1];
568
569 if(which == 3)
570 {
571 // creating a line with right click
572 g->dragging = 2;
573 g->a.x = pzx;
574 g->a.y = pzy;
575 g->b.x = pzx;
576 g->b.y = pzy;
577 g->oldx = pzx;
578 g->oldy = pzy;
579 return 1;
580 }
581 else if(g->selected > 0 && which == 1)
582 {
583 g->dragging = g->selected;
584 g->oldx = pzx;
585 g->oldy = pzy;
586 return 1;
587 }
588 g->dragging = 0;
589 return 0;
590}
591
592int button_released(struct dt_iop_module_t *self, double x, double y, int which, uint32_t state)
593{
596 if(g->dragging > 0)
597 {
598 float rotation = 0.0;
599 float offset = 0.0;
600 set_grad_from_points(self, &g->a, &g->b, &rotation, &offset);
601
602 // if this is a "line dragging, we reset extremities, to be sure they are not outside the image
603 if(g->dragging == 3)
604 {
605 // whole line dragging should not change rotation, so we should reuse
606 // old rotation to avoid rounding issues
607
608 rotation = p->rotation;
609 set_points_from_grad(self, &g->a, &g->b, rotation, offset);
610 }
612 dt_bauhaus_slider_set(g->rotation, rotation);
614 p->rotation = rotation;
615 p->offset = offset;
616 g->dragging = 0;
617 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
618 }
619
620 g->dragging = 0;
621 return 0;
622}
623
624int scrolled(dt_iop_module_t *self, double x, double y, int up, uint32_t state)
625{
628
629 // A drawn mask being edited (anywhere on canvas, not just hovered directly -- that case
630 // already wins via dt_masks_events_mouse_scrolled in views/darkroom.c) must not leak
631 // Ctrl/Shift+scroll into this module's density/hardness adjustment.
632 if(self->dev->form_gui && dt_masks_get_visible_form(self->dev)) return 0;
633
635 {
636 float dens;
637 if(up)
638 dens = fminf(8.0, p->density + 0.1);
639 else
640 dens = fmaxf(-8.0, p->density - 0.1);
641 if(dens != p->density)
642 {
643 dt_bauhaus_slider_set(g->density, dens);
644 }
645 return 1;
646 }
647 if(dt_modifier_is(state, GDK_SHIFT_MASK))
648 {
649 float comp;
650 if(up)
651 comp = fminf(100.0, p->hardness + 1.0);
652 else
653 comp = fmaxf(0.0, p->hardness - 1.0);
654 if(comp != p->hardness)
655 {
656 dt_bauhaus_slider_set(g->hardness, comp);
657 }
658 return 1;
659 }
660 return 0;
661}
662
663__OMP_DECLARE_SIMD__(simdlen(4))
664static inline float density_times_length(const float dens, const float length)
665{
666// return (dens * CLIP(0.5f + length) / 8.0f);
667 return (dens * CLAMP(0.5f + length, 0.0f, 1.0f) / 8.0f);
668}
669
670__OMP_DECLARE_SIMD__(simdlen(4))
671static inline float compute_density(const float dens, const float length)
672{
673#if 1
674 // !!! approximation is ok only when highest density is 8
675 // for input x = (data->density * CLIP( 0.5+length ), calculate 2^x as (e^(ln2*x/8))^8
676 // use exp2f approximation to calculate e^(ln2*x/8)
677 // in worst case - density==8,CLIP(0.5-length) == 1.0 it gives 0.6% of error
678 const float t = DT_M_LN2f * density_times_length(dens,length);
679 const float d1 = t * t * 0.5f;
680 const float d2 = d1 * t * 0.333333333f;
681 const float d3 = d2 * t * 0.25f;
682 const float d = 1 + t + d1 + d2 + d3; /* taylor series for e^x till x^4 */
683 // printf("%d %d %f\n",y,x,d);
684 float density = d * d;
685 density = density * density;
686 density = density * density;
687#else
688 // use fair exp2f
689 const float density = exp2f(dens * CLIP(0.5f + length));
690#endif
691 return density;
692}
693
695int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
696 void *const ovoid)
697{
698 const dt_iop_roi_t *const roi_in = &piece->roi_in;
699 const dt_iop_roi_t *const roi_out = &piece->roi_out;
700 const dt_iop_graduatednd_data_t *const data = (const dt_iop_graduatednd_data_t *const)piece->data;
701 const int ch = piece->dsc_in.channels;
702
703 const int ix = (roi_in->x);
704 const int iy = (roi_in->y);
705 const float iw = piece->buf_in.width * roi_out->scale;
706 const float ih = piece->buf_in.height * roi_out->scale;
707 const float hw = iw / 2.0f;
708 const float hh = ih / 2.0f;
709 const float hw_inv = 1.0f / hw;
710 const float hh_inv = 1.0f / hh;
711 const float v = (-data->rotation / 180) * M_PI;
712 const float sinv = sinf(v);
713 const float cosv = cosf(v);
714 const float filter_radie = sqrtf((hh * hh) + (hw * hw)) / hh;
715 const float offset = data->offset / 100.0f * 2;
716
717 const float filter_hardness = 1.0 / filter_radie / (1.0 - (0.5 + (data->hardness / 100.0) * 0.9 / 2.0)) * 0.5;
718
719 const int width = roi_out->width;
720 const int height = roi_out->height;
721 if(data->density > 0)
722 {
724 for(int y = 0; y < height; y++)
725 {
726 const size_t k = (size_t)width * y * ch;
727 const float *const restrict in = (float *)ivoid + k;
728 float *const restrict out = (float *)ovoid + k;
729
730 float length = (sinv * (-1.0 + ix * hw_inv) - cosv * (-1.0 + (iy + y) * hh_inv) - 1.0 + offset)
731 * filter_hardness;
732 const float length_inc = sinv * hw_inv * filter_hardness;
733
734 for(int x = 0; x < width; x++)
735 {
736 const float density = compute_density(data->density, length);
737 __OMP_SIMD__(aligned(in, out : 16))
738 for(int l = 0; l < 4; l++)
739 {
740 out[ch*x+l] = MAX(0.0f, (in[ch*x+l] / (data->color[l] + data->color1[l] * density)));
741 }
742 length += length_inc;
743 }
744 }
745 }
746 else
747 {
749 for(int y = 0; y < height; y++)
750 {
751 const size_t k = (size_t)width * y * ch;
752 const float *const restrict in = (float *)ivoid + k;
753 float *const restrict out = (float *)ovoid + k;
754
755 float length = (sinv * (-1.0f + ix * hw_inv) - cosv * (-1.0f + (iy + y) * hh_inv) - 1.0f + offset)
756 * filter_hardness;
757 const float length_inc = sinv * hw_inv * filter_hardness;
758
759 for(int x = 0; x < width; x++)
760 {
761 const float density = compute_density(-data->density, -length);
762 __OMP_SIMD__(aligned(in, out : 16))
763 for(int l = 0; l < 4; l++)
764 {
765 out[ch*x+l] = MAX(0.0f, (in[ch*x+l] * (data->color[l] + data->color1[l] * density)));
766 }
767 length += length_inc;
768 }
769 }
770 }
771
773 dt_iop_alpha_copy(ivoid, ovoid, roi_out->width, roi_out->height);
774 return 0;
775}
776
777#ifdef HAVE_OPENCL
778int process_cl(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, cl_mem dev_in, cl_mem dev_out)
779{
780 const dt_iop_roi_t *const roi_in = &piece->roi_in;
781 const dt_iop_roi_t *const roi_out = &piece->roi_out;
784
785 cl_int err = -999;
786 const int devid = pipe->devid;
787 const int width = roi_in->width;
788 const int height = roi_in->height;
789
790 const int ix = (roi_in->x);
791 const int iy = (roi_in->y);
792 const float iw = piece->buf_in.width * roi_out->scale;
793 const float ih = piece->buf_in.height * roi_out->scale;
794 const float hw = iw / 2.0f;
795 const float hh = ih / 2.0f;
796 const float hw_inv = 1.0f / hw;
797 const float hh_inv = 1.0f / hh;
798 const float v = (-data->rotation / 180) * M_PI;
799 const float sinv = sinf(v);
800 const float cosv = cosf(v);
801 const float filter_radie = sqrtf((hh * hh) + (hw * hw)) / hh;
802 const float offset = data->offset / 100.0f * 2;
803 const float density = data->density;
804
805#if 1
806 const float filter_hardness = 1.0 / filter_radie
807 / (1.0 - (0.5 + (data->hardness / 100.0) * 0.9 / 2.0)) * 0.5;
808#else
809 const float hardness = data->hardness / 100.0f;
810 const float t = 1.0f - .8f / (.8f + hardness);
811 const float c = 1.0f + 1000.0f * powf(4.0, hardness);
812#endif
813
814 const float length_base = (sinv * (-1.0 + ix * hw_inv) - cosv * (-1.0 + iy * hh_inv) - 1.0 + offset)
815 * filter_hardness;
816 const float length_inc_y = -cosv * hh_inv * filter_hardness;
817 const float length_inc_x = sinv * hw_inv * filter_hardness;
818
819 size_t sizes[] = { ROUNDUPDWD(width, devid), ROUNDUPDHT(height, devid), 1 };
820
821 int kernel = density > 0 ? gd->kernel_graduatedndp : gd->kernel_graduatedndm;
822
823 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), (void *)&dev_in);
824 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), (void *)&dev_out);
825 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(int), (void *)&width);
826 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(int), (void *)&height);
827 dt_opencl_set_kernel_arg(devid, kernel, 4, 4 * sizeof(float), (void *)data->color);
828 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(float), (void *)&density);
829 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(float), (void *)&length_base);
830 dt_opencl_set_kernel_arg(devid, kernel, 7, sizeof(float), (void *)&length_inc_x);
831 dt_opencl_set_kernel_arg(devid, kernel, 8, sizeof(float), (void *)&length_inc_y);
832 err = dt_opencl_enqueue_kernel_2d(devid, kernel, sizes);
833 if(err != CL_SUCCESS) goto error;
834 return TRUE;
835
836error:
837 dt_print(DT_DEBUG_OPENCL, "[opencl_graduatednd] couldn't enqueue kernel! %d\n", err);
838 return FALSE;
839}
840#endif
841
843{
844 const int program = 8; // extended.cl, from programs.conf
847 module->data = gd;
848 gd->kernel_graduatedndp = dt_opencl_create_kernel(program, "graduatedndp");
849 gd->kernel_graduatedndm = dt_opencl_create_kernel(program, "graduatedndm");
850}
851
859
860void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
861{
864 if(w == g->rotation)
865 {
866 set_points_from_grad(self, &g->a, &g->b, p->rotation, p->offset);
867 }
868 else if(w == g->hue)
869 {
870 update_saturation_slider_end_color(g->saturation, p->hue);
871 gtk_widget_queue_draw(g->saturation);
872 }
873}
874
877{
880
881 d->density = p->density;
882 d->hardness = p->hardness;
883 d->rotation = p->rotation;
884 d->offset = p->offset;
885
886 hsl2rgb(d->color, p->hue, p->saturation, 0.5);
887 d->color[3] = 0.0f;
888
889 if(d->density < 0)
890 for(int l = 0; l < 4; l++) d->color[l] = 1.0 - d->color[l];
891
892 for(int l = 0; l < 4; l++) d->color1[l] = 1.0 - d->color[l];
893}
894
896{
898 piece->data_size = sizeof(dt_iop_graduatednd_data_t);
899}
900
902{
903 dt_free_align(piece->data);
904 piece->data = NULL;
905}
906
917
918void gui_init(struct dt_iop_module_t *self)
919{
921
922 g->density = dt_bauhaus_slider_from_params(self, "density");
923 dt_bauhaus_slider_set_format(g->density, _(" EV"));
924 gtk_widget_set_tooltip_text(g->density, _("the density in EV for the filter"));
925
926 g->hardness = dt_bauhaus_slider_from_params(self, "hardness");
927 dt_bauhaus_slider_set_format(g->hardness, "%");
928 /* xgettext:no-c-format */
929 gtk_widget_set_tooltip_text(g->hardness, _("hardness of graduation:\n0% = soft, 100% = hard"));
930
931 g->rotation = dt_bauhaus_slider_from_params(self, "rotation");
932 dt_bauhaus_slider_set_format(g->rotation, "\302\260");
933 gtk_widget_set_tooltip_text(g->rotation, _("rotation of filter -180 to 180 degrees"));
934
937 dt_bauhaus_slider_set_factor(g->hue, 360.0f);
938 dt_bauhaus_slider_set_format(g->hue, "\302\260");
939 dt_bauhaus_slider_set_stop(g->hue, 0.0f, 1.0f, 0.0f, 0.0f);
940 dt_bauhaus_slider_set_stop(g->hue, 0.166f, 1.0f, 1.0f, 0.0f);
941 dt_bauhaus_slider_set_stop(g->hue, 0.322f, 0.0f, 1.0f, 0.0f);
942 dt_bauhaus_slider_set_stop(g->hue, 0.498f, 0.0f, 1.0f, 1.0f);
943 dt_bauhaus_slider_set_stop(g->hue, 0.664f, 0.0f, 0.0f, 1.0f);
944 dt_bauhaus_slider_set_stop(g->hue, 0.830f, 1.0f, 0.0f, 1.0f);
945 dt_bauhaus_slider_set_stop(g->hue, 1.0f, 1.0f, 0.0f, 0.0f);
946 gtk_widget_set_tooltip_text(g->hue, _("select the hue tone of filter"));
947
948 g->saturation = dt_bauhaus_slider_from_params(self, "saturation");
949 dt_bauhaus_slider_set_format(g->saturation, "%");
950 dt_bauhaus_slider_set_stop(g->saturation, 0.0f, 0.2f, 0.2f, 0.2f);
951 dt_bauhaus_slider_set_stop(g->saturation, 1.0f, 1.0f, 1.0f, 1.0f);
952 gtk_widget_set_tooltip_text(g->saturation, _("select the saturation of filter"));
953
954 g->selected = 0;
955 g->dragging = 0;
956 g->define = 0;
957}
958
959// clang-format off
960// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
961// vim: shiftwidth=2 expandtab tabstop=2 cindent
962// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
963// clang-format on
Handle default and user-set shortcuts (accelerators)
#define DT_PRIMARY_MASK
static void error(char *msg)
Definition ashift_lsd.c:202
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
void dt_bauhaus_slider_set_stop(GtkWidget *widget, float stop, float r, float g, float b)
Definition bauhaus.c:2161
void dt_bauhaus_slider_set_feedback(GtkWidget *widget, int feedback)
Definition bauhaus.c:3389
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
void dt_bauhaus_slider_set_factor(GtkWidget *widget, float factor)
Definition bauhaus.c:3423
@ 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_POINT
static const float x
const float f
const int t
const float v
void rgb2hsl(const dt_aligned_pixel_t rgb, float *h, float *s, float *l)
Convert RGB to HSL. Common helper used by iop modules.
void hsl2rgb(dt_aligned_pixel_t rgb, float h, float s, float l)
Convert HSL back to RGB. Common helper used by iop modules.
The colour-profile module's API: which profiles exist, and how to apply one.
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
static dt_aligned_pixel_t rgb
const dt_colormatrix_t dt_aligned_pixel_t out
#define S(V, params)
void dt_control_queue_redraw_center()
Request a redraw of the centre view.
Definition control.c:924
#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
float dt_dev_viewport_scaling(const dt_develop_t *dev)
void dt_dev_coordinates_image_abs_to_image_norm(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1219
int dt_dev_distort_backtransform_gui(dt_develop_t *dev, const double iop_order, const int transf_direction, float *points, size_t points_count)
The inverse of dt_dev_distort_transform_gui(), same rules.
Definition develop.c:1699
void dt_dev_coordinates_image_norm_to_preview_abs(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1291
gboolean dt_dev_module_geometry_gui(dt_develop_t *dev, dt_iop_module_t *module, dt_iop_roi_t *in, dt_iop_roi_t *out)
One module's own input and output rectangles at full resolution, from the geometry service.
Definition develop.c:1751
int dt_dev_distort_transform_gui(dt_develop_t *dev, const double iop_order, const int transf_direction, float *points, size_t points_count)
The GUI's bounded transform folds, composed by the geometry service.
Definition develop.c:1691
gboolean dt_dev_rescale_roi(dt_develop_t *dev, cairo_t *cr, int32_t width, int32_t height)
Scale the ROI to fit within given width/height, centered.
Definition develop.c:2071
void dt_dev_coordinates_image_norm_to_image_abs(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1203
void dt_dev_coordinates_widget_to_image_norm(dt_develop_t *dev, float *points, size_t num_points)
Coordinate conversion helpers between widget, normalized image, and absolute image spaces.
Definition develop.c:1144
@ DT_DEV_PIXELPIPE_DISPLAY_MASK
Definition develop.h:123
@ DT_DEV_TRANSFORM_DIR_FORW_EXCL
Definition develop.h:109
#define H
Definition diffuse.c:614
static void dt_draw_set_color_overlay(cairo_t *cr, gboolean bright, double alpha)
Definition draw.h:147
static void dt_draw_node(cairo_t *cr, const gboolean square, const gboolean point_action, const gboolean selected, const float zoom_scale, const float x, const float y)
Draw an node point of a mask.
Definition draw.h:669
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)
const char ** description(struct dt_iop_module_t *self)
int default_group()
__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)
static float compute_density(const float dens, const float length)
static int set_grad_from_points(struct dt_iop_module_t *self, const grad_point_t *a, const grad_point_t *b, float *rotation, float *offset)
void init_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
const char * name()
void gui_reset(struct dt_iop_module_t *self)
int scrolled(dt_iop_module_t *self, double x, double y, int up, uint32_t state)
void gui_update(struct dt_iop_module_t *self)
Refresh GUI controls from current params and configuration.
void gui_init(struct dt_iop_module_t *self)
int button_pressed(struct dt_iop_module_t *self, double x, double y, double pressure, int which, int type, uint32_t state)
static int set_points_from_grad(struct dt_iop_module_t *self, grad_point_t *a, grad_point_t *b, const float rotation, const float offset)
void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
int button_released(struct dt_iop_module_t *self, double x, double y, int which, uint32_t state)
void cleanup_global(dt_iop_module_so_t *module)
int default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece)
int flags()
void gui_post_expose(struct dt_iop_module_t *self, cairo_t *cr, int32_t width, int32_t height, int32_t pointerx, int32_t pointery)
void init_presets(dt_iop_module_so_t *self)
static float density_times_length(const float dens, const float length)
void cleanup_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
static void _draw_end_marker(cairo_t *cr, const grad_point_t endpoint, const grad_point_t opposite, const float zoom_scale, const float normal_sign, const gboolean active)
Draw one triangular endpoint marker on the graduated line.
void init_global(dt_iop_module_so_t *module)
static void update_saturation_slider_end_color(GtkWidget *slider, float hue)
int process_cl(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, cl_mem dev_in, cl_mem dev_out)
int mouse_moved(struct dt_iop_module_t *self, double x, double y, double pressure, int which)
static float _dist_seg(grad_point_t a, grad_point_t b, grad_point_t c)
void color_picker_apply(dt_iop_module_t *self, GtkWidget *picker, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
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)
float dt_boundingbox_t[4]
Definition image.h:83
const char ** dt_iop_set_description(dt_iop_module_t *module, const char *main_text, const char *purpose, const char *input, const char *process, const char *output)
Definition imageop.c:1893
@ IOP_FLAGS_INCLUDE_IN_STYLES
Definition imageop.h:185
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:186
@ IOP_FLAGS_ALLOW_TILING
Definition imageop.h:188
@ IOP_FLAGS_TILING_FULL_ROI
Definition imageop.h:190
@ IOP_GROUP_EFFECTS
Definition imageop.h:161
GtkWidget * dt_bauhaus_slider_from_params(dt_iop_module_t *self, const char *param)
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 float kernel(const float *x, const float *y)
_lib_location_type_t type
Definition location.c:1
@ DT_DEBUG_OPENCL
Definition logging.h:57
void dt_print(dt_debug_thread_t thread, const char *msg,...) __attribute__((format(printf
Print to stdout when thread is enabled, prefixed with seconds since startup.
float *const restrict 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
dt_masks_form_t * dt_masks_get_visible_form(const dt_develop_t *dev)
Return the currently visible form used by the masks GUI.
Definition masks_gui.c:1408
The interactive half of the masks subsystem: the editing state (dt_masks_form_gui_t),...
#define DT_M_LN2f
Definition math.h:57
#define CLIP(x)
Definition math.h:83
#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
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
#define DT_MODULE_INTROSPECTION(MODVER, PARAMSTYPE)
DT_MODULE() for a module whose params struct is introspected.
int dt_opencl_enqueue_kernel_2d(const int dev, const int kernel, const size_t *sizes)
Definition opencl.c:2554
int dt_opencl_create_kernel(const int prog, const char *name)
Definition opencl.c:2448
void dt_opencl_free_kernel(const int kernel)
Definition opencl.c:2491
int dt_opencl_set_kernel_arg(const int dev, const int kernel, const int num, const size_t size, const void *arg)
Definition opencl.c:2545
#define ROUNDUPDHT(a, b)
Definition opencl.h:86
#define ROUNDUPDWD(a, b)
Definition opencl.h:85
#define __OMP_SIMD__(...)
Definition openmp.h:99
#define __OMP_DECLARE_SIMD__(...)
Definition openmp.h:100
#define __OMP_PARALLEL_FOR__(...)
Definition openmp.h:95
#define eps
Definition rcd.c:81
Asking the interface to repaint itself.
DT_ALIGNED_PIXEL float dt_aligned_pixel_t[4]
Definition simd.h:53
const float uint32_t state[4]
dt_iop_buffer_dsc_t dsc_in
struct dt_iop_module_t *void * data
struct dt_masks_form_gui_t * form_gui
Definition develop.h:310
unsigned int channels
Definition format.h:83
dt_dev_operation_t op
Definition imageop.h:235
dt_iop_global_data_t * data
Definition imageop.h:238
struct dt_develop_t * dev
Definition imageop.h:311
dt_iop_global_data_t * global_data
Definition imageop.h:337
dt_aligned_pixel_t picked_color
Definition imageop.h:287
dt_iop_params_t * params
Definition imageop.h:333
Region of interest passed through the pixelpipe.
Definition format.h:49
double scale
Definition format.h:51
int width
Definition format.h:50
int height
Definition format.h:50
#define __DT_CLONE_TARGETS__
#define MAX(a, b)
Definition thinplate.c:29
static gboolean dt_modifier_is(GdkModifierType state, const GdkModifierType desired_modifier_mask)
#define dt_gui_freeze_begin()
#define dt_gui_freeze_end()
#define DT_GUI_MOUSE_EFFECT_RADIUS
#define DT_PIXEL_APPLY_DPI_DPP(value)
#define DT_PIXEL_APPLY_DPI(value)