Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
liquify.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2014 Marcello.
4 Copyright (C) 2016 Chris Hodapp.
5 Copyright (C) 2016, 2018 johannes hanika.
6 Copyright (C) 2016-2018, 2020-2022 Pascal Obry.
7 Copyright (C) 2016 Roman Lebedev.
8 Copyright (C) 2016, 2018-2019 Tobias Ellinghaus.
9 Copyright (C) 2016 Ulrich Pegelow.
10 Copyright (C) 2017, 2019-2020 Heiko Bauke.
11 Copyright (C) 2017, 2019 luzpaz.
12 Copyright (C) 2017-2018 Matthieu Moy.
13 Copyright (C) 2018-2021, 2023-2026 Aurélien PIERRE.
14 Copyright (C) 2018-2019 Edgardo Hoszowski.
15 Copyright (C) 2018 Maurizio Paglia.
16 Copyright (C) 2018 rawfiner.
17 Copyright (C) 2019 Andreas Schneider.
18 Copyright (C) 2019 mepi0011.
19 Copyright (C) 2020-2021 Aldric Renaudin.
20 Copyright (C) 2020-2021 Diederik Ter Rahe.
21 Copyright (C) 2020 GrahamByrnes.
22 Copyright (C) 2020-2022 Hanno Schwalm.
23 Copyright (C) 2020-2021 Hubert Kowalski.
24 Copyright (C) 2020 Marco.
25 Copyright (C) 2020 Martin Straeten.
26 Copyright (C) 2020-2021 Ralf Brown.
27 Copyright (C) 2022 Martin Bařinka.
28 Copyright (C) 2022 Philipp Lutz.
29 Copyright (C) 2024 Alynx Zhou.
30 Copyright (C) 2025 Guillaume Stutin.
31
32 darktable is free software: you can redistribute it and/or modify
33 it under the terms of the GNU General Public License as published by
34 the Free Software Foundation, either version 3 of the License, or
35 (at your option) any later version.
36
37 darktable is distributed in the hope that it will be useful,
38 but WITHOUT ANY WARRANTY; without even the implied warranty of
39 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
40 GNU General Public License for more details.
41
42 You should have received a copy of the GNU General Public License
43 along with darktable. If not, see <http://www.gnu.org/licenses/>.
44*/
45
46#ifdef HAVE_CONFIG_H
47#include "common/darktable.h"
48#include "gui/gdkkeys.h"
49#include "config.h"
50#endif
51#include "bauhaus/bauhaus.h"
53#include "common/opencl.h"
54#include "common/math.h"
55#include "common/collection.h"
56#include "control/conf.h"
57#include "control/control.h"
58#include "develop/imageop.h"
59#include "develop/imageop_gui.h"
60#include "develop/develop.h"
61
62#include "gui/gtk.h"
63#include "iop/iop_api.h"
64#include <assert.h>
65#include <cairo.h>
66#include <complex.h>
67#include <math.h>
68#include <stdlib.h>
69
70#ifdef _OPENMP
71#include <omp.h>
72#endif
73
74// this is the version of the modules parameters, and includes version information about compile-time dt
76
77#pragma GCC diagnostic ignored "-Wshadow"
78
79#define MAX_NODES 100 // max of nodes in one instance
80
81const int LOOKUP_OVERSAMPLE = 10;
82const int INTERPOLATION_POINTS = 100; // when interpolating bezier
83const float STAMP_RELOCATION = 0.1; // how many radii to move stamp forward when following a path
84
85#define CONF_RADIUS "plugins/darkroom/liquify/radius"
86#define CONF_ANGLE "plugins/darkroom/liquify/angle"
87#define CONF_STRENGTH "plugins/darkroom/liquify/strength"
88
89// enum of layers. sorted back to front.
90
114
126
127typedef struct
128{
129 float red, green, blue, alpha;
131
132#define COLOR_NULL { 0.0, 0.0, 0.0, 0.8 }
133#define GREY { 0.3, 0.3, 0.3, 0.8 }
134#define LGREY { 0.8, 0.8, 0.8, 1.0 }
135#define COLOR_DEBUG { 0.9, 0.9, 0.0, 1.0 }
136static const dt_liquify_rgba_t DT_LIQUIFY_COLOR_SELECTED = { 1.0, 1.0, 1.0, 1.0 };
137static const dt_liquify_rgba_t DT_LIQUIFY_COLOR_HOVER = { 1.0, 1.0, 1.0, 0.8 };
138
150
152{
172};
173
186
188{
189 // value in 1/96 inch (that is: in pixels on a standard 96 dpi screen)
190 2.0, // DT_LIQUIFY_UI_WIDTH_THINLINE
191 3.0, // DT_LIQUIFY_UI_WIDTH_THICKLINE
192 3.0, // DT_LIQUIFY_UI_WIDTH_DOUBLELINE
193 9.0, // DT_LIQUIFY_UI_WIDTH_GIZMO
194 7.0, // DT_LIQUIFY_UI_WIDTH_GIZMO_SMALL
195 100.0, // DT_LIQUIFY_UI_WIDTH_DEFAULT_RADIUS,
196 50.0, // DT_LIQUIFY_UI_WIDTH_DEFAULT_STRENGTH,
197 3.0 // DT_LIQUIFY_UI_WIDTH_MIN_DRAG
198};
199
200typedef enum
201{
202 DT_LIQUIFY_WARP_TYPE_LINEAR, // $DESCRIPTION: "linear" A linear warp originating from one point.
203 DT_LIQUIFY_WARP_TYPE_RADIAL_GROW, // $DESCRIPTION: "radial grow" A radial warp originating from one point.
204 DT_LIQUIFY_WARP_TYPE_RADIAL_SHRINK, // $DESCRIPTION: "radial shrink"
207
208typedef enum
209{
210 DT_LIQUIFY_NODE_TYPE_CUSP, // $DESCRIPTION: "cusp"
211 DT_LIQUIFY_NODE_TYPE_SMOOTH, // $DESCRIPTION: "smooth"
212 DT_LIQUIFY_NODE_TYPE_SYMMETRICAL, // $DESCRIPTION: "symmetrical"
213 DT_LIQUIFY_NODE_TYPE_AUTOSMOOTH, // $DESCRIPTION: "autosmooth"
216
217typedef enum
218{
219 DT_LIQUIFY_STATUS_NONE = 0, // $DESCRIPTION: "none"
220 DT_LIQUIFY_STATUS_NEW = 1, // $DESCRIPTION: "new"
221 DT_LIQUIFY_STATUS_INTERPOLATED = 2, // $DESCRIPTION: "interpolated"
222 DT_LIQUIFY_STATUS_PREVIEW = 4, // $DESCRIPTION: "preview"
225
226// enumerates the shapes types we use.
227
228typedef enum
229{
230 DT_LIQUIFY_PATH_INVALIDATED = 0, // $DESCRIPTION: "invalidated"
231 DT_LIQUIFY_PATH_MOVE_TO_V1, // $DESCRIPTION: "move"
232 DT_LIQUIFY_PATH_LINE_TO_V1, // $DESCRIPTION: "line"
233 DT_LIQUIFY_PATH_CURVE_TO_V1, // $DESCRIPTION: "curve"
235
246
247// Scalars and vectors are represented here as points because the only
248// thing we can reasonably distort_transform are points.
249
250typedef struct
251{
252 float complex point;
253 float complex strength;
254 float complex radius;
255 float control1;
256 float control2;
260
261typedef struct
262{
263 float complex ctrl1;
264 float complex ctrl2;
266
267// set up lots of alternative ways to get at the popular members.
268
275
281
283
288
289typedef struct
290{
293
294typedef struct
295{
297 int node_index; // last node index inserted
298
299 float complex last_mouse_pos;
301 GdkModifierType last_mouse_mods;
302
305
308
309 GtkLabel *label_warp, *label_node;
310 GtkToggleButton *btn_point_tool, *btn_line_tool, *btn_curve_tool, *btn_node_tool;
311
312 gboolean just_started;
314
315
316// this returns a translatable name
317const char *name()
318{
319 return _("l_iquify");
320}
321
322const char **description(struct dt_iop_module_t *self)
323{
324 return dt_iop_set_description(self, _("distort parts of the image"),
325 _("creative"),
326 _("linear, RGB, scene-referred"),
327 _("geometric, RGB"),
328 _("linear, RGB, scene-referred"));
329}
330
331
333{
334 return IOP_GROUP_EFFECTS;
335}
336
337int flags()
338{
340}
341
343{
344 return IOP_TAG_DISTORT;
345}
346
348{
349 return IOP_CS_RGB;
350}
351
354{
355 default_input_format(self, pipe, piece, dsc);
356 dsc->channels = 4;
357 dsc->datatype = TYPE_FLOAT;
358}
359
360/******************************************************************************/
361/* Code common to op-engine and gui. */
362/******************************************************************************/
363
364static inline float get_rot(const dt_liquify_warp_type_enum_t warp_type)
365{
367 return DT_M_PI_F;
368 else
369 return 0.0f;
370}
371
373{
374 for(int k=0; k<MAX_NODES; k++)
375 if(p->nodes[k].header.type == DT_LIQUIFY_PATH_INVALIDATED)
376 {
377 *node_index = k;
378 p->nodes[k].header.idx = k;
379 p->nodes[k].header.next = p->nodes[k].header.prev = -1;
380 p->nodes[k].header.selected = p->nodes[k].header.hovered = 0;
381 return &p->nodes[k];
382 }
383 return NULL;
384}
385
387{
388 if(n->header.prev == -1)
389 return NULL;
390 else
391 return &p->nodes[n->header.prev];
392}
393
395{
396 if(index > -1 && index < MAX_NODES)
397 return &p->nodes[index];
398 else
399 return NULL;
400}
401
403{
404 if(n->header.next == -1)
405 return NULL;
406 else
407 return &p->nodes[n->header.next];
408}
409
411{
412 new->header.next = this->header.idx;
413 new->header.prev = this->header.prev;
414 if(this->header.prev != -1)
415 p->nodes[this->header.prev].header.next = new->header.idx;
416 this->header.prev = new->header.idx;
417}
418
420{
421 int last=0;
422 for(last=MAX_NODES-1; last>0; last--)
423 if(p->nodes[last].header.type != DT_LIQUIFY_PATH_INVALIDATED)
424 break;
425 int k = 0;
426
427 while(k<=last)
428 {
429 if(p->nodes[k].header.type == DT_LIQUIFY_PATH_INVALIDATED)
430 {
431 for(int e=0; e<last; e++)
432 {
433 // then move slot if above position k
434 if(e >= k) p->nodes[e] = p->nodes[e+1];
435 // update all pointers above position k
436 if(e >= k) p->nodes[e].header.idx--;
437 if(p->nodes[e].header.prev >= k) p->nodes[e].header.prev--;
438 if(p->nodes[e].header.next >= k) p->nodes[e].header.next--;
439 }
440 last--;
441 }
442 else
443 k++;
444 }
445 // invalidate all nodes beyond the last moved one
446 for(int k=last+1; k<MAX_NODES; k++)
447 p->nodes[k].header.type = DT_LIQUIFY_PATH_INVALIDATED;
448}
449
451{
452 dt_liquify_path_data_t *prev = node_prev(p, this);
453 dt_liquify_path_data_t *next = node_next(p, this);
454
455 if(IS_NULL_PTR(prev) && !IS_NULL_PTR(next))
456 {
457 next->header.prev = -1;
459 }
460 else if(!IS_NULL_PTR(prev))
461 {
462 prev->header.next = this->header.next;
463
464 if(!IS_NULL_PTR(next))
465 next->header.prev = prev->header.idx;
466 }
467
468 this->header.prev = this->header.next = - 1;
469 this->header.type = DT_LIQUIFY_PATH_INVALIDATED;
470 node_gc(p);
471}
472
474{
476
477 // clear next
478 while(n)
479 {
481 n = node_next(p, n);
482 }
483
484 // clear prev
485 n = this;
486 while(n)
487 {
488 n->header.type = DT_LIQUIFY_PATH_INVALIDATED;
489 n = node_prev(p, n);
490 }
491 node_gc(p);
492}
493
565
566static void _distort_paths(const struct dt_iop_module_t *module,
567 const distort_params_t *params, const dt_iop_liquify_params_t *p)
568{
569 int len = 0;
570
571 // count nodes
572
573 for(int k = 0; k < MAX_NODES; k++)
574 {
577 break;
578
579 switch (data->header.type)
580 {
582 len += 2;
583 // fall thru
586 len += 3;
587 break;
588 default:
589 break;
590 }
591 }
592
593 // create buffer with all points
594
595 float *buffer = malloc(sizeof(float) * 2 * len);
596 float *b = buffer;
597
598 for(int k = 0; k < MAX_NODES; k++)
599 {
602 break;
603
604 switch (data->header.type)
605 {
607 *b++ = crealf(data->node.ctrl1) / params->from_scale;
608 *b++ = cimagf(data->node.ctrl1) / params->from_scale;
609 *b++ = crealf(data->node.ctrl2) / params->from_scale;
610 *b++ = cimagf(data->node.ctrl2) / params->from_scale;
611 // fall thru
614 *b++ = crealf(data->warp.point) / params->from_scale;
615 *b++ = cimagf(data->warp.point) / params->from_scale;
616 *b++ = crealf(data->warp.strength) / params->from_scale;
617 *b++ = cimagf(data->warp.strength) / params->from_scale;
618 *b++ = crealf(data->warp.radius) / params->from_scale;
619 *b++ = cimagf(data->warp.radius) / params->from_scale;
620 break;
621 default:
622 break;
623 }
624 }
625 if(params->from_distort_transform)
626 {
627 if(params->transf_direction == DT_DEV_TRANSFORM_DIR_ALL)
628 {
629 dt_dev_distort_transform_locked(params->pipe, module->iop_order,
630 DT_DEV_TRANSFORM_DIR_BACK_EXCL, buffer, len);
631 dt_dev_distort_transform_locked(params->pipe, module->iop_order,
632 DT_DEV_TRANSFORM_DIR_FORW_EXCL, buffer, len);
633 }
634 else
635 dt_dev_distort_transform_locked(params->pipe, module->iop_order,
636 params->transf_direction, buffer, len);
637 }
638 else
639 {
640 if(params->transf_direction == DT_DEV_TRANSFORM_DIR_ALL)
641 {
644 }
645 else
646 dt_dev_distort_transform_plus(params->pipe, module->iop_order, params->transf_direction, buffer, len);
647 }
648
649 // record back the transformed points
650
651 b = buffer;
652
653 for(int k = 0; k < MAX_NODES; k++)
654 {
657 break;
658
659 switch (data->header.type)
660 {
662 data->node.ctrl1 = (b[0] + b[1] * I) * params->to_scale;
663 b += 2;
664 data->node.ctrl2 = (b[0] + b[1] * I) * params->to_scale;
665 b += 2;
666 // fall thru
669 data->warp.point = (b[0] + b[1] * I) * params->to_scale;
670 b += 2;
671 data->warp.strength = (b[0] + b[1] * I) * params->to_scale;
672 b += 2;
673 data->warp.radius = (b[0] + b[1] * I) * params->to_scale;
674 b += 2;
675 break;
676 default:
677 break;
678 }
679 }
680
681 dt_free(buffer);
682}
683
684static void distort_paths_raw_to_piece(const struct dt_iop_module_t *module,
685 const dt_dev_pixelpipe_t *pipe,
686 const float roi_in_scale,
688 const gboolean from_distort_transform)
689{
690 const distort_params_t params = { module->dev, pipe, 1.f, roi_in_scale, DT_DEV_TRANSFORM_DIR_BACK_EXCL, from_distort_transform };
691 _distort_paths(module, &params, p);
692}
693
694// op-engine code
695
696static inline float complex normalize(const float complex v)
697{
698 if(cabsf(v) < 0.000001f)
699 return 1.0f;
700 return v / cabsf(v);
701}
702
703// calculate the linear blend of scalars a and b
704
705static inline float mix(const float a, const float b, const float t)
706{
707 return a + (b - a) * t;
708}
709
710
711// calculate the linear blend of points p0 and p1
712
713static inline float complex cmix(const float complex p0, const float complex p1, const float t)
714{
715 return p0 + (p1 - p0) * t;
716}
717
718static void mix_warps(dt_liquify_warp_t *result,
719 const dt_liquify_warp_t *warp1,
720 const dt_liquify_warp_t *warp2,
721 const complex float pt,
722 const float t)
723{
724 result->type = warp1->type;
725 result->control1 = mix (warp1->control1, warp2->control1, t);
726 result->control2 = mix (warp1->control2, warp2->control2, t);
727
728 const float radius = mix(cabsf(warp1->radius - warp1->point), cabsf(warp2->radius - warp2->point), t);
729 result->radius = pt + radius;
730
731 const complex float p1 = warp1->strength - warp1->point;
732 const complex float p2 = warp2->strength - warp2->point;
733 float arg1 = cargf(p1);
734 float arg2 = cargf(p2);
735 gboolean invert = FALSE;
736
737 if(arg1 > .0f && arg2 < -(M_PI_F / 2.f))
738 {
739 invert = TRUE;
740 arg1 = M_PI_F - arg1;
741 arg2 = -M_PI_F - arg2;
742 }
743 else if(arg1 < -(M_PI_F / 2.f) && arg2 > .0f)
744 {
745 invert = TRUE;
746 arg1 = -M_PI_F - arg1;
747 arg2 = M_PI_F - arg2;
748 }
749
750 const float r = mix(cabsf(p1), cabsf(p2), t);
751 const float phi = invert ? M_PI_F - mix(arg1, arg2, t) : mix(arg1, arg2, t);
752
753 result->strength = pt + r * cexpf(phi * I);
754 result->point = pt;
755}
756
757// Interpolate a cubic bezier spline into a series of points.
758
759static void interpolate_cubic_bezier(const float complex p0,
760 const float complex p1,
761 const float complex p2,
762 const float complex p3,
763 float complex buffer[],
764 const int n)
765{
766 // convert from bernstein basis to polynomial basis to get faster math
767 // See: http://www.tinaja.com/glib/cubemath.pdf
768 const float complex A = p3 - 3 * p2 + 3 * p1 - p0;
769 const float complex B = 3 * p2 - 6 * p1 + 3 * p0;
770 const float complex C = 3 * p1 - 3 * p0;
771 const float complex D = p0;
772
773 float complex *buf = buffer;
774 const float step = 1.0f / n;
775 float t = step;
776 *buf++ = p0;
777
778 for(int i = 1; i < n - 1; ++i)
779 {
780 *buf++ = ((A * t + B) * t + C) * t + D;
781 t += step;
782 }
783 *buf = p3;
784}
785
787
788/*
789 Get approx. arc length of a curve.
790
791 Used to approximate the arc length of a bezier curve.
792*/
793
794static float get_arc_length(const float complex points[], const int n_points)
795{
796 float length = 0.0f;
797 for(int i = 1; i < n_points; i++)
798 length += cabsf(points[i-1] - points[i]);
799 return length;
800}
801
802typedef struct
803{
804 int i;
805 float length;
807
808/*
809 Interpolate a point on a curve at a specified arc length.
810
811 In a bezier curve the parameter t usually does not correspond to
812 the arc length.
813*/
814
815static float complex point_at_arc_length(const float complex points[], const int n_points,
816 const float arc_length, restart_cookie_t *restart)
817{
818 float length = restart ? restart->length : 0.0f;
819
820 for(int i = restart ? restart->i : 1; i < n_points; i++)
821 {
822 const float prev_length = length;
823 length += cabsf(points[i-1] - points[i]);
824
825 if(length >= arc_length)
826 {
827 const float t = (arc_length - prev_length) / (length - prev_length);
828 if(!IS_NULL_PTR(restart))
829 {
830 restart->i = i;
831 restart->length = prev_length;
832 }
833 return cmix(points[i - 1], points[i], t);
834 }
835 }
836
837 return points[n_points - 1];
838}
839
840/*
841 Build a lookup table for the warp intensity.
842
843 Lookup table for the warp intensity function: f(x). The warp
844 intensity function determines how much a pixel is influenced by the
845 warp depending from its distance from a central point.
846
847 Boundary conditions: f(0) must be 1 and f(@a distance) must be 0.
848 f'(0) and f'(@a distance) must both be 0 or we'll get artifacts on
849 the picture.
850
851 Implementation: a bezier curve with p0 = 0, 1 and p3 = 1, 0. p1 is
852 defined by @a control1, 1 and p2 by @a control1, 0. Because a
853 bezier is parameterized on t, we have to reparameterize on x, which
854 we do by linear interpolation.
855
856 Octave code:
857
858 t = linspace(0,1,100);
859 grid;
860 hold on;
861 for steps = 0:0.1:1
862 cpoints = [0,1; steps,1; steps,0; 1,0];
863 bezier = cbezier2poly(cpoints);
864 x = polyval(bezier(1,:), t);
865 y = polyval(bezier(2,:), t);
866 plot(t, interp1(x, y, t));
867 end
868 hold off;
869*/
870
871static float *build_lookup_table(const int distance, const float control1, const float control2)
872{
873 float complex *clookup = dt_pixelpipe_cache_alloc_align_cache(sizeof(float complex) * (distance + 2), 0);
874 if(IS_NULL_PTR(clookup)) return NULL;
875
876 interpolate_cubic_bezier(I, control1 + I, control2, 1.0, clookup, distance + 2);
877
878 // reparameterize bezier by x and keep only y values
879 float *lookup = dt_pixelpipe_cache_alloc_align_float_cache((size_t)(distance + 2), 0);
881 {
883 return NULL;
884 }
885 float *ptr = lookup;
886 float complex *cptr = clookup + 1;
887 const float complex *cptr_end = cptr + distance;
888 const float step = 1.0f / (float) distance;
889 float x = 0.0f;
890
891 *ptr++ = 1.0f;
892 for(int i = 1; i < distance && cptr < cptr_end; i++)
893 {
894 x += step;
895 while(crealf(*cptr) < x && cptr < cptr_end)
896 cptr++;
897 const float dx1 = crealf(cptr[0] - cptr[-1]);
898 const float dx2 = x - crealf(cptr[-1]);
899 *ptr++ = cimagf(cptr[0]) +(dx2 / dx1) * (cimagf(cptr[0]) - cimagf(cptr[-1]));
900 }
901 *ptr++ = 0.0f;
902
904 return lookup;
905}
906
907static void compute_round_stamp_extent(cairo_rectangle_int_t *const restrict stamp_extent,
908 const dt_liquify_warp_t *const restrict warp)
909{
910
911 const int iradius = round(cabsf(warp->radius - warp->point));
912 assert(iradius > 0);
913
914 stamp_extent->x = stamp_extent->y = -iradius;
915 stamp_extent->x += crealf(warp->point);
916 stamp_extent->y += cimagf(warp->point);
917 stamp_extent->width = stamp_extent->height = 2 * iradius + 1;
918}
919
920/*
921 Compute a round(circular) stamp.
922
923 The stamp is a vector field of warp vectors around a center point.
924
925 In a linear warp the center point gets a warp of @a strength, while
926 points on the circumference of the circle get no warp at all.
927 Between center and circumference the warp magnitude tapers off
928 following a curve (see: build_lookup_table()).
929
930 Note that when applying a linear stamp to a path, we will first rotate its
931 vectors into the direction of the path.
932
933 In a radial warp the center point and the points on the
934 circumference get no warp. Between center and circumference the
935 warp magnitude follows a curve with maximum at radius / 0.5
936
937 Our stamp is stored in a rectangular region.
938*/
939
940static int build_round_stamp(float complex **pstamp,
941 cairo_rectangle_int_t *const restrict stamp_extent,
942 const dt_liquify_warp_t *const restrict warp)
943{
944 const int iradius = round(cabsf(warp->radius - warp->point));
945 assert(iradius > 0);
946
947 stamp_extent->x = stamp_extent->y = -iradius;
948 stamp_extent->width = stamp_extent->height = 2 * iradius + 1;
949
950 // 0.5 is factored in so the warp starts to degenerate when the
951 // strength arrow crosses the warp radius.
952 float complex strength = 0.5f * (warp->strength - warp->point);
953 strength = (warp->status & DT_LIQUIFY_STATUS_INTERPOLATED) ?
955 const float abs_strength = cabsf(strength);
956
957 float complex *restrict stamp =
958 calloc(sizeof(float complex), (size_t)stamp_extent->width * stamp_extent->height);
959 if(IS_NULL_PTR(stamp)) return 1;
960
961 // lookup table: map of distance from center point => warp
962 const int table_size = iradius * LOOKUP_OVERSAMPLE;
963 const float *restrict lookup_table = build_lookup_table(table_size, warp->control1, warp->control2);
964 if(IS_NULL_PTR(lookup_table))
965 {
966 dt_free(stamp);
967 return 1;
968 }
969
970 // points into buffer at the center of the circle
971 float complex *const center = stamp + 2 * iradius * iradius + 2 * iradius;
972
973 // The expensive operation here is hypotf (). By dividing the
974 // circle in quadrants and doing only the inside we have to calculate
975 // hypotf only for PI / 16 = 0.196 of the stamp area.
976 // We don't do octants to avoid false sharing of cache lines between threads.
977 // doesn't work for OSX see issue #7349
979 for(int y = 0; y <= iradius; y++)
980 {
981 for(int x = 0; x <= iradius; x++)
982 {
983 const float dist = dt_fast_hypotf(x, y);
984 const int idist = round(dist * LOOKUP_OVERSAMPLE);
985 if(idist >= table_size)
986 // idist will only grow bigger in this row
987 break;
988
989 // pointers into the 4 quadrants of the circle
990 // quadrant count is ccw from positive x-axis
991 float complex *const q1 = center - y * stamp_extent->width + x;
992 float complex *const q2 = center - y * stamp_extent->width - x;
993 float complex *const q3 = center + y * stamp_extent->width - x;
994 float complex *const q4 = center + y * stamp_extent->width + x;
995
996 float abs_lookup = abs_strength * lookup_table[idist] / iradius;
997
998 switch (warp->type)
999 {
1001 *q1 = abs_lookup * ( x - y * I);
1002 *q2 = abs_lookup * (-x - y * I);
1003 *q3 = abs_lookup * (-x + y * I);
1004 *q4 = abs_lookup * ( x + y * I);
1005 break;
1006
1008 *q1 = -abs_lookup * ( x - y * I);
1009 *q2 = -abs_lookup * (-x - y * I);
1010 *q3 = -abs_lookup * (-x + y * I);
1011 *q4 = -abs_lookup * ( x + y * I);
1012 break;
1013
1014 default:
1015 *q1 = *q2 = *q3 = *q4 = strength * lookup_table[idist];
1016 break;
1017 }
1018 }
1019 }
1020
1021 dt_pixelpipe_cache_free_align(lookup_table);
1022 *pstamp = stamp;
1023 return 0;
1024}
1025
1026/*
1027 Applies a stamp at a specified position.
1028
1029 Applies a stamp at the position specified by @a point and adds the
1030 resulting vector field to the global distortion map @a global_map.
1031
1032 The global distortion map is a map of relative pixel displacements
1033 encompassing all our paths.
1034*/
1035
1036static void add_to_global_distortion_map(float complex *global_map,
1037 const cairo_rectangle_int_t *const restrict global_map_extent,
1038 const dt_liquify_warp_t *const restrict warp,
1039 const float complex *const restrict stamp,
1040 const cairo_rectangle_int_t *stamp_extent)
1041{
1042 cairo_rectangle_int_t mmext = *stamp_extent;
1043 mmext.x += (int) round(crealf(warp->point));
1044 mmext.y += (int) round(cimagf(warp->point));
1045 cairo_rectangle_int_t cmmext = mmext;
1046 cairo_region_t *mmreg = cairo_region_create_rectangle(&mmext);
1047 cairo_region_intersect_rectangle(mmreg, global_map_extent);
1048 cairo_region_get_extents(mmreg, &cmmext);
1049 dt_free(mmreg);
1050
1051 #ifdef _OPENMP
1052 #pragma omp parallel for schedule (static) default (shared)
1053 #endif
1054
1055 for(int y = cmmext.y; y < cmmext.y + cmmext.height; y++)
1056 {
1057 const float complex *const srcrow = stamp + ((y - mmext.y) * mmext.width);
1058 float complex *const destrow = global_map + ((y - global_map_extent->y) * global_map_extent->width);
1059
1060 for(int x = cmmext.x; x < cmmext.x + cmmext.width; x++)
1061 {
1062 destrow[x - global_map_extent->x] -= srcrow[x - mmext.x];
1063 }
1064 }
1065}
1066
1067/*
1068 Applies the global distortion map to the picture. The distortion
1069 map maps points to the position from where the new color of the
1070 point should be sampled from. The distortion map is in relative
1071 device coords.
1072*/
1073
1076 const dt_dev_pixelpipe_iop_t *piece,
1077 const float *const restrict in,
1078 float *const restrict out,
1079 const dt_iop_roi_t *const roi_in,
1080 const dt_iop_roi_t *const roi_out,
1081 const int ch,
1082 const float complex *const map,
1083 const cairo_rectangle_int_t *extent)
1084{
1085 const int ch_width = ch * roi_in->width;
1086 const struct dt_interpolation * const interpolation =
1088
1089 #ifdef _OPENMP
1090 #pragma omp parallel for schedule (static) default (shared)
1091 #endif
1092
1093 for(int y = extent->y; y < extent->y + extent->height; y++)
1094 {
1095 // point inside roi_out ?
1096 if(y >= roi_out->y && y < roi_out->y + roi_out->height)
1097 {
1098 const float complex *row = map + (y - extent->y) * extent->width;
1099 float* out_sample = out + ((y - roi_out->y) * roi_out->width +
1100 extent->x - roi_out->x) * ch;
1101 for(int x = extent->x; x < extent->x + extent->width; x++)
1102 {
1103 if(
1104 // point inside roi_out ?
1105 (x >= roi_out->x && x < roi_out->x + roi_out->width) &&
1106 // point actually warped ?
1107 (*row != 0))
1108 {
1109 if(ch == 1)
1110 *out_sample = dt_interpolation_compute_sample(interpolation,
1111 in,
1112 x + crealf(*row) - roi_in->x,
1113 y + cimagf(*row) - roi_in->y,
1114 roi_in->width,
1115 roi_in->height,
1116 ch,
1117 ch_width);
1118 else
1120 interpolation,
1121 in,
1122 out_sample,
1123 x + crealf(*row) - roi_in->x,
1124 y + cimagf(*row) - roi_in->y,
1125 roi_in->width,
1126 roi_in->height,
1127 ch_width);
1128
1129 }
1130 ++row;
1131 out_sample += ch;
1132 }
1133 }
1134 }
1135}
1136
1137// calculate the map extent.
1138
1139static GSList *_get_map_extent(const dt_iop_roi_t *roi_out,
1140 const GList *interpolated,
1141 cairo_rectangle_int_t *map_extent)
1142{
1143 const cairo_rectangle_int_t roi_out_rect = { roi_out->x, roi_out->y, roi_out->width, roi_out->height };
1144 cairo_region_t *roi_out_region = cairo_region_create_rectangle(&roi_out_rect);
1145 cairo_region_t *map_region = cairo_region_create();
1146 GSList *in_roi = NULL;
1147
1148 for(const GList *i = interpolated; i; i = g_list_next(i))
1149 {
1150 const dt_liquify_warp_t *warp = ((dt_liquify_warp_t *) i->data);
1151 cairo_rectangle_int_t r;
1153 // add extent if not entirely outside the roi
1154 if(cairo_region_contains_rectangle(roi_out_region, &r) != CAIRO_REGION_OVERLAP_OUT)
1155 {
1156 cairo_region_union_rectangle(map_region, &r);
1157 in_roi = g_slist_prepend(in_roi, i->data);
1158 }
1159 }
1160
1161 // return the paths and the extent of all paths
1162 cairo_region_get_extents(map_region, map_extent);
1163 cairo_region_destroy(map_region);
1164 cairo_region_destroy(roi_out_region);
1165
1166 return g_slist_reverse(in_roi);
1167}
1168
1169static float complex *create_global_distortion_map(const cairo_rectangle_int_t *map_extent,
1170 const GSList *interpolated,
1171 gboolean inverted)
1172{
1173 const int mapsize = map_extent->width * map_extent->height;
1174 if (mapsize == 0)
1175 {
1176 // there are no pixels for which we need distortion info, so return right away
1177 // caller will see the NULL and bypass any further processing of the points it wants to distort
1178 return NULL;
1179 }
1180
1181 // allocate distortion map big enough to contain all paths
1182 float complex *map = dt_pixelpipe_cache_alloc_align_cache(sizeof(float complex) * mapsize, 0);
1183 if(IS_NULL_PTR(map)) return NULL;
1184 memset(map, 0, sizeof(float complex) * mapsize);
1185
1186 // build map
1187 for(const GSList *i = interpolated; i; i = g_slist_next(i))
1188 {
1189 const dt_liquify_warp_t *warp = ((dt_liquify_warp_t *) i->data);
1190 float complex *stamp = NULL;
1191 cairo_rectangle_int_t r;
1192 if(build_round_stamp(&stamp, &r, warp) != 0)
1193 {
1195 return NULL;
1196 }
1197 add_to_global_distortion_map(map, map_extent, warp, stamp, &r);
1198 dt_free(stamp);
1199 }
1200
1201 if(inverted)
1202 {
1203 float complex * const imap = dt_pixelpipe_cache_alloc_align_cache(sizeof(float complex) * mapsize, 0);
1204 if(IS_NULL_PTR(imap))
1205 {
1207 return NULL;
1208 }
1209 memset(imap, 0, sizeof(float complex) * mapsize);
1210
1211 // copy map into imap(inverted map).
1212 // imap [ n + dx(map[n]) , n + dy(map[n]) ] = -map[n]
1213
1214 #ifdef _OPENMP
1215 #pragma omp parallel for schedule (static) default (shared)
1216 #endif
1217
1218 for(int y = 0; y < map_extent->height; y++)
1219 {
1220 const float complex *const row = map + y * map_extent->width;
1221 for(int x = 0; x < map_extent->width; x++)
1222 {
1223 const float complex d = row[x];
1224 // compute new position (nx,ny) given the displacement d
1225 const int nx = x + (int)crealf(d);
1226 const int ny = y + (int)cimagf(d);
1227
1228 // if the point falls into the extent, set it
1229 if(nx>0 && nx<map_extent->width && ny>0 && ny<map_extent->height)
1230 imap[nx + ny * map_extent->width] = -d;
1231 }
1232 }
1233
1235
1236 // now just do a pass to avoid gap with a displacement of zero, note that we do not need high
1237 // precision here as the inverted distortion mask is only used to compute a final displacement
1238 // of points.
1239
1240 #ifdef _OPENMP
1241 #pragma omp parallel for schedule (static) default (shared)
1242 #endif
1243
1244 for(int y = 0; y < map_extent->height; y++)
1245 {
1246 float complex *const row = imap + y * map_extent->width;
1247 float complex last[2] = { 0, 0 };
1248 for(int x = 0; x < map_extent->width / 2 + 1; x++)
1249 {
1250 float complex *cl = row + x;
1251 float complex *cr = row + map_extent->width - x;
1252 if(x!=0)
1253 {
1254 if(*cl == 0) *cl = last[0];
1255 if(*cr == 0) *cr = last[1];
1256 }
1257 last[0] = *cl; last[1] = *cr;
1258 }
1259 }
1260
1261 map = imap;
1262 }
1263 return map;
1264}
1265
1266static float complex *build_global_distortion_map(struct dt_iop_module_t *module,
1267 const dt_dev_pixelpipe_t *pipe,
1268 const dt_dev_pixelpipe_iop_t *piece,
1269 const dt_iop_roi_t *roi_in,
1270 const dt_iop_roi_t *roi_out,
1271 cairo_rectangle_int_t *map_extent)
1272{
1273 // copy params
1274 dt_iop_liquify_params_t copy_params;
1275 memcpy(&copy_params, (dt_iop_liquify_params_t *)piece->data, sizeof(dt_iop_liquify_params_t));
1276
1277 distort_paths_raw_to_piece(module, pipe, roi_in->scale, &copy_params, FALSE);
1278
1279 GList *interpolated = interpolate_paths(&copy_params);
1280 GSList *interpolated_in_roi = _get_map_extent(roi_out, interpolated, map_extent);
1281
1282 float complex *map = create_global_distortion_map(map_extent, interpolated_in_roi, FALSE);
1283
1284 g_slist_free(interpolated_in_roi);
1285 interpolated_in_roi = NULL;
1286 g_list_free_full(interpolated, dt_free_gpointer);
1287 interpolated = NULL;
1288 return map;
1289}
1290
1291// 1st pass: how large would the output be, given this input roi?
1292// this is always called with the full buffer before processing.
1293void modify_roi_out(struct dt_iop_module_t *module, const struct dt_dev_pixelpipe_t *pipe,
1294 struct dt_dev_pixelpipe_iop_t *piece, dt_iop_roi_t *roi_out,
1295 const dt_iop_roi_t *roi_in)
1296{
1297 // output is same size as input
1298 *roi_out = *roi_in;
1299}
1300
1301// 2nd pass: which roi would this operation need as input to fill the given output region?
1302void modify_roi_in(struct dt_iop_module_t *module, const struct dt_dev_pixelpipe_t *pipe,
1303 struct dt_dev_pixelpipe_iop_t *piece, const dt_iop_roi_t *roi_out,
1304 dt_iop_roi_t *roi_in)
1305{
1306 // Because we move pixels, and we may have to sample a pixel from
1307 // outside roi_in, we need to expand roi_in to contain all our
1308 // paths. But we may ignore paths completely outside of roi_out.
1309
1310 *roi_in = *roi_out;
1311
1312 // copy params
1313 dt_iop_liquify_params_t copy_params;
1314 memcpy(&copy_params, (dt_iop_liquify_params_t*)piece->data, sizeof(dt_iop_liquify_params_t));
1315
1316 // Keep ROI expansion on the same processing pipe as piece->data. Preview-only state can be NULL
1317 // in workers, and mixing pipes here desynchronizes the distortion graph from the current piece.
1318 distort_paths_raw_to_piece(module, pipe, roi_in->scale, &copy_params, FALSE);
1319
1320 cairo_rectangle_int_t pipe_rect =
1321 {
1322 0,
1323 0,
1324 lroundf(piece->buf_in.width * roi_in->scale),
1325 lroundf(piece->buf_in.height * roi_in->scale)
1326 };
1327
1328 cairo_rectangle_int_t roi_in_rect =
1329 {
1330 roi_in->x,
1331 roi_in->y,
1332 roi_in->width,
1333 roi_in->height
1334 };
1335 cairo_region_t *roi_in_region = cairo_region_create_rectangle(&roi_in_rect);
1336
1337 // get extent of all paths
1338 GList *interpolated = interpolate_paths(&copy_params);
1339 cairo_rectangle_int_t extent;
1340 GSList *interpolated_in_roi = _get_map_extent(roi_out, interpolated, &extent);
1341 g_slist_free(interpolated_in_roi);
1342 interpolated_in_roi = NULL;
1343 g_list_free_full(interpolated, dt_free_gpointer);
1344 interpolated = NULL;
1345
1346 // (eventually) extend roi_in
1347 cairo_region_union_rectangle(roi_in_region, &extent);
1348 // and clamp to pipe extent
1349 cairo_region_intersect_rectangle(roi_in_region, &pipe_rect);
1350
1351 // write new extent to roi_in
1352 cairo_region_get_extents(roi_in_region, &roi_in_rect);
1353 roi_in->x = roi_in_rect.x;
1354 roi_in->y = roi_in_rect.y;
1355 roi_in->width = roi_in_rect.width;
1356 roi_in->height = roi_in_rect.height;
1357
1358 // cleanup
1359 cairo_region_destroy(roi_in_region);
1360}
1361
1363 const dt_dev_pixelpipe_iop_t *piece,
1364 float *const restrict points, const size_t points_count,
1365 const gboolean inverted)
1366{
1367
1368 // compute the extent of all points (all computations are done in RAW coordinate)
1369 float xmin = FLT_MAX, xmax = FLT_MIN, ymin = FLT_MAX, ymax = FLT_MIN;
1370 __OMP_PARALLEL_FOR__(if(points_count > 100) reduction(min:xmin, ymin) reduction(max:xmax, ymax))
1371 for(size_t i = 0; i < points_count * 2; i += 2)
1372 {
1373 const float x = points[i];
1374 const float y = points[i + 1];
1375 xmin = fmin(xmin, x);
1376 xmax = fmax(xmax, x);
1377 ymin = fmin(ymin, y);
1378 ymax = fmax(ymax, y);
1379 }
1380
1381 cairo_rectangle_int_t extent = { .x = (int)(xmin - .5), .y = (int)(ymin - .5),
1382 .width = (int)(xmax - xmin + 2.5), .height = (int)(ymax - ymin + 2.5) };
1383
1384 if(extent.width > 0 && extent.height > 0)
1385 {
1386 // copy params
1387 dt_iop_liquify_params_t copy_params;
1388 memcpy(&copy_params, (dt_iop_liquify_params_t *)piece->data, sizeof(dt_iop_liquify_params_t));
1389
1390 distort_paths_raw_to_piece(self, pipe, 1.f, &copy_params, TRUE);
1391
1392 // create the distortion map for this extent
1393
1394 GList *interpolated = interpolate_paths(&copy_params);
1395
1396 // we need to adjust the extent to be the union enclosing all the points (currently in extent) and
1397 // the warps that are in (possibly partly) in this same region.
1398
1399 dt_iop_roi_t roi_in = { .x = extent.x, .y = extent.y, .width = extent.width, .height = extent.height };
1400 GSList *interpolated_in_roi = _get_map_extent(&roi_in, interpolated, &extent);
1401
1402 float complex *map = create_global_distortion_map(&extent, interpolated_in_roi, inverted);
1403 g_slist_free(interpolated_in_roi);
1404 interpolated_in_roi = NULL;
1405 g_list_free_full(interpolated, dt_free_gpointer);
1406 interpolated = NULL;
1407
1408 if(IS_NULL_PTR(map)) return 0;
1409
1410 const int map_size = extent.width * extent.height;
1411 const int x_last = extent.x + extent.width;
1412 const int y_last = extent.y + extent.height;
1413
1414 // apply distortion to all points (this is a simple displacement given by a vector at this same point in the map)
1415 __OMP_PARALLEL_FOR__(if(points_count > 100))
1416 for(size_t i = 0; i < points_count; i++)
1417 {
1418 float *px = &points[i*2];
1419 float *py = &points[i*2+1];
1420 const float x = *px;
1421 const float y = *py;
1422 const int map_offset = ((int)(x - 0.5) - extent.x) + ((int)(y - 0.5) - extent.y) * extent.width;
1423
1424 if(x >= extent.x && x < x_last && y >= extent.y && y < y_last && map_offset >= 0 && map_offset < map_size)
1425 {
1426 const float complex dist = map[map_offset];
1427 *px += crealf(dist);
1428 *py += cimagf(dist);
1429 }
1430 }
1431
1433 }
1434
1435 return 1;
1436}
1437
1439{
1440 g->dragging.layer = layer;
1441 g->dragging.elem = elem;
1442}
1443
1445{
1446 g->dragging = NOWHERE;
1447}
1448
1450{
1451 return !IS_NULL_PTR(g->dragging.elem);
1452}
1453
1455 float *const restrict points, size_t points_count)
1456{
1457 // Recurse on the caller pipe so we reuse the same node list and piece data in preview, export,
1458 // thumbnail, and mask evaluation paths.
1459 return _distort_xtransform(self, pipe, piece, points, points_count, TRUE);
1460}
1461
1463 float *const restrict points, size_t points_count)
1464{
1465 return _distort_xtransform(self, pipe, piece, points, points_count, FALSE);
1466}
1467
1468void distort_mask(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe, struct dt_dev_pixelpipe_iop_t *piece,
1469 const float *const in, float *const out, const dt_iop_roi_t *const roi_in,
1470 const dt_iop_roi_t *const roi_out)
1471{
1472 (void)pipe;
1473 // 1. copy the whole image (we'll change only a small part of it)
1475 for(int i = 0; i < roi_out->height; i++)
1476 {
1477 float *destrow = out + (size_t) i * roi_out->width;
1478 const float *srcrow = in + (size_t) (roi_in->width * (i + roi_out->y - roi_in->y) + roi_out->x - roi_in->x);
1479
1480 memcpy(destrow, srcrow, sizeof(float) * roi_out->width);
1481 }
1482
1483 // 2. build the distortion map
1484
1485 cairo_rectangle_int_t map_extent;
1486 float complex *map = build_global_distortion_map(self, pipe, piece, roi_in, roi_out, &map_extent);
1487 if(IS_NULL_PTR(map))
1488 {
1489 if(map_extent.width != 0 && map_extent.height != 0) return;
1490 return;
1491 }
1492
1493 // 3. apply the map
1494
1495 if(map_extent.width != 0 && map_extent.height != 0)
1496 apply_global_distortion_map(self, piece, in, out, roi_in, roi_out, 1, map, &map_extent);
1497
1499
1500}
1501
1503int process(struct dt_iop_module_t *module, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece,
1504 const void *const in,
1505 void *const out)
1506{
1507 const dt_iop_roi_t *const roi_in = &piece->roi_in;
1508 const dt_iop_roi_t *const roi_out = &piece->roi_out;
1509 // 1. copy the whole image (we'll change only a small part of it)
1510
1511 const int ch = 4;
1512 const int height = MIN(roi_in->height, roi_out->height);
1513 const int width = MIN(roi_in->width, roi_out->width);
1515 for(int i = 0; i < height; i++)
1516 {
1517 float *destrow = (float *)out + (size_t)ch * i * roi_out->width;
1518 const float *srcrow = (float *)in + (size_t)ch * (roi_in->width * (i + roi_out->y - roi_in->y) +
1519 roi_out->x - roi_in->x);
1520
1521 memcpy(destrow, srcrow, sizeof(float) * ch * width);
1522 }
1523
1524 // 2. build the distortion map
1525
1526 cairo_rectangle_int_t map_extent;
1527 float complex *map = build_global_distortion_map(module, pipe, piece, roi_in, roi_out, &map_extent);
1528 if(IS_NULL_PTR(map))
1529 {
1530 if(map_extent.width != 0 && map_extent.height != 0) return 1;
1531 return 0;
1532 }
1533
1534 // 3. apply the map
1535
1536 if(map_extent.width != 0 && map_extent.height != 0)
1537 apply_global_distortion_map(module, piece, in, out, roi_in, roi_out, 1, map, &map_extent);
1538
1540
1541 return 0;
1542}
1543
1544#ifdef HAVE_OPENCL
1545
1546// compute bicubic kernel. See: https://en.wikipedia.org/wiki/Bicubic_interpolation#Bicubic_convolution_algorithm
1547
1548static inline float bicubic(const float a, const float x)
1549{
1550 const float absx = fabsf(x);
1551 if(absx <= 1) return ((a + 2) * absx - (a + 3)) * absx * absx + 1;
1552 if(absx < 2) return ((a * absx - 5 * a) * absx + 8 * a) * absx - 4 * a;
1553 return 0.0f;
1554}
1555
1556// compute Mitchell-Netravali cubic kernel (B=C=1/3): sharp but effectively
1557// halo-free, unlike Catmull-Rom bicubic and Lanczos which overshoot at edges.
1558
1559static inline float mitchell(const float x)
1560{
1561 const float absx = fabsf(x);
1562 const float x2 = absx * absx;
1563 const float x3 = x2 * absx;
1564 if(absx < 1.0f) return (7.0f / 6.0f) * x3 - 2.0f * x2 + 8.0f / 9.0f;
1565 if(absx < 2.0f) return -(7.0f / 18.0f) * x3 + 2.0f * x2 - (10.0f / 3.0f) * absx + 16.0f / 9.0f;
1566 return 0.0f;
1567}
1568
1569typedef struct
1570{
1571 int size;
1574
1575typedef cl_mem cl_mem_t;
1576typedef cl_int cl_int_t;
1577
1579 const dt_dev_pixelpipe_t *pipe,
1580 const dt_dev_pixelpipe_iop_t *piece,
1581 const cl_mem_t dev_in,
1582 const cl_mem_t dev_out,
1583 const dt_iop_roi_t *roi_in,
1584 const dt_iop_roi_t *roi_out,
1585 const float complex *map,
1586 const cairo_rectangle_int_t *map_extent)
1587{
1588 cl_int_t err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
1589
1591 const int devid = pipe->devid;
1592
1594 dt_liquify_kernel_descriptor_t kdesc = { .size = 0, .resolution = 100 };
1595 float *k = NULL;
1596
1597 switch (interpolation->id)
1598 {
1600 kdesc.size = 1;
1601 kdesc.resolution = 1;
1602 k = malloc(sizeof(float) * 2);
1603 if(k)
1604 {
1605 k[0] = 1.0f;
1606 k[1] = 0.0f;
1607 }
1608 break;
1610 kdesc.size = 2;
1611 k = malloc(sizeof(float) * ((size_t)kdesc.size * kdesc.resolution + 1));
1612 if(k)
1613 for(int i = 0; i <= kdesc.size * kdesc.resolution; ++i)
1614 k[i] = bicubic(0.5f, (float) i / kdesc.resolution);
1615 break;
1617 kdesc.size = 2;
1618 k = malloc(sizeof(float) * ((size_t)kdesc.size * kdesc.resolution + 1));
1619 if(k)
1620 for(int i = 0; i <= kdesc.size * kdesc.resolution; ++i)
1621 k[i] = mitchell((float) i / kdesc.resolution);
1622 break;
1623 default:
1624 return FALSE;
1625 }
1626
1628 (devid, sizeof(dt_iop_roi_t), (void *) roi_in);
1629
1631 (devid, sizeof(dt_iop_roi_t), (void *) roi_out);
1632
1634 (devid, sizeof(float complex) * map_extent->width * map_extent->height, (void *) map);
1635
1637 (devid, sizeof(cairo_rectangle_int_t), (void *) map_extent);
1638
1640 (devid, sizeof(dt_liquify_kernel_descriptor_t), (void *) &kdesc);
1641
1643 (devid, sizeof(float) * (kdesc.size * kdesc.resolution + 1), (void *) k);
1644
1645 if(IS_NULL_PTR(dev_roi_in) || IS_NULL_PTR(dev_roi_out) || IS_NULL_PTR(dev_map) || IS_NULL_PTR(dev_map_extent)
1646 || IS_NULL_PTR(dev_kdesc) || IS_NULL_PTR(dev_kernel) || IS_NULL_PTR(k))
1647 goto error;
1648
1649 dt_opencl_set_kernel_arg(devid, gd->warp_kernel, 0, sizeof(cl_mem), &dev_in);
1650 dt_opencl_set_kernel_arg(devid, gd->warp_kernel, 1, sizeof(cl_mem), &dev_out);
1651 dt_opencl_set_kernel_arg(devid, gd->warp_kernel, 2, sizeof(cl_mem), &dev_roi_in);
1652 dt_opencl_set_kernel_arg(devid, gd->warp_kernel, 3, sizeof(cl_mem), &dev_roi_out);
1653 dt_opencl_set_kernel_arg(devid, gd->warp_kernel, 4, sizeof(cl_mem), &dev_map);
1654 dt_opencl_set_kernel_arg(devid, gd->warp_kernel, 5, sizeof(cl_mem), &dev_map_extent);
1655
1656 dt_opencl_set_kernel_arg(devid, gd->warp_kernel, 6, sizeof(cl_mem), &dev_kdesc);
1657 dt_opencl_set_kernel_arg(devid, gd->warp_kernel, 7, sizeof(cl_mem), &dev_kernel);
1658
1659 const size_t sizes[] = { ROUNDUPDWD(map_extent->width, devid), ROUNDUPDHT(map_extent->height, devid) };
1660 err = dt_opencl_enqueue_kernel_2d(devid, gd->warp_kernel, sizes);
1661
1662error:
1663
1664 dt_opencl_release_mem_object(dev_kernel);
1666 dt_opencl_release_mem_object(dev_map_extent);
1668 dt_opencl_release_mem_object(dev_roi_out);
1669 dt_opencl_release_mem_object(dev_roi_in);
1670 dt_free(k);
1671
1672 return err;
1673}
1674
1675int process_cl(struct dt_iop_module_t *module, const dt_dev_pixelpipe_t *pipe,
1676 const dt_dev_pixelpipe_iop_t *piece,
1677 const cl_mem_t dev_in,
1678 const cl_mem_t dev_out)
1679{
1680 const dt_iop_roi_t *const roi_in = &piece->roi_in;
1681 const dt_iop_roi_t *const roi_out = &piece->roi_out;
1682 cl_int_t err = -999;
1683 const int devid = pipe->devid;
1684 const int height = MIN(roi_in->height, roi_out->height);
1685 const int width = MIN(roi_in->width, roi_out->width);
1686
1687 // 1. copy the whole image (we'll change only a small part of it)
1688 {
1689 size_t src[] = { roi_out->x - roi_in->x, roi_out->y - roi_in->y, 0 };
1690 size_t dest[] = { 0, 0, 0 };
1691 size_t extent[] = { width, height, 1 };
1692 err = dt_opencl_enqueue_copy_image(devid, dev_in, dev_out, src, dest, extent);
1693 if(err != CL_SUCCESS) goto error;
1694 }
1695
1696 // 2. build the distortion map
1697 cairo_rectangle_int_t map_extent;
1698 const float complex *map = build_global_distortion_map(module, pipe, piece, roi_in, roi_out, &map_extent);
1699 if(IS_NULL_PTR(map))
1700 {
1701 if(map_extent.width != 0 && map_extent.height != 0) return FALSE;
1702 return TRUE;
1703 }
1704
1705 // 3. apply the map
1706 if(map_extent.width != 0 && map_extent.height != 0)
1707 err = apply_global_distortion_map_cl(module, pipe, piece, dev_in, dev_out, roi_in, roi_out, map, &map_extent);
1709 if(err != CL_SUCCESS) goto error;
1710
1711 return TRUE;
1712
1713error:
1714 dt_print(DT_DEBUG_OPENCL, "[opencl_liquify] couldn't enqueue kernel! %d\n", err);
1715 return FALSE;
1716}
1717
1718#endif
1719
1721{
1722 // called once at startup
1723 const int program = 17; // from programs.conf
1725 module->data = gd;
1726 gd->warp_kernel = dt_opencl_create_kernel(program, "warp_kernel");
1727}
1728
1730{
1731 // called once at shutdown
1734 dt_free(module->data);
1735}
1736
1738{
1739 // module is disabled by default
1740 module->default_enabled = 0;
1741 module->params_size = sizeof(dt_iop_liquify_params_t);
1742 module->gui_data = NULL;
1743
1744 // all allocated to 0, which is the default
1745 module->params = calloc(1, module->params_size);
1746 module->default_params = calloc(1, module->params_size);
1747}
1748
1750{
1752 piece->data_size = sizeof(dt_iop_liquify_params_t);
1753}
1754
1756{
1757 dt_free_align(piece->data);
1758 piece->data = NULL;
1759}
1760
1761/* commit is the synch point between core and gui, so it copies params to pipe data. */
1762
1763void commit_params(struct dt_iop_module_t *module,
1764 dt_iop_params_t *params,
1765 dt_dev_pixelpipe_t *pipe,
1767{
1768 memcpy(piece->data, params, module->params_size);
1769}
1770
1771// calculate the dot product of 2 vectors.
1772
1773static float cdot(const float complex p0, const float complex p1)
1774{
1775#ifdef FP_FAST_FMA
1776 return fma(crealf(p0), crealf(p1), cimagf(p0) * cimagf(p1));
1777#else
1778 return crealf(p0) * crealf(p1) + cimagf(p0) * cimagf(p1);
1779#endif
1780}
1781
1782static void draw_rectangle(cairo_t *cr, const float complex pt, const double theta, const double size)
1783{
1784 const double x = creal(pt), y = cimag(pt);
1785 cairo_save(cr);
1786 cairo_translate(cr, x, y);
1787 cairo_rotate(cr, theta);
1788 cairo_rectangle(cr, -size / 2.0, -size / 2.0, size, size);
1789 cairo_restore(cr);
1790}
1791
1792static void draw_triangle(cairo_t *cr, const float complex pt, const double theta, const double size)
1793{
1794 const double x = creal(pt), y = cimag(pt);
1795 cairo_save(cr);
1796 cairo_translate(cr, x, y);
1797 cairo_rotate(cr, theta);
1798 cairo_move_to(cr, -size, -size / 2.0);
1799 cairo_line_to(cr, 0, 0 );
1800 cairo_line_to(cr, -size, +size / 2.0);
1801 cairo_close_path(cr);
1802 cairo_restore(cr);
1803}
1804
1805static void draw_circle(cairo_t *cr, const float complex pt, const double diameter)
1806{
1807 const double x = creal(pt), y = cimag(pt);
1808 cairo_save(cr);
1809 cairo_new_sub_path(cr);
1810 cairo_arc(cr, x, y, diameter / 2.0, 0, 2 * DT_M_PI);
1811 cairo_restore(cr);
1812}
1813
1814static void set_source_rgba(cairo_t *cr, dt_liquify_rgba_t rgba)
1815{
1816 cairo_set_source_rgba(cr, rgba.red, rgba.green, rgba.blue, rgba.alpha);
1817}
1818
1819static float get_ui_width(const float scale, const dt_liquify_ui_width_enum_t w)
1820{
1821 assert(w >= 0 && w < DT_LIQUIFY_UI_WIDTH_LAST);
1822 return scale * DT_PIXEL_APPLY_DPI(dt_liquify_ui_widths[w]);
1823}
1824
1825#define GET_UI_WIDTH(a) (get_ui_width(scale, DT_LIQUIFY_UI_WIDTH_##a))
1826
1827static void set_line_width(cairo_t *cr, double scale, dt_liquify_ui_width_enum_t w)
1828{
1829 const double width = get_ui_width(scale, w);
1830 cairo_set_line_width(cr, width);
1831}
1832
1833static gboolean detect_drag(const dt_iop_liquify_gui_data_t *g, const double scale, const float complex pt)
1834{
1835 // g->last_button1_pressed_pos is valid only while BUTTON1 is down
1836 return g->last_button1_pressed_pos != -1.0 &&
1837 cabsf(pt - g->last_button1_pressed_pos) >= (GET_UI_WIDTH(MIN_DRAG) / scale);
1838}
1839
1841{
1842 guint warp = 0, node = 0;
1843 for(int k=0; k<MAX_NODES; k++)
1844 if(g->params.nodes[k].header.type == DT_LIQUIFY_PATH_INVALIDATED)
1845 break;
1846 else
1847 {
1848 node++;
1849 if(g->params.nodes[k].header.type == DT_LIQUIFY_PATH_MOVE_TO_V1)
1850 warp++;
1851 }
1852 char str_warp[10], str_node[20];
1853 snprintf(str_warp, sizeof(str_warp), "%d", warp);
1854 snprintf(str_node, sizeof(str_node), "%d", node);
1855 gtk_label_set_text(g->label_warp, str_warp);
1856 gtk_label_set_text(g->label_node, str_node);
1857}
1858
1860{
1861 GList *l = NULL;
1862 for(int k=0; k<MAX_NODES; k++)
1863 {
1864 const dt_liquify_path_data_t *data = &p->nodes[k];
1866 break;
1867
1868 const float complex *p2 = &data->warp.point;
1869 const dt_liquify_warp_t *warp2 = &data->warp;
1870
1872 {
1873 if(data->header.next == -1)
1874 {
1875 dt_liquify_warp_t *w = malloc(sizeof(dt_liquify_warp_t));
1876 *w = *warp2;
1877 l = g_list_append(l, w);
1878 }
1879 continue;
1880 }
1881
1882 const dt_liquify_path_data_t *prev = node_prev(p, data);
1883 const dt_liquify_warp_t *warp1 = &prev->warp;
1884 const float complex *p1 = &prev->warp.point;
1886 {
1887 const float total_length = cabsf(*p1 - *p2);
1888 float arc_length = 0.0f;
1889 while(arc_length < total_length)
1890 {
1891 dt_liquify_warp_t *w = malloc(sizeof(dt_liquify_warp_t));
1892 const float t = arc_length / total_length;
1893 const float complex pt = cmix(*p1, *p2, t);
1894 mix_warps(w, warp1, warp2, pt, t);
1896 arc_length += cabsf(w->radius - w->point) * STAMP_RELOCATION;
1897 l = g_list_append(l, w);
1898 }
1899 continue;
1900 }
1901
1903 {
1904 float complex *buffer = malloc(sizeof(float complex) * INTERPOLATION_POINTS);
1906 data->node.ctrl1,
1907 data->node.ctrl2,
1908 *p2,
1909 buffer,
1911 const float total_length = get_arc_length(buffer, INTERPOLATION_POINTS);
1912 float arc_length = 0.0f;
1913 restart_cookie_t restart = { 1, 0.0 };
1914
1915 while(arc_length < total_length)
1916 {
1917 dt_liquify_warp_t *w = malloc(sizeof(dt_liquify_warp_t));
1918 const float t = arc_length / total_length;
1919 const float complex pt = point_at_arc_length(buffer, INTERPOLATION_POINTS, arc_length, &restart);
1920 mix_warps(w, warp1, warp2, pt, t);
1922 arc_length += cabsf(w->radius - w->point) * STAMP_RELOCATION;
1923 l = g_list_append(l, w);
1924 }
1925 dt_free(buffer);
1926 continue;
1927 }
1928 }
1929 return l;
1930}
1931
1932#define FG_COLOR set_source_rgba(cr, fg_color)
1933#define BG_COLOR set_source_rgba(cr, bg_color)
1934#define VERYTHINLINE set_line_width (cr, scale / 2.0f, DT_LIQUIFY_UI_WIDTH_THINLINE)
1935#define THINLINE set_line_width (cr, scale, DT_LIQUIFY_UI_WIDTH_THINLINE)
1936#define THICKLINE set_line_width (cr, scale, DT_LIQUIFY_UI_WIDTH_THICKLINE)
1937
1938static void _draw_paths(dt_iop_module_t *module,
1939 cairo_t *cr,
1940 const float scale,
1942 GList *layers)
1943{
1945
1946 cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
1947
1948 // do not display any iterpolated items as slow when:
1949 // - we are dragging (pan)
1950 // - the button one is pressed
1951 // - exception for DT_LIQUIFY_LAYER_STRENGTHPOINT where we want to see the
1952 // interpolated strength lines.
1953 GList *interpolated = (is_dragging(g) || g->last_button1_pressed_pos != -1)
1954 && (g->last_hit.layer != DT_LIQUIFY_LAYER_STRENGTHPOINT)
1955 ? NULL
1957
1958 for(const GList *l = layers; l; l = g_list_next(l))
1959 {
1960 const dt_liquify_layer_enum_t layer = (dt_liquify_layer_enum_t) GPOINTER_TO_INT(l->data);
1961 dt_liquify_rgba_t fg_color = dt_liquify_layers[layer].fg;
1962 dt_liquify_rgba_t bg_color = dt_liquify_layers[layer].bg;
1963
1964 if(dt_liquify_layers[layer].opacity < 1.0)
1965 cairo_push_group(cr);
1966
1967 for(int k=0; k<MAX_NODES; k++)
1968 {
1969 // this is an empty bin, old invalidated node, nothing more to do
1970 if(p->nodes[k].header.type == DT_LIQUIFY_PATH_INVALIDATED)
1971 break;
1972
1973 dt_liquify_path_data_t *data = &p->nodes[k];
1974 const dt_liquify_path_data_t *prev = node_prev(p, data);
1975
1977 && !data->header.selected)
1978 continue;
1979
1981 && (IS_NULL_PTR(prev) || !prev->header.selected))
1982 continue;
1983
1984 fg_color = dt_liquify_layers[layer].fg;
1985 bg_color = dt_liquify_layers[layer].bg;
1986
1987 if(data->header.selected == layer)
1988 fg_color = DT_LIQUIFY_COLOR_SELECTED;
1989
1990 if(data->header.hovered == dt_liquify_layers[layer].hover_master)
1991 fg_color = DT_LIQUIFY_COLOR_HOVER;
1992
1993 cairo_new_path(cr);
1994
1995 const float complex point = data->warp.point;
1996
1998 cairo_move_to(cr, crealf(point), cimagf(point));
1999
2000 if(layer == DT_LIQUIFY_LAYER_RADIUS)
2001 {
2002 for(const GList *i = interpolated; i; i = g_list_next(i))
2003 {
2004 const dt_liquify_warp_t *pwarp = ((dt_liquify_warp_t *) i->data);
2005 draw_circle(cr, pwarp->point, 2.0f * cabsf(pwarp->radius - pwarp->point));
2006 }
2007 draw_circle(cr, point, 2.0f * cabsf(data->warp.radius - data->warp.point));
2008 FG_COLOR;
2009 cairo_fill(cr);
2010 }
2011 else if(layer == DT_LIQUIFY_LAYER_HARDNESS1)
2012 {
2013 for(const GList *i = interpolated; i; i = g_list_next(i))
2014 {
2015 const dt_liquify_warp_t *pwarp = ((dt_liquify_warp_t *) i->data);
2016 draw_circle(cr, pwarp->point, 2.0f * cabsf(pwarp->radius - pwarp->point) * pwarp->control1);
2017 }
2018 FG_COLOR;
2019 cairo_fill(cr);
2020 }
2021 else if(layer == DT_LIQUIFY_LAYER_HARDNESS2)
2022 {
2023 for(const GList *i = interpolated; i; i = g_list_next(i))
2024 {
2025 const dt_liquify_warp_t *pwarp = ((dt_liquify_warp_t *) i->data);
2026 draw_circle(cr, pwarp->point, 2.0f * cabsf(pwarp->radius - pwarp->point) * pwarp->control2);
2027 }
2028 FG_COLOR;
2029 cairo_fill(cr);
2030 }
2031 else if(layer == DT_LIQUIFY_LAYER_WARPS)
2032 {
2034 for(const GList *i = interpolated; i; i = g_list_next(i))
2035 {
2036 const dt_liquify_warp_t *pwarp = ((dt_liquify_warp_t *) i->data);
2037 cairo_move_to(cr, crealf(pwarp->point), cimagf(pwarp->point));
2038 cairo_line_to(cr, crealf(pwarp->strength), cimagf(pwarp->strength));
2039 }
2040 cairo_stroke(cr);
2041
2042 for(const GList *i = interpolated; i; i = g_list_next(i))
2043 {
2044 const dt_liquify_warp_t *pwarp = ((dt_liquify_warp_t *) i->data);
2045 const float rot = get_rot(pwarp->type);
2046 draw_circle(cr, pwarp->point, GET_UI_WIDTH(GIZMO_SMALL));
2047 draw_triangle(cr, pwarp->strength,
2048 cargf(pwarp->strength - pwarp->point) + rot,
2049 GET_UI_WIDTH(GIZMO_SMALL) / 3.0);
2050 }
2051 BG_COLOR;
2052 cairo_fill_preserve(cr);
2053 FG_COLOR;
2054 cairo_stroke(cr);
2055 }
2056 else if(layer == DT_LIQUIFY_LAYER_PATH)
2057 {
2060 {
2061 assert(prev);
2062 cairo_move_to(cr, crealf(prev->warp.point), cimagf(prev->warp.point));
2064 cairo_line_to(cr, crealf(point), cimagf(point));
2066 {
2067 cairo_curve_to(cr, crealf(data->node.ctrl1), cimagf(data->node.ctrl1),
2068 crealf(data->node.ctrl2), cimagf(data->node.ctrl2),
2069 crealf(point), cimagf(point));
2070 }
2072 cairo_stroke_preserve(cr);
2074 cairo_stroke(cr);
2075 }
2076 }
2077 else if(layer == DT_LIQUIFY_LAYER_CENTERPOINT)
2078 {
2082 {
2083 const float w = GET_UI_WIDTH(GIZMO);
2084 switch (data->header.node_type)
2085 {
2087 draw_triangle(cr, point - w / 2.0 * I, -DT_M_PI / 2.0, w);
2088 break;
2090 draw_rectangle(cr, point, DT_M_PI / 4.0, w);
2091 break;
2093 draw_rectangle(cr, point, 0, w);
2094 break;
2096 draw_circle(cr, point, w);
2097 break;
2098 default:
2099 break;
2100 }
2102 cairo_fill_preserve(cr);
2103 FG_COLOR;
2104 cairo_stroke(cr);
2105 }
2106 }
2107
2109 {
2112 {
2114 cairo_move_to(cr, crealf(prev->warp.point), cimagf(prev->warp.point));
2115 cairo_line_to(cr, crealf(data->node.ctrl1), cimagf(data->node.ctrl1));
2116 cairo_stroke(cr);
2117 }
2120 {
2122 cairo_move_to(cr, crealf(data->warp.point), cimagf(data->warp.point));
2123 cairo_line_to(cr, crealf(data->node.ctrl2), cimagf(data->node.ctrl2));
2124 cairo_stroke(cr);
2125 }
2126 if(layer == DT_LIQUIFY_LAYER_CTRLPOINT1 &&
2128 {
2130 draw_circle(cr, data->node.ctrl1, GET_UI_WIDTH(GIZMO_SMALL));
2131 cairo_fill_preserve(cr);
2132 FG_COLOR;
2133 cairo_stroke(cr);
2134 }
2135 if(layer == DT_LIQUIFY_LAYER_CTRLPOINT2 &&
2137 {
2139 draw_circle(cr, data->node.ctrl2, GET_UI_WIDTH(GIZMO_SMALL));
2140 cairo_fill_preserve(cr);
2141 FG_COLOR;
2142 cairo_stroke(cr);
2143 }
2144 }
2145
2146 const dt_liquify_warp_t *warp = &data->warp;
2147
2149 {
2150 draw_circle(cr, point, 2.0 * cabsf(warp->radius - point));
2152 cairo_stroke_preserve(cr);
2154 cairo_stroke(cr);
2155 }
2156
2157 if(layer == DT_LIQUIFY_LAYER_RADIUSPOINT)
2158 {
2160 draw_circle(cr, warp->radius, GET_UI_WIDTH(GIZMO_SMALL));
2161 cairo_fill_preserve(cr);
2162 FG_COLOR;
2163 cairo_stroke(cr);
2164 }
2165
2167 {
2168 draw_circle(cr, point, 2.0 * cabsf(warp->radius - point) * warp->control1);
2170 cairo_stroke_preserve(cr);
2172 cairo_stroke(cr);
2173 }
2174
2176 {
2177 draw_circle(cr, point, 2.0 * cabsf(warp->radius - point) * warp->control2);
2179 cairo_stroke_preserve(cr);
2181 cairo_stroke(cr);
2182 }
2183
2185 {
2186 draw_triangle(cr, cmix(point, warp->radius, warp->control1),
2187 cargf(warp->radius - point),
2188 GET_UI_WIDTH(GIZMO_SMALL));
2190 cairo_fill_preserve(cr);
2191 FG_COLOR;
2192 cairo_stroke(cr);
2193 }
2194
2196 {
2197 draw_triangle(cr, cmix(point, warp->radius, warp->control2),
2198 cargf(-(warp->radius - point)),
2199 GET_UI_WIDTH(GIZMO_SMALL));
2201 cairo_fill_preserve(cr);
2202 FG_COLOR;
2203 cairo_stroke(cr);
2204 }
2205
2207 {
2208 cairo_move_to(cr, crealf(point), cimagf(point));
2210 {
2211 const float complex pt = cmix(point, warp->strength,
2212 1.0 - 0.5
2213 * (GET_UI_WIDTH(GIZMO_SMALL)
2214 / cabsf(warp->strength - point)));
2215 cairo_line_to(cr, crealf(pt), cimagf(pt));
2216 }
2217 else
2218 draw_circle(cr, point, 2.0 * cabsf(warp->strength - warp->point));
2220 cairo_stroke_preserve(cr);
2222 cairo_stroke(cr);
2223 }
2224
2226 {
2227 cairo_move_to(cr, crealf(warp->strength), cimagf(warp->strength));
2228 const float rot = get_rot(warp->type);
2229 draw_triangle(cr, warp->strength,
2230 cargf(warp->strength - warp->point) + rot,
2231 GET_UI_WIDTH(GIZMO_SMALL));
2233 cairo_fill_preserve(cr);
2234 FG_COLOR;
2235 cairo_stroke(cr);
2236 }
2237 }
2238
2239 if(dt_liquify_layers[layer].opacity < 1.0)
2240 {
2241 cairo_pop_group_to_source(cr);
2242 cairo_paint_with_alpha(cr, dt_liquify_layers[layer].opacity);
2243 }
2244 }
2245
2246 g_list_free_full(interpolated, dt_free_gpointer);
2247 interpolated = NULL;
2248}
2249
2250/*
2251 Find the nearest point on a cubic bezier curve.
2252
2253 Return the curve parameter t of the point on a cubic bezier curve
2254 that is nearest to another arbitrary point. Uses interpolation.
2255
2256 FIXME: Implement a faster method, see:
2257 http://tog.acm.org/resources/GraphicsGems/gems/NearestPoint.c
2258*/
2259
2260static float find_nearest_on_curve_t(const float complex p0,
2261 const float complex p1,
2262 const float complex p2,
2263 const float complex p3,
2264 const float complex x,
2265 const int n)
2266{
2267 float min_t = 0.0f, min_dist = cabsf(x - p0);
2268
2269 for(int i = 0; i < n; i++)
2270 {
2271 const float t = (1.0 * i) / n;
2272 const float t1 = 1.0 - t;
2273 const float complex ip =
2274 t1 * t1 * t1 * p0 +
2275 3 * t1 * t1 * t * p1 +
2276 3 * t1 * t * t * p2 +
2277 t * t * t * p3;
2278
2279 const float dist = cabsf(x - ip);
2280 if(dist < min_dist)
2281 {
2282 min_dist = dist;
2283 min_t = t;
2284 }
2285 }
2286 return min_t;
2287}
2288
2289/*
2290 Find the nearest point on a line.
2291
2292 Return the line parameter t of the point on a line that is nearest
2293 to another arbitrary point.
2294*/
2295
2296static float find_nearest_on_line_t(const float complex p0, const float complex p1, const float complex x)
2297{
2298 // scalar projection
2299 const float b = cabsf(p1 - p0); // |b|
2300 const float dotab = cdot(x - p0, p1 - p0); // |a| * |b| * cos(phi)
2301 return dotab / (b * b); // |a| / |b| * cos(phi)
2302}
2303
2304// split a cubic bezier at t into two cubic beziers.
2305
2306static void casteljau(const float complex *p0, float complex *p1, float complex *p2, float complex *p3, const float t)
2307{
2308 const float complex p01 = *p0 + (*p1 - *p0) * t;
2309 const float complex p12 = *p1 + (*p2 - *p1) * t;
2310 const float complex p23 = *p2 + (*p3 - *p2) * t;
2311
2312 const float complex p012 = p01 + (p12 - p01) * t;
2313 const float complex p123 = p12 + (p23 - p12) * t;
2314
2315 const float complex p0123 = p012 + (p123 - p012) * t;
2316
2317 *p1 = p01;
2318 *p2 = p012;
2319 *p3 = p0123;
2320}
2321
2322#define CHECK_HIT_PT(point) \
2323 const float d = cabsf(point - (*pt)); \
2324 if(d < distance) \
2325 { \
2326 distance = d; \
2327 hit.layer = layer; \
2328 hit.elem = data; \
2329 }
2330
2333 GList *layers,
2334 const float complex *pt)
2335{
2337
2338 float distance = FLT_MAX;
2339
2340 for(const GList *l = layers; l; l = g_list_next(l))
2341 {
2342 const dt_liquify_layer_enum_t layer = (dt_liquify_layer_enum_t) GPOINTER_TO_INT(l->data);
2343
2345 continue;
2346
2347 for(int k=0; k<MAX_NODES; k++)
2348 {
2349 dt_liquify_path_data_t *data = &p->nodes[k];
2350 const dt_liquify_path_data_t *prev = node_prev(p, data);
2351
2352 if(p->nodes[k].header.type == DT_LIQUIFY_PATH_INVALIDATED)
2353 break;
2354
2356 && !data->header.selected)
2357 continue;
2358
2360 && (IS_NULL_PTR(prev) || !prev->header.selected))
2361 continue;
2362
2363 const dt_liquify_warp_t *warp = &data->warp;
2364 const float complex point = data->warp.point;
2365
2366 if(layer == DT_LIQUIFY_LAYER_PATH)
2367 {
2369 {
2370 // remove 5% from start and end of line as non sensible area
2371 // this is to avoid wrong interaction for center point on both
2372 // sides.
2373 const float complex deadzone = (point - prev->warp.point) / 20.0f;
2374 const float complex lp1 = prev->warp.point + deadzone;
2375 const float complex lp2 = point - deadzone;
2376 const float t = find_nearest_on_line_t(lp1, lp2, *pt);
2377
2378 if(t > 0.0f && t < 1.0f)
2379 {
2380 const float complex linepoint = cmix(lp1, lp2, t);
2381 const float d = cabsf(linepoint - *pt);
2382 if(d < distance)
2383 {
2384 distance = d;
2385 hit.layer = layer;
2386 hit.elem = data;
2387 }
2388 }
2389 }
2390 else if(data->header.type == DT_LIQUIFY_PATH_CURVE_TO_V1)
2391 {
2392 // remove 5% from start and end of line as non sensible area
2393 // this is to avoid wrong interaction for center point on both
2394 // sides.
2395 const float complex deadzone = (point - prev->warp.point) / 20.0f;
2396 const float complex lp1 = prev->warp.point + deadzone;
2397 const float complex lp2 = point - deadzone;
2398 const float t = find_nearest_on_curve_t(lp1, data->node.ctrl1, data->node.ctrl2, lp2, *pt, INTERPOLATION_POINTS);
2399
2400 if(t > 0.0f && t < 1.0f)
2401 {
2402 float complex curvepoint = lp2;
2403 float complex p1 = data->node.ctrl1;
2404 float complex p2 = data->node.ctrl2;
2405 casteljau(&lp1, &p1, &p2, &curvepoint, t);
2406
2407 const float d = cabsf(curvepoint - *pt);
2408 if(d < distance)
2409 {
2410 distance = d;
2411 hit.layer = layer;
2412 hit.elem = data;
2413 }
2414 }
2415 }
2416 }
2417 else if(layer == DT_LIQUIFY_LAYER_CENTERPOINT)
2418 {
2422 {
2424 }
2425 }
2426 else if(layer == DT_LIQUIFY_LAYER_RADIUSPOINT)
2427 {
2428 CHECK_HIT_PT(warp->radius);
2429 }
2430 else if(layer == DT_LIQUIFY_LAYER_HARDNESSPOINT1)
2431 {
2432 CHECK_HIT_PT(cmix(point, warp->radius, warp->control1));
2433 }
2434 else if(layer == DT_LIQUIFY_LAYER_HARDNESSPOINT2)
2435 {
2436 CHECK_HIT_PT(cmix(point, warp->radius, warp->control2));
2437 }
2438 else if(layer == DT_LIQUIFY_LAYER_STRENGTHPOINT)
2439 {
2440 const float complex p = warp->point - warp->strength;
2441 CHECK_HIT_PT(warp->strength + (float)DT_PIXEL_APPLY_DPI(5) * (p / cabsf(p)));
2442 }
2443
2445 {
2446 if(layer == DT_LIQUIFY_LAYER_CTRLPOINT1 &&
2448 {
2449 CHECK_HIT_PT(data->node.ctrl1);
2450 }
2451 if(layer == DT_LIQUIFY_LAYER_CTRLPOINT2 &&
2453 {
2454 CHECK_HIT_PT(data->node.ctrl2);
2455 }
2456 }
2457 }
2458 }
2459
2460 if(distance < DT_GUI_MOUSE_EFFECT_RADIUS * 0.5)
2461 return hit;
2462 else
2463 return NOWHERE;
2464}
2465
2466static void draw_paths(struct dt_iop_module_t *module, cairo_t *cr, const float scale, dt_iop_liquify_params_t *params)
2467{
2469 GList *layers = NULL;
2470
2471 for(dt_liquify_layer_enum_t layer = 0; layer < DT_LIQUIFY_LAYER_LAST; ++layer)
2472 {
2473 if(gtk_toggle_button_get_active(g->btn_point_tool)
2475 layers = g_list_prepend(layers, GINT_TO_POINTER(layer));
2476 if(gtk_toggle_button_get_active(g->btn_line_tool)
2478 layers = g_list_prepend(layers, GINT_TO_POINTER(layer));
2479 if(gtk_toggle_button_get_active(g->btn_curve_tool)
2481 layers = g_list_prepend(layers, GINT_TO_POINTER(layer));
2482 if(gtk_toggle_button_get_active(g->btn_node_tool)
2484 layers = g_list_prepend(layers, GINT_TO_POINTER(layer));
2485 }
2486 layers = g_list_reverse(layers); // list was built in reverse order, so un-reverse it
2487
2488 _draw_paths(module, cr, scale, params, layers);
2489
2490 g_list_free(layers);
2491 layers = NULL;
2492}
2493
2496 float complex pt)
2497{
2499 GList *layers = NULL;
2500
2501 for(dt_liquify_layer_enum_t layer = 0; layer < DT_LIQUIFY_LAYER_LAST; ++layer)
2502 {
2504 layers = g_list_prepend(layers, GINT_TO_POINTER(layer));
2505 }
2506 layers = g_list_reverse(layers); // list was built in reverse order, so un-reverse it
2507
2508 hit = _hit_paths(module, params, layers, &pt);
2509 g_list_free(layers);
2510 layers = NULL;
2511 return hit;
2512}
2513
2568static void smooth_path_linsys(size_t n,
2569 const float complex *k,
2570 float complex *c1,
2571 float complex *c2,
2572 const int *equation)
2573{
2574 --n;
2575 float *a = malloc(sizeof(float) * n); // subdiagonal
2576 float *b = malloc(sizeof(float) * n); // main diagonal
2577 float *c = malloc(sizeof(float) * n); // superdiagonal
2578 float complex *d = malloc(sizeof(float complex) * n); // right hand side
2579
2580 // Build the tridiagonal matrix.
2581
2582 for(int i = 0; i < n; i++)
2583 {
2584 switch (equation[i])
2585 {
2586 #define ABCD(A,B,C,D) { { a[i] = A; b[i] = B; c[i] = C; d[i] = D; continue; } }
2587 case 1: ABCD(0, 2, 1, k[i] + 2 * k[i+1] ); break;
2588 case 2: ABCD(1, 4, 1, 4 * k[i] + 2 * k[i+1] ); break;
2589 case 3: ABCD(2, 7, 0, 8 * k[i] + k[i+1] ); break;
2590 case 4: ABCD(0, 1, 0, c1[i] ); break;
2591 case 5: ABCD(0, 1, 0, c1[i] ); break;
2592 case 6: ABCD(1, 4, 0, 4 * k[i] + c2[i] ); break;
2593 case 7: ABCD(0, 1, 0, c1[i] ); break;
2594 case 8: ABCD(0, 3, 0, 2 * k[i] + k[i+1] ); break;
2595 case 9: ABCD(0, 2, 0, k[i] + c2[i] ); break;
2596 #undef ABCD
2597 }
2598 }
2599
2600 // Solve with the Thomas algorithm to compute c1's. See:
2601 // http://en.wikipedia.org/wiki/Tridiagonal_matrix_algorithm
2602
2603 for(int i = 1; i < n; i++)
2604 {
2605 const float m = a[i] / b[i-1];
2606 b[i] = b[i] - m * c[i-1];
2607 d[i] = d[i] - m * d[i-1];
2608 }
2609
2610 c1[n-1] = d[n-1] / b[n-1];
2611 for(int i = n - 2; i >= 0; i--)
2612 c1[i] = (d[i] - c[i] * c1[i+1]) / b[i];
2613
2614 // Now compute the c2's.
2615
2616 for(int i = 0; i < n; i++)
2617 {
2618 switch (equation[i])
2619 {
2620 // keep end: c2 does not change
2621 case 5:
2622 case 6:
2623 case 9: break;
2624
2625 // straight end: put c2[i] halfway between c1[i] and k[i+1]
2626 case 3:
2627 case 7:
2628 case 8: c2[i] = (c1[i] + k[i+1]) / 2; break;
2629
2630 // smooth end: c2 and c1 are symmetrical around the knot
2631 default: c2[i] = 2 * k[i+1] - c1[i+1];
2632 }
2633 }
2634
2635 dt_free(a);
2636 dt_free(b);
2637 dt_free(c);
2638 dt_free(d);
2639}
2640
2642{
2643 int count = 1;
2644 while(n->header.next != -1)
2645 {
2646 count++;
2647 n = &p->nodes[n->header.next];
2648 }
2649 return count;
2650}
2651
2653{
2654 for(int k = 0; k < MAX_NODES; k++)
2655 {
2656 if(params->nodes[k].header.type == DT_LIQUIFY_PATH_INVALIDATED)
2657 break;
2658
2659 if(params->nodes[k].header.prev != -1)
2660 continue;
2661
2662 dt_liquify_path_data_t *node = &params->nodes[k];
2663
2664 const size_t n = path_length(params, node);
2665
2666 if(n < 2)
2667 continue;
2668
2669 float complex *pt = calloc(n, sizeof(float complex));
2670 float complex *c1 = calloc(n, sizeof(float complex));
2671 float complex *c2 = calloc(n, sizeof(float complex));
2672 int *eqn = calloc(n, sizeof(int));
2673 size_t idx = 0;
2674
2675 while(node)
2676 {
2678 const dt_liquify_path_data_t *p = node_prev(params, node);
2679 const dt_liquify_path_data_t *n = node_next(params, node);
2680 const dt_liquify_path_data_t *nn = n ? node_next(params, n) : NULL;
2681
2682 pt[idx] = node->warp.point;
2683 if(d->header.type == DT_LIQUIFY_PATH_CURVE_TO_V1)
2684 {
2685 c1[idx-1] = d->node.ctrl1;
2686 c2[idx-1] = d->node.ctrl2;
2687 }
2688
2689 const int autosmooth = d->header.node_type == DT_LIQUIFY_NODE_TYPE_AUTOSMOOTH;
2690 const int next_autosmooth = n && n->header.node_type == DT_LIQUIFY_NODE_TYPE_AUTOSMOOTH;
2691 const int firstseg = !p || d->header.type != DT_LIQUIFY_PATH_CURVE_TO_V1;
2692 const int lastseg = !nn || nn->header.type != DT_LIQUIFY_PATH_CURVE_TO_V1;
2693 const int lineseg = n && n->header.type == DT_LIQUIFY_PATH_LINE_TO_V1;
2694
2695 // Program the linear system with equations:
2696 //
2697 // START END
2698 // --------------------------
2699 // 1: straight smooth
2700 // 2: smooth smooth
2701 // 3: smooth straight
2702 // 4: keep smooth
2703 // 5: keep keep
2704 // 6: smooth keep
2705 // 7: keep straight
2706 // 8: straight straight (== line)
2707 // 9: straight keep
2708
2709 if(lineseg) eqn[idx] = 5;
2710 else if(!autosmooth && !next_autosmooth) eqn[idx] = 5;
2711 else if(firstseg && lastseg && !autosmooth && next_autosmooth) eqn[idx] = 7;
2712 else if(firstseg && lastseg && autosmooth && next_autosmooth) eqn[idx] = 8;
2713 else if(firstseg && lastseg && autosmooth && !next_autosmooth) eqn[idx] = 9;
2714 else if(firstseg && autosmooth && !next_autosmooth) eqn[idx] = 5;
2715 else if(firstseg && autosmooth) eqn[idx] = 1;
2716 else if(lastseg && autosmooth && next_autosmooth) eqn[idx] = 3;
2717 else if(lastseg && !autosmooth && next_autosmooth) eqn[idx] = 7;
2718 else if(autosmooth && !next_autosmooth) eqn[idx] = 6;
2719 else if(!autosmooth && next_autosmooth) eqn[idx] = 4;
2720 else eqn[idx] = 2;
2721
2722 ++idx;
2723 node = node_next(params, node);
2724 }
2725
2726 smooth_path_linsys(n, pt, c1, c2, eqn);
2727
2728 // write calculated control points back to list structure
2729 node = &params->nodes[k];
2730 node = node_next(params, node);
2731 idx = 0;
2732 while(node)
2733 {
2735 if(d->header.type == DT_LIQUIFY_PATH_CURVE_TO_V1)
2736 {
2737 d->node.ctrl1 = c1[idx];
2738 d->node.ctrl2 = c2[idx];
2739 }
2740 ++idx;
2741 node = node_next(params, node);
2742 }
2743
2744 dt_free(pt);
2745 dt_free(c1);
2746 dt_free(c2);
2747 dt_free(eqn);
2748 }
2749}
2750
2752{
2753 for(int k=0; k<MAX_NODES; k++)
2754 if(p->nodes[k].header.type == DT_LIQUIFY_PATH_INVALIDATED)
2755 break;
2756 else if(p->nodes[k].header.hovered)
2757 return &p->nodes[k];
2758 return NULL;
2759}
2760
2761static void init_warp(dt_liquify_warp_t *warp, float complex point)
2762{
2764 warp->point = point;
2765 warp->radius = point;
2766 warp->strength = point;
2767 warp->control1 = 0.5;
2768 warp->control2 = 0.75;
2770}
2771
2772static dt_liquify_path_data_t *alloc_move_to(dt_iop_module_t *module, float complex start_point)
2773{
2775 dt_liquify_path_data_t* m = (dt_liquify_path_data_t*)node_alloc(&g->params, &g->node_index);
2776 if(m)
2777 {
2779 m->header.node_type = DT_LIQUIFY_NODE_TYPE_AUTOSMOOTH;
2780 init_warp(&m->warp, start_point);
2781 }
2782 return (dt_liquify_path_data_t *)m;
2783}
2784
2785static dt_liquify_path_data_t *alloc_line_to(dt_iop_module_t *module, float complex end_point)
2786{
2788 dt_liquify_path_data_t* l = (dt_liquify_path_data_t*)node_alloc(&g->params, &g->node_index);
2789 if(!IS_NULL_PTR(l))
2790 {
2793 init_warp(&l->warp, end_point);
2794 }
2795 return (dt_liquify_path_data_t *)l;
2796}
2797
2798static dt_liquify_path_data_t *alloc_curve_to(dt_iop_module_t *module, float complex end_point)
2799{
2801 dt_liquify_path_data_t* c = (dt_liquify_path_data_t*)node_alloc(&g->params, &g->node_index);
2802 if(!IS_NULL_PTR(c))
2803 {
2804 c->header.type = DT_LIQUIFY_PATH_CURVE_TO_V1;
2805 c->header.node_type = DT_LIQUIFY_NODE_TYPE_AUTOSMOOTH;
2806 c->node.ctrl1 = c->node.ctrl2 = 0.0;
2807 init_warp(&c->warp, end_point);
2808 }
2809 return (dt_liquify_path_data_t *)c;
2810}
2811
2813{
2814 for(int k=0; k<MAX_NODES; k++)
2815 if(p->nodes[k].header.type == DT_LIQUIFY_PATH_INVALIDATED)
2816 break;
2817 else
2818 p->nodes[k].header.selected = 0;
2819}
2820
2821static float get_zoom_scale(const dt_develop_t *develop)
2822{
2823 return dt_dev_get_zoom_scale(develop, 1);
2824}
2825
2827 cairo_t *cr,
2828 int32_t width,
2829 int32_t height,
2830 int32_t pointerx,
2831 int32_t pointery)
2832{
2833 if(IS_NULL_PTR(module))
2834 return;
2835 dt_develop_t *develop = module->dev;
2836 if(IS_NULL_PTR(develop))
2837 return;
2839 if(IS_NULL_PTR(g))
2840 return;
2841
2842 const float bb_width = develop->roi.processed_width;
2843 const float bb_height = develop->roi.processed_height;
2844 if(bb_width < 1.0 || bb_height < 1.0)
2845 return;
2846
2847 // get a copy of all iop params
2850 smooth_paths_linsys(&g->params);
2851 dt_iop_liquify_params_t copy_params;
2852 memcpy(&copy_params, &g->params, sizeof(dt_iop_liquify_params_t));
2854
2855 // distort all points
2858 const distort_params_t d_params = { develop, develop->virtual_pipe, 1.0, 1.0, DT_DEV_TRANSFORM_DIR_ALL, FALSE };
2859 _distort_paths(module, &d_params, &copy_params);
2860
2861 // You're not supposed to understand this
2862 const float zoom_scale = get_zoom_scale(develop) * develop->roi.scaling;
2863
2864 if(dt_dev_rescale_roi_to_input(develop, cr, width, height))
2865 return;
2866
2867 draw_paths(module, cr, 1.0 / zoom_scale, &copy_params);
2868}
2869
2870static gboolean btn_make_radio_callback(GtkToggleButton *btn, GdkEventButton *event, dt_iop_module_t *module);
2871
2872void gui_focus(struct dt_iop_module_t *module, gboolean in)
2873{
2874 if(!in)
2875 {
2877 btn_make_radio_callback(NULL, NULL, module);
2878 }
2879}
2880
2881static void sync_pipe(struct dt_iop_module_t *module, gboolean history)
2882{
2883 if(history)
2884 {
2886 // something definitive has happened like button release ... so
2887 // redraw pipe
2888 memcpy(module->params, &g->params, sizeof(dt_iop_liquify_params_t));
2890 }
2891 else
2892 {
2893 // only moving mouse around, pointing at things or dragging ... so
2894 // give some cairo feedback, but don't redraw pipe
2896 }
2897}
2898
2899/*
2900 right-click on node: Delete node.
2901 right-click on path: Delete whole path.
2902
2903 ctrl+click on node: Cycle symmetrical, smooth, cusp, autosmooth
2904 ctrl+click on path: Add node
2905 ctrl+alt+click on path: Change line / bezier
2906
2907 ctrl+click on strength: Cycle linear, grow, shrink
2908*/
2909
2910static void get_point_scale(struct dt_iop_module_t *module, float x, float y, float complex *pt, float *scale)
2911{
2912 float pts[2] = { (float)x, (float)y };
2919
2920 *scale = get_zoom_scale(module->dev);
2921 *pt = pts[0] + pts[1] * I;
2922}
2923
2924static gboolean _is_movable_layer(const dt_liquify_layer_enum_t layer)
2925{
2926 switch(layer)
2927 {
2936 return TRUE;
2937 default:
2938 return FALSE;
2939 }
2940}
2941
2942int mouse_moved(struct dt_iop_module_t *module,
2943 double x,
2944 double y,
2945 double pressure,
2946 int which)
2947{
2949 gboolean handled = FALSE;
2950 float complex pt = 0.0f;
2951 float scale = 0.0f;
2952
2953 get_point_scale(module, x, y, &pt, &scale);
2954
2956
2957 const float complex prev_mouse_pos = g->last_mouse_pos;
2958 g->last_mouse_pos = pt;
2959
2960 // Don't hit test while dragging, you'd only hit the dragged thing
2961 // anyway.
2962
2963 if(!is_dragging(g))
2964 {
2965 dt_liquify_hit_t hit = _hit_test_paths(module, &g->params, pt);
2966 dt_control_queue_cursor_by_name(_is_movable_layer(hit.layer) ? "move" : "default");
2967 dt_liquify_path_data_t *last_hovered = find_hovered(&g->params);
2968 if(hit.elem != last_hovered
2969 || (!IS_NULL_PTR(last_hovered) && !IS_NULL_PTR(hit.elem)
2970 && hit.elem->header.hovered != last_hovered->header.hovered))
2971 {
2972 if(!IS_NULL_PTR(hit.elem))
2973 hit.elem->header.hovered = hit.layer;
2974 if(!IS_NULL_PTR(last_hovered))
2975 last_hovered->header.hovered = 0;
2976 // change in hover display
2978 handled = TRUE;
2979 goto done;
2980 }
2981
2982 const gboolean dragged = detect_drag(g, scale, pt);
2983
2984 if(dragged && !IS_NULL_PTR(g->last_hit.elem))
2985 {
2986 // start dragging
2987 start_drag(g, g->last_hit.layer, g->last_hit.elem);
2988 // nothing more to do, we will refresh on the next call anyway
2989 // this makes the initial move of a node a bit more fluid.
2990 handled = TRUE;
2991 goto done;
2992 }
2993
2994 if(!IS_NULL_PTR(g->last_hit.elem))
2995 {
2996 // an item is selected, so this mouvement is handled and must
2997 // not trigger any panning.
2998 handled = TRUE;
2999 }
3000 }
3001 else // we are dragging
3002 {
3004 dt_liquify_path_data_t *d = g->dragging.elem;
3005 dt_liquify_path_data_t *n = node_next(&g->params, d);
3006 dt_liquify_path_data_t *p = node_prev(&g->params, d);
3007
3008 const float complex *start_pt = &d->warp.point;
3009
3010 switch (g->dragging.layer)
3011 {
3013 {
3014 if(IS_NULL_PTR(p)) break;
3015 const float complex delta = prev_mouse_pos == -1 ? 0.0f : pt - prev_mouse_pos;
3016 if(cabsf(delta) < 1e-6f) break;
3017
3018 dt_liquify_path_data_t *moved_nodes[2] = { p, d };
3019 for(int i = 0; i < 2; i++)
3020 {
3021 dt_liquify_path_data_t *node = moved_nodes[i];
3022 dt_liquify_path_data_t *node_next_ptr = node_next(&g->params, node);
3023 dt_liquify_path_data_t *node_prev_ptr = node_prev(&g->params, node);
3024
3026 node->node.ctrl2 += delta;
3027 if(!IS_NULL_PTR(node_next_ptr) && node_next_ptr->header.type == DT_LIQUIFY_PATH_CURVE_TO_V1)
3028 node_next_ptr->node.ctrl1 += delta;
3029 if(!IS_NULL_PTR(node_prev_ptr) && node_prev_ptr->header.type == DT_LIQUIFY_PATH_CURVE_TO_V1)
3030 node_prev_ptr->node.ctrl2 += delta;
3031
3032 node->warp.radius += delta;
3033 node->warp.strength += delta;
3034 node->warp.point += delta;
3035 }
3036 break;
3037 }
3038
3040 switch (d->header.type)
3041 {
3043 d->node.ctrl2 += pt - d->warp.point;
3044 // fall thru
3047 if(!IS_NULL_PTR(n) && n->header.type == DT_LIQUIFY_PATH_CURVE_TO_V1)
3048 n->node.ctrl1 += pt - d->warp.point;
3049 if(!IS_NULL_PTR(p) && p->header.type == DT_LIQUIFY_PATH_CURVE_TO_V1)
3050 p->node.ctrl2 += pt - d->warp.point;
3051 d->warp.radius += pt - d->warp.point;
3052 d->warp.strength += pt - d->warp.point;
3053 d->warp.point = pt;
3054 break;
3055 default:
3056 break;
3057 }
3058 break;
3059
3061 switch (d->header.type)
3062 {
3064 d->node.ctrl1 = pt;
3065 if(!IS_NULL_PTR(p) && p->header.type == DT_LIQUIFY_PATH_CURVE_TO_V1)
3066 {
3067 switch (p->header.node_type)
3068 {
3070 p->node.ctrl2 = p->warp.point +
3071 cabsf(p->warp.point - p->node.ctrl2) *
3072 cexpf(cargf(p->warp.point - pt) * I);
3073 break;
3075 p->node.ctrl2 = 2 * p->warp.point - pt;
3076 break;
3077 default:
3078 break;
3079 }
3080 }
3081 break;
3082 default:
3083 break;
3084 }
3085 break;
3086
3088 switch (d->header.type)
3089 {
3091 d->node.ctrl2 = pt;
3092 if(!IS_NULL_PTR(n) && n->header.type == DT_LIQUIFY_PATH_CURVE_TO_V1)
3093 {
3094 switch (d->header.node_type)
3095 {
3097 n->node.ctrl1 = d->warp.point +
3098 cabsf(d->warp.point - n->node.ctrl1) *
3099 cexpf(cargf(d->warp.point - pt) * I);
3100 break;
3102 n->node.ctrl1 = 2 * d->warp.point - pt;
3103 break;
3104 default:
3105 break;
3106 }
3107 }
3108 break;
3109 default:
3110 break;
3111 }
3112 break;
3113
3115 d->warp.radius = pt;
3116 dt_conf_set_float(CONF_RADIUS, cabsf(d->warp.radius - d->warp.point));
3117 break;
3118
3120 d->warp.strength = pt;
3121 dt_conf_set_float(CONF_STRENGTH, cabsf(d->warp.strength - d->warp.point));
3122 dt_conf_set_float(CONF_ANGLE, cargf(d->warp.strength - d->warp.point));
3123 break;
3124
3126 d->warp.control1 = MIN(1.0, cabsf(pt - *start_pt) / cabsf(d->warp.radius - *start_pt));
3127 break;
3128
3130 d->warp.control2 = MIN(1.0, cabsf(pt - *start_pt) / cabsf(d->warp.radius - *start_pt));
3131 break;
3132
3133 default:
3134 break;
3135 }
3136 handled = TRUE;
3137 }
3138
3139done:
3140 if(!handled && which != 0 && (!IS_NULL_PTR(g->temp) || !IS_NULL_PTR(g->last_hit.elem)))
3141 {
3142 // A drag/edit sequence is in progress, keep consuming motion events
3143 // so darkroom pan does not steal the interaction.
3144 handled = TRUE;
3145 }
3147 if(handled)
3148 {
3149 sync_pipe(module, FALSE);
3150 }
3151 return handled;
3152}
3153
3154static float dt_conf_get_sanitize_float(const char *name, float min, float max, float default_value)
3155{
3156 const float value = dt_conf_get_float(name);
3157 float new_value = CLAMP(value, min, max);
3158
3159 if (default_value != 0.0f && new_value != value) new_value = 0.25f * default_value + 0.75f * value;
3160
3161 dt_conf_set_float(name, new_value);
3162 return new_value;
3163}
3164
3165static void get_stamp_params(dt_iop_module_t *module, float *radius, float *r_strength, float *phi)
3166{
3168 GtkAllocation allocation;
3169 gtk_widget_get_allocation(widget, &allocation);
3170 const int last_win_min = MIN(allocation.width, allocation.height);
3171
3172 const dt_develop_t *dev = module->dev;
3173 const float iwd_min = MIN(dev->roi.raw_width, dev->roi.raw_height);
3174 const float proc_wdht_min = MIN(dev->roi.processed_width, dev->roi.processed_height);
3175 const float scale = 1.f / (get_zoom_scale(dev));
3176 const float im_scale = 0.09f * iwd_min * last_win_min * scale / proc_wdht_min;
3177
3178 *radius = dt_conf_get_sanitize_float(CONF_RADIUS, 0.1f*im_scale, 3.0f*im_scale, im_scale);
3179 *r_strength = dt_conf_get_sanitize_float(CONF_STRENGTH, 0.5f * *radius, 2.0f * *radius, 1.5f * *radius);
3181}
3182/*
3183 add support for changing the radius and the strength vector for the temp node
3184 */
3185int scrolled(struct dt_iop_module_t *module, double x, double y, int up, uint32_t state)
3186{
3188
3189 // add an option to allow skip mouse events while editing masks
3190 const gboolean incr = dt_mask_scroll_increases(up);
3191
3192 if(g->temp)
3193 {
3194 dt_liquify_warp_t *warp = &g->temp->warp;
3195 const float complex strength_v = warp->strength - warp->point;
3196 if(dt_modifier_is(state, 0))
3197 {
3198 // change size
3199 float radius = 0.0f, r = 0.0f, phi = 0.0f;
3200 get_stamp_params(module, &radius, &r, &phi);
3201
3202 float factor = 1.0f;
3203 if(incr)
3204 factor *= 1.0f / 0.97f;
3205 else if(!incr && cabsf(warp->radius - warp->point) > 10.0f)
3206 factor *= 0.97f;
3207
3208 r *= factor;
3209 radius *= factor;
3210
3211 warp->radius = warp->point + (radius * factor);
3212 warp->strength = warp->point + r * cexpf(phi * I);
3213
3216 return 1;
3217 }
3218 else if(dt_modifier_is(state, GDK_CONTROL_MASK))
3219 {
3220 // change the strength direction
3221 float phi = cargf(strength_v);
3222 const float r = cabsf(strength_v);
3223
3224 if(incr)
3225 phi += DT_M_PI_F / 16.0f;
3226 else
3227 phi -= DT_M_PI_F / 16.0f;
3228
3229 warp->strength = warp->point + r * cexpf(phi * I);
3232 return 1;
3233 }
3234 else if(dt_modifier_is(state, GDK_SHIFT_MASK))
3235 {
3236 // change the strength
3237 const float phi = cargf(strength_v);
3238 float r = cabsf(strength_v);
3239
3240 if(incr)
3241 r *= 1.0f / 0.97f;
3242 else
3243 r *= 0.97f;
3244
3245 warp->strength = warp->point + r * cexpf(phi * I);
3248 return 1;
3249 }
3250 }
3251
3252 return 0;
3253}
3254
3256 double x,
3257 double y,
3258 double pressure,
3259 int which,
3260 int type,
3261 uint32_t state)
3262{
3264 int handled = 0;
3265 float complex pt = 0.0f;
3266 float scale = 0.0f;
3267
3268 get_point_scale(module, x, y, &pt, &scale);
3269
3271
3272 g->last_mouse_pos = pt;
3273 g->last_mouse_mods = state;
3274 if(which == 1)
3275 g->last_button1_pressed_pos = pt;
3276
3277 if(!is_dragging(g))
3278 // while dragging you would always hit the dragged thing
3279 g->last_hit = _hit_test_paths(module, &g->params, pt);
3280
3281 if(which == 2) goto done;
3282
3283 // Point tool
3284
3285 if(which == 1 && gtk_toggle_button_get_active(g->btn_point_tool))
3286 {
3287 // always end dragging before manipulating the path list to avoid
3288 // dangling pointers
3289 end_drag(g);
3290
3291 if(IS_NULL_PTR(g->temp)) goto done;
3292 g->status |= DT_LIQUIFY_STATUS_NEW;
3293 g->status &= ~DT_LIQUIFY_STATUS_PREVIEW;
3294
3296 g->last_hit = NOWHERE;
3297 handled = 1;
3298 goto done;
3299 }
3300
3301 // Line tool or curve tool
3302
3303 if(which == 1 && (gtk_toggle_button_get_active(g->btn_line_tool)
3304 || gtk_toggle_button_get_active(g->btn_curve_tool)))
3305 {
3306 // always end dragging before manipulating the path list to avoid
3307 // dangling pointers
3308 end_drag(g);
3309 if(IS_NULL_PTR(g->temp))
3310 {
3311 if(g->last_hit.layer == DT_LIQUIFY_LAYER_CENTERPOINT)
3312 {
3313 // continue path
3314 g->temp = g->last_hit.elem;
3315 }
3316 else
3317 {
3318 goto done;
3319 }
3320 }
3321
3322 g->last_hit = NOWHERE;
3323 if(gtk_toggle_button_get_active(g->btn_curve_tool))
3324 {
3326 }
3327 g->status |= DT_LIQUIFY_STATUS_NEW;
3328 g->status &= ~DT_LIQUIFY_STATUS_PREVIEW;
3329 handled = 1;
3330 goto done;
3331 }
3332
3333 // Node tool
3334
3335 if(gtk_toggle_button_get_active(g->btn_node_tool))
3336 {
3337 if(which == 1 && dt_modifier_is(g->last_mouse_mods, GDK_CONTROL_MASK) &&
3338 (g->last_hit.layer == DT_LIQUIFY_LAYER_CENTERPOINT))
3339 {
3340 // cycle node type: smooth -> cusp etc.
3341 dt_liquify_path_data_t *node = g->last_hit.elem;
3343 handled = 1;
3344 goto done;
3345 }
3346 if(which == 1 && dt_modifier_is(g->last_mouse_mods, GDK_CONTROL_MASK) &&
3347 (g->last_hit.layer == DT_LIQUIFY_LAYER_STRENGTHPOINT))
3348 {
3349 // cycle warp type: linear -> radial etc.
3350 if(g->last_hit.elem->header.type == DT_LIQUIFY_PATH_MOVE_TO_V1)
3351 {
3352 dt_liquify_warp_t *warp = &g->last_hit.elem->warp;
3353 warp->type = (warp->type + 1) % DT_LIQUIFY_WARP_TYPE_LAST;
3354 }
3355 handled = 1;
3356 goto done;
3357 }
3358 }
3359
3360 if(!handled && (which == 1 || which == 3) && (!IS_NULL_PTR(g->temp) || !IS_NULL_PTR(g->last_hit.elem)))
3361 {
3362 // Even when the actual edit is finalized on button release, this press
3363 // starts an interaction sequence and must remain captured by liquify.
3364 handled = 1;
3365 }
3366
3367done:
3369 return handled;
3370}
3371
3373{
3375
3376 // create initial shape at the center
3377 float complex pt = 0.0f;
3378 float scale = 1.0f;
3379 get_point_scale(module, 0.5f * module->dev->roi.width, 0.5f * module->dev->roi.height, &pt, &scale);
3380 float radius = 0.0f, r = 1.0f, phi = 0.0f;
3381 get_stamp_params(module, &radius, &r, &phi);
3382 // start a new path
3383 g->temp = alloc_move_to(module, pt);
3384 g->temp->warp.radius = pt + radius;
3385 g->temp->warp.strength = pt + r * cexpf(phi * I);
3386 g->status |= DT_LIQUIFY_STATUS_PREVIEW;
3387 g->status |= DT_LIQUIFY_STATUS_NEW;
3388
3389 g->just_started = TRUE;
3390
3391
3393 g->last_hit = NOWHERE;
3394}
3395
3397 double x,
3398 double y,
3399 int which,
3400 uint32_t state)
3401{
3403 int handled = 0;
3404 float complex pt = 0.0f;
3405 float scale = 0.0f;
3406
3407 get_point_scale(module, x, y, &pt, &scale);
3408
3410
3411 g->last_mouse_pos = pt;
3412
3413 const gboolean dragged = detect_drag(g, scale, pt);
3414
3415 if(which == 1 && g->temp && (g->status & DT_LIQUIFY_STATUS_NEW))
3416 {
3417 end_drag(g);
3418 if(gtk_toggle_button_get_active(g->btn_point_tool))
3419 {
3420 g->temp = NULL; // a point is done
3421 btn_make_radio_callback(g->btn_node_tool, NULL, module);
3422 handled = 2;
3423 }
3424 else if(gtk_toggle_button_get_active(g->btn_line_tool))
3425 {
3426 const int prev_index = g->node_index;
3427 const float complex strength = (g->temp->warp.strength - g->temp->warp.point);
3428 const float radius = cabsf(g->temp->warp.radius - g->temp->warp.point);
3429 g->temp = alloc_line_to(module, pt);
3430 if(IS_NULL_PTR(g->temp)) goto done;
3431 g->temp->warp.radius = pt + radius;
3432 g->temp->warp.strength = pt + strength;
3433 // links
3434 g->temp->header.prev = prev_index;
3435 node_get(&g->params, prev_index)->header.next = g->node_index;
3437 g->just_started = FALSE;
3438 handled = 1;
3439 }
3440 else if(gtk_toggle_button_get_active(g->btn_curve_tool))
3441 {
3442 const int prev_index = g->node_index;
3443 const float complex strength = (g->temp->warp.strength - g->temp->warp.point);
3444 const float radius = cabsf(g->temp->warp.radius - g->temp->warp.point);
3445 g->temp = alloc_curve_to(module, pt);
3446 if(IS_NULL_PTR(g->temp)) goto done;
3447 g->temp->warp.radius = pt + radius;
3448 g->temp->warp.strength = pt + strength;
3449 // links
3450 g->temp->header.prev = prev_index;
3451 node_get(&g->params, prev_index)->header.next = g->node_index;
3453 g->just_started = FALSE;
3454 handled = 1;
3455 }
3456 g->status &= ~DT_LIQUIFY_STATUS_NEW;
3457 goto done;
3458 }
3459
3460 if(which == 1 && is_dragging(g))
3461 {
3462 end_drag(g);
3463 handled = 2;
3464 goto done;
3465 }
3466
3467 // right click == cancel or delete
3468 if(which == 3)
3469 {
3471 end_drag(g);
3472
3473 // cancel line or curve creation
3474 if(g->temp)
3475 {
3476 node_delete(&g->params, g->temp);
3477 g->temp = NULL;
3478 g->status &= ~DT_LIQUIFY_STATUS_PREVIEW;
3479 btn_make_radio_callback(g->btn_node_tool, NULL, module);
3480 handled = 2;
3481 goto done;
3482 }
3483
3484 // right click on background toggles node tool
3485 if(g->last_hit.layer == DT_LIQUIFY_LAYER_BACKGROUND)
3486 {
3487 btn_make_radio_callback(g->btn_node_tool, NULL, module);
3488 handled = 1;
3489 goto done;
3490 }
3491
3492 // delete node
3493 if(g->last_hit.layer == DT_LIQUIFY_LAYER_CENTERPOINT)
3494 {
3495 node_delete(&g->params, g->last_hit.elem);
3496 g->last_hit = NOWHERE;
3497 handled = 2;
3498 goto done;
3499 }
3500 // delete shape
3501 if(g->last_hit.layer == DT_LIQUIFY_LAYER_PATH)
3502 {
3503 path_delete(&g->params, g->last_hit.elem);
3504 g->last_hit = NOWHERE;
3505 handled = 2;
3506 goto done;
3507 }
3508 goto done;
3509 }
3510
3511 // Node tool
3512
3513 if(gtk_toggle_button_get_active(g->btn_node_tool))
3514 {
3515 if(which == 1 && dt_modifier_is(g->last_mouse_mods, 0) && !dragged)
3516 {
3517 // select/unselect start/endpoint and clear previous selections
3518 if(g->last_hit.layer == DT_LIQUIFY_LAYER_CENTERPOINT)
3519 {
3520 const int oldsel = !!g->last_hit.elem->header.selected;
3521 unselect_all(&g->params);
3522 g->last_hit.elem->header.selected = oldsel ? 0 : g->last_hit.layer;
3523 handled = 1;
3524 goto done;
3525 }
3526 // unselect all
3527 if(g->last_hit.layer == DT_LIQUIFY_LAYER_BACKGROUND)
3528 {
3529 unselect_all(&g->params);
3530 handled = 1;
3531 goto done;
3532 }
3533 }
3534 if(which == 1 && dt_modifier_is(g->last_mouse_mods, GDK_SHIFT_MASK) && !dragged)
3535 {
3536 // select/unselect start/endpoint and keep previous selections
3537 if(g->last_hit.layer == DT_LIQUIFY_LAYER_CENTERPOINT)
3538 {
3539 const int oldsel = !!g->last_hit.elem->header.selected;
3540 g->last_hit.elem->header.selected = oldsel ? 0 : g->last_hit.layer;
3541 handled = 1;
3542 goto done;
3543 }
3544 }
3545 if(which == 1 && dt_modifier_is(g->last_mouse_mods, GDK_CONTROL_MASK) && !dragged)
3546 {
3547 // add node
3548 if(g->last_hit.layer == DT_LIQUIFY_LAYER_PATH)
3549 {
3550 dt_liquify_path_data_t *e = g->last_hit.elem;
3551 dt_liquify_path_data_t *prev = node_prev(&g->params, e);
3553 {
3554 // add node to curve
3556
3558 if(IS_NULL_PTR(curve2)) goto done;
3559
3560 curve2->node.ctrl1 = curve1->node.ctrl1;
3561 curve2->node.ctrl2 = curve1->node.ctrl2;
3562
3563 dt_liquify_warp_t *warp1 = &prev->warp;
3564 dt_liquify_warp_t *warp2 = &curve2->warp;
3565 dt_liquify_warp_t *warp3 = &e->warp;
3566
3567 const float t = find_nearest_on_curve_t(warp1->point, curve1->node.ctrl1, curve1->node.ctrl2,
3568 warp3->point, pt, INTERPOLATION_POINTS);
3569
3570 float complex midpoint = warp3->point;
3571 casteljau(&warp1->point, &curve1->node.ctrl1, &curve1->node.ctrl2, &midpoint, t);
3572 midpoint = warp1->point;
3573 casteljau(&warp3->point, &curve2->node.ctrl2, &curve2->node.ctrl1, &midpoint, 1.0 - t);
3574
3575 mix_warps(warp2, warp1, warp3, midpoint, t);
3576
3577 node_insert_before(&g->params, e, (dt_liquify_path_data_t *)curve2);
3578
3579 handled = 2;
3580 goto done;
3581 }
3583 {
3584 // add node to line
3585 dt_liquify_warp_t *warp1 = &prev->warp;
3586 dt_liquify_warp_t *warp3 = &e->warp;
3587 const float t = find_nearest_on_line_t(warp1->point, warp3->point, pt);
3588
3590 if(IS_NULL_PTR(tmp)) goto done;
3591
3592 dt_liquify_warp_t *warp2 = &tmp->warp;
3593 const float complex midpoint = cmix(warp1->point, warp3->point, t);
3594
3595 mix_warps(warp2, warp1, warp3, midpoint, t);
3596 node_insert_before(&g->params, e, tmp);
3597
3598 handled = 2;
3599 goto done;
3600 }
3601 }
3602 }
3603 if(which == 1
3604 && dt_modifier_is(g->last_mouse_mods, GDK_MOD1_MASK | GDK_CONTROL_MASK)
3605 && !dragged)
3606 {
3607 if(g->last_hit.layer == DT_LIQUIFY_LAYER_PATH)
3608 {
3609 // change segment
3610 dt_liquify_path_data_t *e = g->last_hit.elem;
3611 dt_liquify_path_data_t *prev = node_prev(&g->params, e);
3613 {
3614 // curve -> line
3617 e->header.selected = e->header.hovered = 0;
3618 handled = 2;
3619 goto done;
3620 }
3622 {
3623 // line -> curve
3624 const float complex p0 = prev->warp.point;
3625 const float complex p1 = e->warp.point;
3629 c->node.ctrl1 = (2 * p0 + p1) / 3.0;
3630 c->node.ctrl2 = ( p0 + 2 * p1) / 3.0;
3631
3632 handled = 2;
3633 goto done;
3634 }
3635 }
3636 }
3637 }
3638
3639done:
3640 if(which == 1)
3641 g->last_button1_pressed_pos = -1;
3642 g->last_hit = NOWHERE;
3644 if(handled)
3645 {
3647 sync_pipe(module, handled == 2);
3648 }
3649 return handled;
3650}
3651
3652int key_pressed(struct dt_iop_module_t *self, GdkEventKey *event)
3653{
3654 if(IS_NULL_PTR(event)) return 0;
3655
3657 if(IS_NULL_PTR(g)) return 0;
3658 guint key = dt_keys_mainpad_alternatives(event->keyval);
3659
3660
3661 const gboolean creating = gtk_toggle_button_get_active(g->btn_point_tool)
3662 || gtk_toggle_button_get_active(g->btn_line_tool)
3663 || gtk_toggle_button_get_active(g->btn_curve_tool)
3665
3666 // Delete last created node while creating a shape.
3667 if(key == GDK_KEY_BackSpace)
3668 {
3669 if(!creating)
3670 return 0;
3671
3672 const gboolean create_tool_active = gtk_toggle_button_get_active(g->btn_point_tool)
3673 || gtk_toggle_button_get_active(g->btn_line_tool)
3674 || gtk_toggle_button_get_active(g->btn_curve_tool);
3675 gboolean restart_shape = FALSE;
3676
3678
3679 dt_liquify_path_data_t *last = NULL;
3680 for(int k = 0; k < MAX_NODES; k++)
3681 {
3682 if(g->params.nodes[k].header.type == DT_LIQUIFY_PATH_INVALIDATED)
3683 break;
3684 last = &g->params.nodes[k];
3685 }
3686
3687 if(IS_NULL_PTR(last) && IS_NULL_PTR(g->temp))
3688 {
3689 restart_shape = create_tool_active;
3691 if(restart_shape)
3692 {
3693 _start_new_shape(self);
3695 sync_pipe(self, TRUE);
3696 }
3697 return 1;
3698 }
3699
3700 dt_liquify_path_data_t *to_delete = !IS_NULL_PTR(g->temp) ? g->temp : last;
3701
3702 end_drag(g);
3703 const int prev_index = to_delete->header.prev;
3704 node_delete(&g->params, to_delete);
3705 g->temp = prev_index >= 0 ? node_get(&g->params, prev_index) : NULL;
3706 g->node_index = !IS_NULL_PTR(g->temp) ? g->temp->header.idx : 0;
3707 g->last_hit = NOWHERE;
3708
3709 if(!IS_NULL_PTR(g->temp))
3710 {
3711 // Continue creation from the current endpoint by restoring the live drag preview.
3713 g->status &= ~DT_LIQUIFY_STATUS_NEW;
3714 }
3715 else
3716 {
3717 restart_shape = create_tool_active;
3719 }
3720
3722
3723 if(restart_shape)
3724 _start_new_shape(self);
3725
3727 sync_pipe(self, TRUE);
3728 return 1;
3729 }
3730
3731 // Delete selected node outside creation mode.
3732 if(key == GDK_KEY_Delete)
3733 {
3734 if(creating)
3735 return 0;
3736
3738
3739 dt_liquify_path_data_t *selected = NULL;
3740 for(int k = 0; k < MAX_NODES; k++)
3741 {
3742 if(g->params.nodes[k].header.type == DT_LIQUIFY_PATH_INVALIDATED)
3743 break;
3744 if(g->params.nodes[k].header.selected == DT_LIQUIFY_LAYER_CENTERPOINT)
3745 {
3746 selected = &g->params.nodes[k];
3747 break;
3748 }
3749 }
3750
3751 if(IS_NULL_PTR(selected))
3752 {
3754 return 0;
3755 }
3756
3757 end_drag(g);
3758 const int deleted_idx = selected->header.idx;
3759 const int next_idx = selected->header.next;
3760 const int prev_idx = selected->header.prev;
3761 node_delete(&g->params, selected);
3762 g->temp = NULL;
3763 g->last_hit = NOWHERE;
3764
3765 unselect_all(&g->params);
3766 int target_idx = (next_idx != -1) ? next_idx : prev_idx;
3767 if(target_idx > deleted_idx)
3768 target_idx--;
3769
3770 if(target_idx >= 0)
3771 {
3772 dt_liquify_path_data_t *target = node_get(&g->params, target_idx);
3773 if(!IS_NULL_PTR(target) && target->header.type != DT_LIQUIFY_PATH_INVALIDATED)
3775 }
3776
3778
3780 sync_pipe(self, TRUE);
3781 return 1;
3782 }
3783
3784 // Quit current creation or edition on Escape or Enter key
3785 if(key == GDK_KEY_Escape || key == GDK_KEY_Return)
3786 {
3787
3788 if(!creating)
3789 return 0;
3790
3792
3794 end_drag(g);
3795
3796 if(g->temp)
3797 {
3798 node_delete(&g->params, g->temp);
3799 g->temp = NULL;
3800 }
3801
3803 g->last_hit = NOWHERE;
3804
3806
3807 gtk_toggle_button_set_active(g->btn_point_tool, FALSE);
3808 gtk_toggle_button_set_active(g->btn_line_tool, FALSE);
3809 gtk_toggle_button_set_active(g->btn_curve_tool, FALSE);
3810 gtk_toggle_button_set_active(g->btn_node_tool, TRUE);
3811
3812 dt_control_hinter_message(darktable.control, _("click to edit nodes"));
3815 sync_pipe(self, TRUE);
3816
3817 return 1;
3818 }
3819
3820 return 0;
3821}
3822
3823// we need this only because darktable has no radiobutton support
3824
3825static gboolean btn_make_radio_callback(GtkToggleButton *btn, GdkEventButton *event, dt_iop_module_t *module)
3826{
3828
3829 // if currently dragging and a form (line or node) has been started, does nothing (expect resetting the toggle button status).
3830 if(is_dragging(g) && g->temp && node_prev(&g->params, g->temp))
3831 {
3832 return TRUE;
3833 }
3834
3836
3837 // if we are on a preview, it means that a form (point, line, curve) has been started, but no node has yet been placed.
3838 // in this case we abort the current preview and let the new tool activated.
3839 if(g->status & DT_LIQUIFY_STATUS_PREVIEW)
3840 {
3841 node_delete(&g->params, g->temp);
3842 g->temp = NULL;
3843 g->status &= ~DT_LIQUIFY_STATUS_PREVIEW;
3844 }
3845
3846 // now, let's enable and start a new form safely
3847 if(IS_NULL_PTR(btn) || !gtk_toggle_button_get_active(btn))
3848 {
3849 gtk_toggle_button_set_active(g->btn_point_tool, btn == g->btn_point_tool);
3850 gtk_toggle_button_set_active(g->btn_line_tool, btn == g->btn_line_tool);
3851 gtk_toggle_button_set_active(g->btn_curve_tool, btn == g->btn_curve_tool);
3852 gtk_toggle_button_set_active(g->btn_node_tool, btn == g->btn_node_tool);
3853
3854 if(btn == g->btn_point_tool)
3856 (darktable.control, _("click and drag to add point\nscroll to change size - "
3857 "shift+scroll to change strength - ctrl+scroll to change direction"));
3858 else if(btn == g->btn_line_tool)
3860 (darktable.control, _("click to add line\nscroll to change size - "
3861 "shift+scroll to change strength - ctrl+scroll to change direction"));
3862 else if(btn == g->btn_curve_tool)
3864 (darktable.control, _("click to add curve\nscroll to change size - "
3865 "shift+scroll to change strength - ctrl+scroll to change direction"));
3866 else if(btn == g->btn_node_tool)
3867 dt_control_hinter_message(darktable.control, _("click to edit nodes"));
3868
3869 // start the preview mode to show the shape that will be created
3870
3871 if(btn == g->btn_point_tool || btn == g->btn_line_tool || btn == g->btn_curve_tool)
3872 {
3873 _start_new_shape(module);
3874 }
3875
3876 if(!IS_NULL_PTR(btn)) dt_iop_request_focus(module);
3877 }
3878 else
3879 {
3880 gtk_toggle_button_set_active(btn, FALSE);
3881 }
3882
3883 sync_pipe(module, FALSE);
3884
3885 return TRUE;
3886}
3887
3889{
3891 memcpy(&g->params, module->params, sizeof(dt_iop_liquify_params_t));
3893}
3894
3896{
3898
3899 // A dummy surface for calculations only, no drawing.
3900 cairo_surface_t *cs = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1);
3901 cairo_surface_destroy(cs);
3902
3903 g->dragging = NOWHERE;
3904 g->temp = NULL;
3905 g->status = 0;
3906 g->last_mouse_pos =
3907 g->last_button1_pressed_pos = -1;
3908 g->last_hit = NOWHERE;
3909 g->node_index = 0;
3910
3911 self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
3912
3913 GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
3914 gtk_widget_set_tooltip_text(hbox, _("use a tool to add warps.\nright-click to remove a warp."));
3915 gtk_box_pack_start(GTK_BOX(self->widget), hbox, TRUE, TRUE, 0);
3916
3917
3918 GtkWidget *lbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
3919 gtk_box_pack_start(GTK_BOX(hbox), lbox, FALSE, TRUE, 0);
3920
3921 GtkWidget *labelbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
3922 GtkWidget *label = dt_ui_label_new(_("Warps: "));
3923 gtk_box_pack_start(GTK_BOX(labelbox), label, FALSE, TRUE, 0);
3924 g->label_warp = GTK_LABEL(dt_ui_label_new("-"));
3925 gtk_box_pack_start(GTK_BOX(labelbox), GTK_WIDGET(g->label_warp), FALSE, TRUE, 0);
3926 gtk_box_pack_start(GTK_BOX(lbox), labelbox, FALSE, TRUE, 0);
3927
3928 GtkWidget *labelbox2 = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
3929 GtkWidget *label2 = dt_ui_label_new(_("Nodes: "));
3930 gtk_box_pack_start(GTK_BOX(labelbox2), label2, FALSE, TRUE, 0);
3931 g->label_node = GTK_LABEL(dt_ui_label_new("-"));
3932 gtk_box_pack_start(GTK_BOX(labelbox2), GTK_WIDGET(g->label_node), FALSE, TRUE, 0);
3933 gtk_box_pack_start(GTK_BOX(lbox), labelbox2, FALSE, TRUE, 0);
3934
3935 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
3936 gtk_box_pack_start(GTK_BOX(self->widget), hbox, TRUE, TRUE, 0);
3937
3938 g->btn_node_tool = GTK_TOGGLE_BUTTON(dt_iop_togglebutton_new(self, NULL, N_("edit, add and delete nodes"), NULL,
3939 G_CALLBACK(btn_make_radio_callback), TRUE, 0, 0,
3941
3942 g->btn_curve_tool = GTK_TOGGLE_BUTTON(dt_iop_togglebutton_new(self, N_("shapes"), N_("draw curves"), N_("draw multiple curves"),
3943 G_CALLBACK(btn_make_radio_callback), TRUE, 0, 0,
3945
3946 g->btn_line_tool = GTK_TOGGLE_BUTTON(dt_iop_togglebutton_new(self, N_("shapes"), N_("draw lines"), N_("draw multiple lines"),
3947 G_CALLBACK(btn_make_radio_callback), TRUE, 0, 0,
3949
3950 g->btn_point_tool = GTK_TOGGLE_BUTTON(dt_iop_togglebutton_new(self, N_("shapes"), N_("draw points"), N_("draw multiple points"),
3951 G_CALLBACK(btn_make_radio_callback), TRUE, 0, 0,
3953
3954 dt_liquify_layers[DT_LIQUIFY_LAYER_PATH].hint = _("drag: move segment - ctrl+click: add node - right click: remove path\n"
3955 "ctrl+alt+click: toggle line/curve");
3956 dt_liquify_layers[DT_LIQUIFY_LAYER_CENTERPOINT].hint = _("click and drag to move - click: show/hide feathering controls\n"
3957 "ctrl+click: autosmooth, cusp, smooth, symmetrical"
3958 " - right click to remove");
3959 dt_liquify_layers[DT_LIQUIFY_LAYER_CTRLPOINT1].hint = _("drag to change shape of path");
3960 dt_liquify_layers[DT_LIQUIFY_LAYER_CTRLPOINT2].hint = _("drag to change shape of path");
3961 dt_liquify_layers[DT_LIQUIFY_LAYER_RADIUSPOINT].hint = _("drag to adjust warp radius");
3962 dt_liquify_layers[DT_LIQUIFY_LAYER_HARDNESSPOINT1].hint = _("drag to adjust hardness (center)");
3963 dt_liquify_layers[DT_LIQUIFY_LAYER_HARDNESSPOINT2].hint = _("drag to adjust hardness (feather)");
3964 dt_liquify_layers[DT_LIQUIFY_LAYER_STRENGTHPOINT].hint = _("drag to adjust warp strength\n"
3965 "ctrl+click: linear, grow, and shrink");
3966}
3967
3969{
3971 g->dragging = NOWHERE;
3972 g->temp = NULL;
3973 g->status = 0;
3974 btn_make_radio_callback(NULL, NULL, self);
3975}
3976
3978{
3980}
3981
3982
3983// clang-format off
3984// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
3985// vim: shiftwidth=2 expandtab tabstop=2 cindent
3986// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
3987// clang-format on
static double dist(double x1, double y1, double x2, double y2)
Definition ashift_lsd.c:250
static void error(char *msg)
Definition ashift_lsd.c:202
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
#define m
Definition basecurve.c:278
int width
Definition bilateral.h:1
int height
Definition bilateral.h:1
static const dt_aligned_pixel_simd_t const dt_adaptation_t const float p
void dt_collection_hint_message(const dt_collection_t *collection)
static float lookup(read_only image2d_t lut, const float x)
@ IOP_CS_RGB
#define B(y, x)
#define A(y, x)
static const float const float const float min
const float max
const dt_colormatrix_t dt_aligned_pixel_t out
static const float const float C
static const int row
const float delta
static float strength(float value, float strength)
Definition colorzones.c:420
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
char * key
int type
void dt_conf_set_float(const char *name, float val)
float dt_conf_get_float(const char *name)
void dt_control_queue_redraw_center()
request redraw of center window. This redraws the center view within a gdk critical section to preven...
Definition control.c:861
void dt_control_hinter_message(const struct dt_control_t *s, const char *message)
Definition control.c:918
void dt_control_queue_cursor_by_name(const char *curs_str)
Queue a GTK named cursor for the next cursor commit.
Definition control.c:398
darktable_t darktable
Definition darktable.c:181
void dt_print(dt_debug_thread_t thread, const char *msg,...)
Definition darktable.c:1542
#define dt_free_align(ptr)
Definition darktable.h:481
static void * dt_calloc_align(size_t size)
Definition darktable.h:488
@ DT_DEBUG_OPENCL
Definition darktable.h:722
#define dt_pixelpipe_cache_alloc_align_float_cache(pixels, id)
Definition darktable.h:447
#define dt_pixelpipe_cache_alloc_align_cache(size, id)
Definition darktable.h:433
static void dt_free_gpointer(gpointer ptr)
Definition darktable.h:463
#define dt_free(ptr)
Definition darktable.h:456
#define DT_MODULE_INTROSPECTION(MODVER, PARAMSTYPE)
Definition darktable.h:151
#define dt_pixelpipe_cache_free_align(mem)
Definition darktable.h:453
#define __DT_CLONE_TARGETS__
Definition darktable.h:367
#define __OMP_PARALLEL_FOR__(...)
Definition darktable.h:258
static const dt_aligned_pixel_simd_t value
Definition darktable.h:577
static gboolean dt_modifier_is(const GdkModifierType state, const GdkModifierType desired_modifier_mask)
Definition darktable.h:893
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition darktable.h:281
#define M_PI_F
#define dt_dev_add_history_item(dev, module, enable, redraw)
void dt_iop_params_t
Definition dev_history.h:41
void dt_dev_pixelpipe_sync_virtual(dt_develop_t *dev, dt_dev_pixelpipe_change_t flag)
int dt_dev_distort_transform_locked(const dt_dev_pixelpipe_t *pipe, const double iop_order, const int transf_direction, float *points, size_t points_count)
Definition develop.c:1536
int dt_dev_distort_transform_plus(const dt_dev_pixelpipe_t *pipe, const double iop_order, const int transf_direction, float *points, size_t points_count)
Definition develop.c:1557
gboolean dt_dev_rescale_roi_to_input(dt_develop_t *dev, cairo_t *cr, int32_t width, int32_t height)
Scale the ROI to fit the input size within given width/height, centered.
Definition develop.c:1834
float dt_dev_get_zoom_scale(const dt_develop_t *dev, const gboolean preview)
Definition develop.c:880
void dt_dev_coordinates_image_norm_to_image_abs(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1060
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:1003
int dt_dev_distort_backtransform_plus(const dt_dev_pixelpipe_t *pipe, const double iop_order, const int transf_direction, float *points, size_t points_count)
Definition develop.c:1586
static uint64_t dt_dev_get_history_hash(const dt_develop_t *dev)
Definition develop.h:504
@ DT_DEV_TRANSFORM_DIR_BACK_EXCL
Definition develop.h:106
@ DT_DEV_TRANSFORM_DIR_ALL
Definition develop.h:102
@ DT_DEV_TRANSFORM_DIR_FORW_EXCL
Definition develop.h:104
void dtgtk_liquify_cairo_paint_curve_tool(cairo_t *cr, const gint x, const gint y, const gint w, const gint h, const gint flags, void *data)
void dtgtk_liquify_cairo_paint_line_tool(cairo_t *cr, const gint x, const gint y, const gint w, const gint h, const gint flags, void *data)
void dtgtk_liquify_cairo_paint_node_tool(cairo_t *cr, const gint x, const gint y, const gint w, const gint h, const gint flags, void *data)
void dtgtk_cairo_paint_masks_edit(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void default_input_format(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece, dt_iop_buffer_dsc_t *dsc)
Definition format.c:57
@ TYPE_FLOAT
Definition format.h:46
static guint dt_keys_mainpad_alternatives(const guint key_val)
Remap keypad keys to usual mainpad ones.
Definition gdkkeys.h:113
#define DT_GUI_MOUSE_EFFECT_RADIUS
Definition gtk.h:70
GtkWidget * dt_ui_main_window(dt_ui_t *ui)
get the main window widget
#define DT_GUI_BOX_SPACING
Definition gtk.h:109
#define DT_PIXEL_APPLY_DPI(value)
Definition gtk.h:90
static GtkWidget * dt_ui_label_new(const gchar *str)
Definition gtk.h:461
void dt_iop_request_focus(dt_iop_module_t *module)
Definition imageop.c:2169
const char ** dt_iop_set_description(dt_iop_module_t *module, const char *main_text, const char *purpose, const char *input, const char *process, const char *output)
Definition imageop.c:3141
#define IOP_GUI_FREE
Definition imageop.h:602
static void dt_iop_gui_enter_critical_section(dt_iop_module_t *const module) ACQUIRE(&module -> gui_lock)
Definition imageop.h:413
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:167
static void dt_iop_gui_leave_critical_section(dt_iop_module_t *const module) RELEASE(&module -> gui_lock)
Definition imageop.h:419
@ IOP_GROUP_EFFECTS
Definition imageop.h:142
#define IOP_GUI_ALLOC(module)
Definition imageop.h:599
@ IOP_TAG_DISTORT
Definition imageop.h:151
GtkWidget * dt_iop_togglebutton_new(dt_iop_module_t *self, const char *section, const gchar *label, const gchar *ctrl_label, GCallback callback, gboolean local, guint accel_key, GdkModifierType mods, DTGTKCairoPaintIconFunc paint, GtkWidget *box)
gboolean dt_mask_scroll_increases(int up)
const struct dt_interpolation * dt_interpolation_new(enum dt_interpolation_type type)
__DT_CLONE_TARGETS__ void dt_interpolation_compute_pixel4c(const struct dt_interpolation *itor, const float *in, float *out, const float x, const float y, const int width, const int height, const int linestride)
__DT_CLONE_TARGETS__ float dt_interpolation_compute_sample(const struct dt_interpolation *itor, const float *in, const float x, const float y, const int width, const int height, const int samplestride, const int linestride)
@ DT_INTERPOLATION_BICUBIC
@ DT_INTERPOLATION_BILINEAR
@ DT_INTERPOLATION_MITCHELL
@ DT_INTERPOLATION_USERPREF_WARP
static const float x
const int t
const float v
int operation_tags()
Definition liquify.c:342
void commit_params(struct dt_iop_module_t *module, dt_iop_params_t *params, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition liquify.c:1763
dt_liquify_path_data_enum_t
Definition liquify.c:229
@ DT_LIQUIFY_PATH_LINE_TO_V1
Definition liquify.c:232
@ DT_LIQUIFY_PATH_INVALIDATED
Definition liquify.c:230
@ DT_LIQUIFY_PATH_CURVE_TO_V1
Definition liquify.c:233
@ DT_LIQUIFY_PATH_MOVE_TO_V1
Definition liquify.c:231
static void _start_new_shape(dt_iop_module_t *module)
Definition liquify.c:3372
int process_cl(struct dt_iop_module_t *module, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const cl_mem_t dev_in, const cl_mem_t dev_out)
Definition liquify.c:1675
void init(dt_iop_module_t *module)
Definition liquify.c:1737
void distort_mask(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe, struct dt_dev_pixelpipe_iop_t *piece, const float *const in, float *const out, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
Definition liquify.c:1468
static void casteljau(const float complex *p0, float complex *p1, float complex *p2, float complex *p3, const float t)
Definition liquify.c:2306
void gui_post_expose(struct dt_iop_module_t *module, cairo_t *cr, int32_t width, int32_t height, int32_t pointerx, int32_t pointery)
Definition liquify.c:2826
const char ** description(struct dt_iop_module_t *self)
Definition liquify.c:322
static __DT_CLONE_TARGETS__ void apply_global_distortion_map(struct dt_iop_module_t *module, const dt_dev_pixelpipe_iop_t *piece, const float *const restrict in, float *const restrict out, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out, const int ch, const float complex *const map, const cairo_rectangle_int_t *extent)
Definition liquify.c:1075
int default_group()
Definition liquify.c:332
int distort_backtransform(dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, float *const restrict points, size_t points_count)
Definition liquify.c:1462
static void get_stamp_params(dt_iop_module_t *module, float *radius, float *r_strength, float *phi)
Definition liquify.c:3165
#define VERYTHINLINE
Definition liquify.c:1934
void gui_reset(dt_iop_module_t *self)
Definition liquify.c:3968
#define CONF_ANGLE
Definition liquify.c:86
float dt_liquify_ui_widths[]
Definition liquify.c:187
cl_int cl_int_t
Definition liquify.c:1576
static dt_liquify_path_data_t * alloc_line_to(dt_iop_module_t *module, float complex end_point)
Definition liquify.c:2785
static gboolean _is_movable_layer(const dt_liquify_layer_enum_t layer)
Definition liquify.c:2924
static int _distort_xtransform(dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, float *const restrict points, const size_t points_count, const gboolean inverted)
Definition liquify.c:1362
#define COLOR_DEBUG
Definition liquify.c:135
dt_liquify_layer_t dt_liquify_layers[]
Definition liquify.c:151
static dt_liquify_hit_t _hit_paths(dt_iop_module_t *module, dt_iop_liquify_params_t *p, GList *layers, const float complex *pt)
Definition liquify.c:2331
#define CONF_STRENGTH
Definition liquify.c:87
static gboolean is_dragging(const dt_iop_liquify_gui_data_t *g)
Definition liquify.c:1449
int distort_transform(dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, float *const restrict points, size_t points_count)
Definition liquify.c:1454
static dt_liquify_path_data_t * alloc_move_to(dt_iop_module_t *module, float complex start_point)
Definition liquify.c:2772
void cleanup_pipe(struct dt_iop_module_t *module, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition liquify.c:1755
static void get_point_scale(struct dt_iop_module_t *module, float x, float y, float complex *pt, float *scale)
Definition liquify.c:2910
static void _draw_paths(dt_iop_module_t *module, cairo_t *cr, const float scale, dt_iop_liquify_params_t *p, GList *layers)
Definition liquify.c:1938
static void node_gc(dt_iop_liquify_params_t *p)
Definition liquify.c:419
static float mix(const float a, const float b, const float t)
Definition liquify.c:705
int scrolled(struct dt_iop_module_t *module, double x, double y, int up, uint32_t state)
Definition liquify.c:3185
static cl_int_t apply_global_distortion_map_cl(struct dt_iop_module_t *module, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const cl_mem_t dev_in, const cl_mem_t dev_out, const dt_iop_roi_t *roi_in, const dt_iop_roi_t *roi_out, const float complex *map, const cairo_rectangle_int_t *map_extent)
Definition liquify.c:1578
static void draw_paths(struct dt_iop_module_t *module, cairo_t *cr, const float scale, dt_iop_liquify_params_t *params)
Definition liquify.c:2466
int button_pressed(struct dt_iop_module_t *module, double x, double y, double pressure, int which, int type, uint32_t state)
Definition liquify.c:3255
static void draw_triangle(cairo_t *cr, const float complex pt, const double theta, const double size)
Definition liquify.c:1792
int mouse_moved(struct dt_iop_module_t *module, double x, double y, double pressure, int which)
Definition liquify.c:2942
dt_liquify_layer_flag_enum_t
Definition liquify.c:116
@ DT_LIQUIFY_LAYER_FLAG_CURVE_TOOL
show if line tool active
Definition liquify.c:122
@ DT_LIQUIFY_LAYER_FLAG_POINT_TOOL
show if point tool active
Definition liquify.c:120
@ DT_LIQUIFY_LAYER_FLAG_HIT_TEST
include layer in hit testing
Definition liquify.c:117
@ DT_LIQUIFY_LAYER_FLAG_ANY_TOOL
Definition liquify.c:124
@ DT_LIQUIFY_LAYER_FLAG_NODE_SELECTED
show if node is selected
Definition liquify.c:119
@ DT_LIQUIFY_LAYER_FLAG_LINE_TOOL
show if line tool active
Definition liquify.c:121
@ DT_LIQUIFY_LAYER_FLAG_PREV_SELECTED
show if previous node is selected
Definition liquify.c:118
@ DT_LIQUIFY_LAYER_FLAG_NODE_TOOL
show if node tool active
Definition liquify.c:123
__DT_CLONE_TARGETS__ int process(struct dt_iop_module_t *module, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const in, void *const out)
Definition liquify.c:1503
dt_liquify_node_type_enum_t
Definition liquify.c:209
@ DT_LIQUIFY_NODE_TYPE_LAST
Definition liquify.c:214
@ DT_LIQUIFY_NODE_TYPE_CUSP
Definition liquify.c:210
@ DT_LIQUIFY_NODE_TYPE_SYMMETRICAL
Definition liquify.c:212
@ DT_LIQUIFY_NODE_TYPE_AUTOSMOOTH
Definition liquify.c:213
@ DT_LIQUIFY_NODE_TYPE_SMOOTH
Definition liquify.c:211
void gui_update(dt_iop_module_t *module)
Refresh GUI controls from current params and configuration.
Definition liquify.c:3888
static float get_rot(const dt_liquify_warp_type_enum_t warp_type)
Definition liquify.c:364
static float complex * create_global_distortion_map(const cairo_rectangle_int_t *map_extent, const GSList *interpolated, gboolean inverted)
Definition liquify.c:1169
#define THINLINE
Definition liquify.c:1935
static void node_delete(dt_iop_liquify_params_t *p, dt_liquify_path_data_t *this)
Definition liquify.c:450
static int build_round_stamp(float complex **pstamp, cairo_rectangle_int_t *const restrict stamp_extent, const dt_liquify_warp_t *const restrict warp)
Definition liquify.c:940
static float complex normalize(const float complex v)
Definition liquify.c:696
static float cdot(const float complex p0, const float complex p1)
Definition liquify.c:1773
static void set_line_width(cairo_t *cr, double scale, dt_liquify_ui_width_enum_t w)
Definition liquify.c:1827
#define CHECK_HIT_PT(point)
Definition liquify.c:2322
const char * name()
Definition liquify.c:317
static void smooth_path_linsys(size_t n, const float complex *k, float complex *c1, float complex *c2, const int *equation)
Definition liquify.c:2568
static gboolean detect_drag(const dt_iop_liquify_gui_data_t *g, const double scale, const float complex pt)
Definition liquify.c:1833
dt_liquify_warp_type_enum_t
Definition liquify.c:201
@ DT_LIQUIFY_WARP_TYPE_RADIAL_GROW
Definition liquify.c:203
@ DT_LIQUIFY_WARP_TYPE_LINEAR
Definition liquify.c:202
@ DT_LIQUIFY_WARP_TYPE_LAST
Definition liquify.c:205
@ DT_LIQUIFY_WARP_TYPE_RADIAL_SHRINK
Definition liquify.c:204
static const dt_liquify_rgba_t DT_LIQUIFY_COLOR_SELECTED
Definition liquify.c:136
static void draw_rectangle(cairo_t *cr, const float complex pt, const double theta, const double size)
Definition liquify.c:1782
static void unselect_all(dt_iop_liquify_params_t *p)
Definition liquify.c:2812
static float mitchell(const float x)
Definition liquify.c:1559
dt_liquify_ui_width_enum_t
Definition liquify.c:175
@ DT_LIQUIFY_UI_WIDTH_GIZMO_SMALL
Definition liquify.c:180
@ DT_LIQUIFY_UI_WIDTH_DOUBLELINE
Definition liquify.c:178
@ DT_LIQUIFY_UI_WIDTH_LAST
Definition liquify.c:184
@ DT_LIQUIFY_UI_WIDTH_DEFAULT_STRENGTH
Definition liquify.c:182
@ DT_LIQUIFY_UI_WIDTH_THICKLINE
Definition liquify.c:177
@ DT_LIQUIFY_UI_WIDTH_THINLINE
Definition liquify.c:176
@ DT_LIQUIFY_UI_WIDTH_DEFAULT_RADIUS
Definition liquify.c:181
@ DT_LIQUIFY_UI_WIDTH_GIZMO
Definition liquify.c:179
@ DT_LIQUIFY_UI_WIDTH_MIN_DRAG
Definition liquify.c:183
void gui_init(dt_iop_module_t *self)
Definition liquify.c:3895
static dt_liquify_path_data_t * find_hovered(dt_iop_liquify_params_t *p)
Definition liquify.c:2751
static float dt_conf_get_sanitize_float(const char *name, float min, float max, float default_value)
Definition liquify.c:3154
static float * build_lookup_table(const int distance, const float control1, const float control2)
Definition liquify.c:871
static void distort_paths_raw_to_piece(const struct dt_iop_module_t *module, const dt_dev_pixelpipe_t *pipe, const float roi_in_scale, dt_iop_liquify_params_t *p, const gboolean from_distort_transform)
Definition liquify.c:684
static float complex point_at_arc_length(const float complex points[], const int n_points, const float arc_length, restart_cookie_t *restart)
Definition liquify.c:815
static void smooth_paths_linsys(dt_iop_liquify_params_t *params)
Definition liquify.c:2652
static void add_to_global_distortion_map(float complex *global_map, const cairo_rectangle_int_t *const restrict global_map_extent, const dt_liquify_warp_t *const restrict warp, const float complex *const restrict stamp, const cairo_rectangle_int_t *stamp_extent)
Definition liquify.c:1036
static void _distort_paths(const struct dt_iop_module_t *module, const distort_params_t *params, const dt_iop_liquify_params_t *p)
Definition liquify.c:566
static float get_zoom_scale(const dt_develop_t *develop)
Definition liquify.c:2821
static dt_liquify_hit_t _hit_test_paths(struct dt_iop_module_t *module, dt_iop_liquify_params_t *params, float complex pt)
Definition liquify.c:2494
static void end_drag(dt_iop_liquify_gui_data_t *g)
Definition liquify.c:1444
void modify_roi_in(struct dt_iop_module_t *module, const struct dt_dev_pixelpipe_t *pipe, struct dt_dev_pixelpipe_iop_t *piece, const dt_iop_roi_t *roi_out, dt_iop_roi_t *roi_in)
Definition liquify.c:1302
void gui_cleanup(dt_iop_module_t *self)
Definition liquify.c:3977
static const dt_liquify_rgba_t DT_LIQUIFY_COLOR_HOVER
Definition liquify.c:137
static dt_liquify_path_data_t * node_alloc(dt_iop_liquify_params_t *p, int *node_index)
Definition liquify.c:372
static float get_ui_width(const float scale, const dt_liquify_ui_width_enum_t w)
Definition liquify.c:1819
static void compute_round_stamp_extent(cairo_rectangle_int_t *const restrict stamp_extent, const dt_liquify_warp_t *const restrict warp)
Definition liquify.c:907
void cleanup_global(dt_iop_module_so_t *module)
Definition liquify.c:1729
#define THICKLINE
Definition liquify.c:1936
static dt_liquify_path_data_t * node_prev(dt_iop_liquify_params_t *p, const dt_liquify_path_data_t *n)
Definition liquify.c:386
static float bicubic(const float a, const float x)
Definition liquify.c:1548
dt_liquify_status_enum_t
Definition liquify.c:218
@ DT_LIQUIFY_STATUS_LAST
Definition liquify.c:223
@ DT_LIQUIFY_STATUS_NONE
Definition liquify.c:219
@ DT_LIQUIFY_STATUS_PREVIEW
Definition liquify.c:222
@ DT_LIQUIFY_STATUS_NEW
Definition liquify.c:220
@ DT_LIQUIFY_STATUS_INTERPOLATED
Definition liquify.c:221
int default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece)
Definition liquify.c:347
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 liquify.c:352
#define COLOR_NULL
Definition liquify.c:132
static void interpolate_cubic_bezier(const float complex p0, const float complex p1, const float complex p2, const float complex p3, float complex buffer[], const int n)
Definition liquify.c:759
static GList * interpolate_paths(dt_iop_liquify_params_t *p)
Definition liquify.c:1859
int flags()
Definition liquify.c:337
static dt_liquify_path_data_t * node_get(dt_iop_liquify_params_t *p, const int index)
Definition liquify.c:394
const float STAMP_RELOCATION
Definition liquify.c:83
const int LOOKUP_OVERSAMPLE
Definition liquify.c:81
const int INTERPOLATION_POINTS
Definition liquify.c:82
cl_mem cl_mem_t
Definition liquify.c:1575
static void start_drag(dt_iop_liquify_gui_data_t *g, dt_liquify_layer_enum_t layer, dt_liquify_path_data_t *elem)
Definition liquify.c:1438
#define FG_COLOR
Definition liquify.c:1932
static float find_nearest_on_line_t(const float complex p0, const float complex p1, const float complex x)
Definition liquify.c:2296
static void path_delete(dt_iop_liquify_params_t *p, dt_liquify_path_data_t *this)
Definition liquify.c:473
static float find_nearest_on_curve_t(const float complex p0, const float complex p1, const float complex p2, const float complex p3, const float complex x, const int n)
Definition liquify.c:2260
#define MAX_NODES
Definition liquify.c:79
static GSList * _get_map_extent(const dt_iop_roi_t *roi_out, const GList *interpolated, cairo_rectangle_int_t *map_extent)
Definition liquify.c:1139
int button_released(struct dt_iop_module_t *module, double x, double y, int which, uint32_t state)
Definition liquify.c:3396
static int path_length(dt_iop_liquify_params_t *p, dt_liquify_path_data_t *n)
Definition liquify.c:2641
#define ABCD(A, B, C, D)
#define LGREY
Definition liquify.c:134
static dt_liquify_path_data_t * node_next(dt_iop_liquify_params_t *p, const dt_liquify_path_data_t *n)
Definition liquify.c:402
#define GET_UI_WIDTH(a)
Definition liquify.c:1825
static const dt_liquify_hit_t NOWHERE
Definition liquify.c:282
void init_pipe(struct dt_iop_module_t *module, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition liquify.c:1749
static float complex * build_global_distortion_map(struct dt_iop_module_t *module, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const dt_iop_roi_t *roi_in, const dt_iop_roi_t *roi_out, cairo_rectangle_int_t *map_extent)
Definition liquify.c:1266
static float complex cmix(const float complex p0, const float complex p1, const float t)
Definition liquify.c:713
static gboolean btn_make_radio_callback(GtkToggleButton *btn, GdkEventButton *event, dt_iop_module_t *module)
Definition liquify.c:3825
static void draw_circle(cairo_t *cr, const float complex pt, const double diameter)
Definition liquify.c:1805
static void update_warp_count(const dt_iop_liquify_gui_data_t *g)
Definition liquify.c:1840
dt_liquify_layer_enum_t
Definition liquify.c:92
@ DT_LIQUIFY_LAYER_STRENGTHPOINT
Definition liquify.c:111
@ DT_LIQUIFY_LAYER_HARDNESSPOINT2_HANDLE
Definition liquify.c:103
@ DT_LIQUIFY_LAYER_HARDNESSPOINT1
Definition liquify.c:109
@ DT_LIQUIFY_LAYER_HARDNESSPOINT2
Definition liquify.c:110
@ DT_LIQUIFY_LAYER_CTRLPOINT1
Definition liquify.c:106
@ DT_LIQUIFY_LAYER_PATH
Definition liquify.c:98
@ DT_LIQUIFY_LAYER_HARDNESS2
Definition liquify.c:96
@ DT_LIQUIFY_LAYER_LAST
Definition liquify.c:112
@ DT_LIQUIFY_LAYER_HARDNESS1
Definition liquify.c:95
@ DT_LIQUIFY_LAYER_RADIUSPOINT
Definition liquify.c:108
@ DT_LIQUIFY_LAYER_RADIUS
Definition liquify.c:94
@ DT_LIQUIFY_LAYER_WARPS
Definition liquify.c:97
@ DT_LIQUIFY_LAYER_HARDNESSPOINT1_HANDLE
Definition liquify.c:102
@ DT_LIQUIFY_LAYER_CTRLPOINT2
Definition liquify.c:107
@ DT_LIQUIFY_LAYER_CENTERPOINT
Definition liquify.c:105
@ DT_LIQUIFY_LAYER_RADIUSPOINT_HANDLE
Definition liquify.c:101
@ DT_LIQUIFY_LAYER_CTRLPOINT1_HANDLE
Definition liquify.c:99
@ DT_LIQUIFY_LAYER_STRENGTHPOINT_HANDLE
Definition liquify.c:104
@ DT_LIQUIFY_LAYER_CTRLPOINT2_HANDLE
Definition liquify.c:100
@ DT_LIQUIFY_LAYER_BACKGROUND
Definition liquify.c:93
static void init_warp(dt_liquify_warp_t *warp, float complex point)
Definition liquify.c:2761
static void node_insert_before(dt_iop_liquify_params_t *p, dt_liquify_path_data_t *this, dt_liquify_path_data_t *new)
Definition liquify.c:410
#define GREY
Definition liquify.c:133
void init_global(dt_iop_module_so_t *module)
Definition liquify.c:1720
static void set_source_rgba(cairo_t *cr, dt_liquify_rgba_t rgba)
Definition liquify.c:1814
void gui_focus(struct dt_iop_module_t *module, gboolean in)
Definition liquify.c:2872
#define BG_COLOR
Definition liquify.c:1933
static void sync_pipe(struct dt_iop_module_t *module, gboolean history)
Definition liquify.c:2881
static dt_liquify_path_data_t * alloc_curve_to(dt_iop_module_t *module, float complex end_point)
Definition liquify.c:2798
static void mix_warps(dt_liquify_warp_t *result, const dt_liquify_warp_t *warp1, const dt_liquify_warp_t *warp2, const complex float pt, const float t)
Definition liquify.c:718
void modify_roi_out(struct dt_iop_module_t *module, const struct dt_dev_pixelpipe_t *pipe, struct dt_dev_pixelpipe_iop_t *piece, dt_iop_roi_t *roi_out, const dt_iop_roi_t *roi_in)
Definition liquify.c:1293
static float get_arc_length(const float complex points[], const int n_points)
Definition liquify.c:794
#define CONF_RADIUS
Definition liquify.c:85
int key_pressed(struct dt_iop_module_t *self, GdkEventKey *event)
Definition liquify.c:3652
float *const restrict const size_t k
float *const restrict const size_t const size_t ch
#define DT_M_PI_F
Definition math.h:52
#define DT_M_PI
Definition math.h:53
#define M_PI
Definition math.h:45
size_t size
Definition mipmap_cache.c:3
int dt_opencl_enqueue_kernel_2d(const int dev, const int kernel, const size_t *sizes)
Definition opencl.c:2136
int dt_opencl_create_kernel(const int prog, const char *name)
Definition opencl.c:2030
void * dt_opencl_copy_host_to_device_constant(const int devid, const size_t size, void *host)
Definition opencl.c:2332
int dt_opencl_enqueue_copy_image(const int devid, cl_mem src, cl_mem dst, size_t *orig_src, size_t *orig_dst, size_t *region)
Definition opencl.c:2261
void dt_opencl_free_kernel(const int kernel)
Definition opencl.c:2073
int dt_opencl_set_kernel_arg(const int dev, const int kernel, const int num, const size_t size, const void *arg)
Definition opencl.c:2127
void dt_opencl_release_mem_object(cl_mem mem)
Definition opencl.c:2383
#define ROUNDUPDHT(a, b)
Definition opencl.h:82
#define ROUNDUPDWD(a, b)
Definition opencl.h:81
const float factor
Definition pdf.h:90
@ DT_DEV_PIPE_TOP_CHANGED
static uint64_t dt_dev_pixelpipe_get_history_hash(const dt_dev_pixelpipe_t *pipe)
struct _GtkWidget GtkWidget
Definition splash.h:29
const float uint32_t state[4]
const float r
struct dt_gui_gtk_t * gui
Definition darktable.h:775
struct dt_collection_t * collection
Definition darktable.h:781
struct dt_develop_t * develop
Definition darktable.h:770
struct dt_control_t * control
Definition darktable.h:773
dt_develop_t * develop
Definition liquify.c:558
gboolean from_distort_transform
Definition liquify.c:563
const dt_dev_pixelpipe_t * pipe
Definition liquify.c:559
struct dt_iop_module_t *void * data
int32_t height
Definition develop.h:189
int32_t raw_height
Definition develop.h:228
int32_t processed_width
Definition develop.h:233
int32_t raw_width
Definition develop.h:228
struct dt_dev_pixelpipe_t * virtual_pipe
Definition develop.h:251
float scaling
Definition develop.h:193
struct dt_develop_t::@17 roi
int32_t processed_height
Definition develop.h:233
int32_t width
Definition develop.h:189
dt_ui_t * ui
Definition gtk.h:164
enum dt_interpolation_type id
unsigned int channels
Definition format.h:54
dt_iop_buffer_type_t datatype
Definition format.h:56
dt_iop_liquify_params_t params
Definition liquify.c:296
GdkModifierType last_mouse_mods
GDK modifiers at the time mouse button was pressed.
Definition liquify.c:301
dt_liquify_path_data_t * temp
Points to the element under construction or NULL.
Definition liquify.c:306
dt_liquify_hit_t dragging
Element being dragged with mouse button.
Definition liquify.c:304
GtkToggleButton * btn_curve_tool
Definition liquify.c:310
float complex last_button1_pressed_pos
Definition liquify.c:300
dt_liquify_hit_t last_hit
Element last hit with mouse button.
Definition liquify.c:303
float complex last_mouse_pos
Definition liquify.c:299
dt_liquify_status_enum_t status
Various flags.
Definition liquify.c:307
dt_iop_global_data_t * data
Definition imageop.h:233
GtkWidget * widget
Definition imageop.h:337
struct dt_develop_t * dev
Definition imageop.h:296
dt_iop_gui_data_t * gui_data
Definition imageop.h:311
dt_iop_global_data_t * global_data
Definition imageop.h:314
int32_t params_size
Definition imageop.h:309
dt_iop_params_t * params
Definition imageop.h:307
Region of interest passed through the pixelpipe.
Definition imageop.h:72
double scale
Definition imageop.h:74
dt_liquify_layer_enum_t layer
Definition liquify.c:278
dt_liquify_path_data_t * elem
Definition liquify.c:279
const char * hint
hint displayed when hovering
Definition liquify.c:148
dt_liquify_layer_enum_t hover_master
hover whenever master layer hovers, eg. to
Definition liquify.c:141
dt_liquify_rgba_t bg
the background color for this layer
Definition liquify.c:143
dt_liquify_layer_flag_enum_t flags
various flags for layer
Definition liquify.c:147
dt_liquify_rgba_t fg
the foreground color for this layer
Definition liquify.c:142
float complex ctrl1
Definition liquify.c:263
float complex ctrl2
Definition liquify.c:264
dt_liquify_node_t node
Definition liquify.c:273
dt_liquify_warp_t warp
Definition liquify.c:272
dt_liquify_path_header_t header
Definition liquify.c:271
dt_liquify_layer_enum_t hovered
Definition liquify.c:241
dt_liquify_layer_enum_t selected
Definition liquify.c:240
dt_liquify_node_type_enum_t node_type
Definition liquify.c:239
dt_liquify_path_data_enum_t type
Definition liquify.c:238
float complex point
Definition liquify.c:252
float complex radius
a point (the effective radius scalar is: cabs(radius - point))
Definition liquify.c:254
float control2
range 0.0 .. 1.0 == radius
Definition liquify.c:256
float complex strength
a point (the effective strength vector is: strength - point)
Definition liquify.c:253
dt_liquify_warp_type_enum_t type
Definition liquify.c:257
dt_liquify_status_enum_t status
Definition liquify.c:258
float control1
range 0.0 .. 1.0 == radius
Definition liquify.c:255
#define MIN(a, b)
Definition thinplate.c:32