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
48#include "widgets/gdkkeys.h"
50#include "config.h"
51#endif
52#include "system/macros.h"
53#include "system/openmp.h"
55#include "system/mem_alloc.h"
56#include "common/logging.h"
58#include "pixel/interpolation.h"
59#include "common/opencl.h"
60#include "math/math.h"
61#include "common/collection.h"
62#include "common/conf.h"
63#include "control/control.h"
65#include "develop/imageop.h"
66#include "develop/imageop_gui.h"
67#include "develop/develop.h"
68
69#include "gui/application.h"
70#include "iop/iop_api.h"
71#include <assert.h>
72#include <cairo.h>
73#include <complex.h>
74#include <math.h>
75#include <stdlib.h>
76#include "widgets/label.h"
78
79#ifdef _OPENMP
80#include <omp.h>
81#endif
82
83// this is the version of the modules parameters, and includes version information about compile-time dt
85
86#pragma GCC diagnostic ignored "-Wshadow"
87
88#define MAX_NODES 100 // max of nodes in one instance
89
90const int LOOKUP_OVERSAMPLE = 10;
91const int INTERPOLATION_POINTS = 100; // when interpolating bezier
92const float STAMP_RELOCATION = 0.1; // how many radii to move stamp forward when following a path
93
94#define CONF_RADIUS "plugins/darkroom/liquify/radius"
95#define CONF_ANGLE "plugins/darkroom/liquify/angle"
96#define CONF_STRENGTH "plugins/darkroom/liquify/strength"
97
98// enum of layers. sorted back to front.
99
123
135
136typedef struct
137{
138 float red, green, blue, alpha;
140
141#define COLOR_NULL { 0.0, 0.0, 0.0, 0.8 }
142#define GREY { 0.3, 0.3, 0.3, 0.8 }
143#define LGREY { 0.8, 0.8, 0.8, 1.0 }
144#define COLOR_DEBUG { 0.9, 0.9, 0.0, 1.0 }
145static const dt_liquify_rgba_t DT_LIQUIFY_COLOR_SELECTED = { 1.0, 1.0, 1.0, 1.0 };
146static const dt_liquify_rgba_t DT_LIQUIFY_COLOR_HOVER = { 1.0, 1.0, 1.0, 0.8 };
147
159
161{
181};
182
195
197{
198 // value in 1/96 inch (that is: in pixels on a standard 96 dpi screen)
199 2.0, // DT_LIQUIFY_UI_WIDTH_THINLINE
200 3.0, // DT_LIQUIFY_UI_WIDTH_THICKLINE
201 3.0, // DT_LIQUIFY_UI_WIDTH_DOUBLELINE
202 9.0, // DT_LIQUIFY_UI_WIDTH_GIZMO
203 7.0, // DT_LIQUIFY_UI_WIDTH_GIZMO_SMALL
204 100.0, // DT_LIQUIFY_UI_WIDTH_DEFAULT_RADIUS,
205 50.0, // DT_LIQUIFY_UI_WIDTH_DEFAULT_STRENGTH,
206 3.0 // DT_LIQUIFY_UI_WIDTH_MIN_DRAG
207};
208
209typedef enum
210{
211 DT_LIQUIFY_WARP_TYPE_LINEAR, // $DESCRIPTION: "linear" A linear warp originating from one point.
212 DT_LIQUIFY_WARP_TYPE_RADIAL_GROW, // $DESCRIPTION: "radial grow" A radial warp originating from one point.
213 DT_LIQUIFY_WARP_TYPE_RADIAL_SHRINK, // $DESCRIPTION: "radial shrink"
216
217typedef enum
218{
219 DT_LIQUIFY_NODE_TYPE_CUSP, // $DESCRIPTION: "cusp"
220 DT_LIQUIFY_NODE_TYPE_SMOOTH, // $DESCRIPTION: "smooth"
221 DT_LIQUIFY_NODE_TYPE_SYMMETRICAL, // $DESCRIPTION: "symmetrical"
222 DT_LIQUIFY_NODE_TYPE_AUTOSMOOTH, // $DESCRIPTION: "autosmooth"
225
226typedef enum
227{
228 DT_LIQUIFY_STATUS_NONE = 0, // $DESCRIPTION: "none"
229 DT_LIQUIFY_STATUS_NEW = 1, // $DESCRIPTION: "new"
230 DT_LIQUIFY_STATUS_INTERPOLATED = 2, // $DESCRIPTION: "interpolated"
231 DT_LIQUIFY_STATUS_PREVIEW = 4, // $DESCRIPTION: "preview"
234
235// enumerates the shapes types we use.
236
237typedef enum
238{
239 DT_LIQUIFY_PATH_INVALIDATED = 0, // $DESCRIPTION: "invalidated"
240 DT_LIQUIFY_PATH_MOVE_TO_V1, // $DESCRIPTION: "move"
241 DT_LIQUIFY_PATH_LINE_TO_V1, // $DESCRIPTION: "line"
242 DT_LIQUIFY_PATH_CURVE_TO_V1, // $DESCRIPTION: "curve"
244
255
256// Scalars and vectors are represented here as points because the only
257// thing we can reasonably distort_transform are points.
258
259typedef struct
260{
261 float complex point;
262 float complex strength;
263 float complex radius;
264 float control1;
265 float control2;
269
270typedef struct
271{
272 float complex ctrl1;
273 float complex ctrl2;
275
276// set up lots of alternative ways to get at the popular members.
277
284
290
292
297
298typedef struct
299{
302
303typedef struct
304{
306 int node_index; // last node index inserted
307
308 float complex last_mouse_pos;
310 GdkModifierType last_mouse_mods;
311
314
317
318 GtkLabel *label_warp, *label_node;
319 GtkToggleButton *btn_point_tool, *btn_line_tool, *btn_curve_tool, *btn_node_tool;
320
321 gboolean just_started;
323
324
325// this returns a translatable name
326const char *name()
327{
328 return _("l_iquify");
329}
330
331const char **description(struct dt_iop_module_t *self)
332{
333 return dt_iop_set_description(self, _("distort parts of the image"),
334 _("creative"),
335 _("linear, RGB, scene-referred"),
336 _("geometric, RGB"),
337 _("linear, RGB, scene-referred"));
338}
339
340
342{
343 return IOP_GROUP_EFFECTS;
344}
345
346int flags()
347{
349}
350
352{
353 return IOP_TAG_DISTORT;
354}
355
357{
358 return IOP_CS_RGB;
359}
360
363{
364 default_input_format(self, pipe, piece, dsc);
365 dsc->channels = 4;
366 dsc->datatype = TYPE_FLOAT;
367}
368
369/******************************************************************************/
370/* Code common to op-engine and gui. */
371/******************************************************************************/
372
373static inline float get_rot(const dt_liquify_warp_type_enum_t warp_type)
374{
376 return DT_M_PI_F;
377 else
378 return 0.0f;
379}
380
382{
383 for(int k=0; k<MAX_NODES; k++)
384 if(p->nodes[k].header.type == DT_LIQUIFY_PATH_INVALIDATED)
385 {
386 *node_index = k;
387 p->nodes[k].header.idx = k;
388 p->nodes[k].header.next = p->nodes[k].header.prev = -1;
389 p->nodes[k].header.selected = p->nodes[k].header.hovered = 0;
390 return &p->nodes[k];
391 }
392 return NULL;
393}
394
396{
397 if(n->header.prev == -1)
398 return NULL;
399 else
400 return &p->nodes[n->header.prev];
401}
402
404{
405 if(index > -1 && index < MAX_NODES)
406 return &p->nodes[index];
407 else
408 return NULL;
409}
410
412{
413 if(n->header.next == -1)
414 return NULL;
415 else
416 return &p->nodes[n->header.next];
417}
418
420{
421 new->header.next = this->header.idx;
422 new->header.prev = this->header.prev;
423 if(this->header.prev != -1)
424 p->nodes[this->header.prev].header.next = new->header.idx;
425 this->header.prev = new->header.idx;
426}
427
429{
430 int last=0;
431 for(last=MAX_NODES-1; last>0; last--)
432 if(p->nodes[last].header.type != DT_LIQUIFY_PATH_INVALIDATED)
433 break;
434 int k = 0;
435
436 while(k<=last)
437 {
438 if(p->nodes[k].header.type == DT_LIQUIFY_PATH_INVALIDATED)
439 {
440 for(int e=0; e<last; e++)
441 {
442 // then move slot if above position k
443 if(e >= k) p->nodes[e] = p->nodes[e+1];
444 // update all pointers above position k
445 if(e >= k) p->nodes[e].header.idx--;
446 if(p->nodes[e].header.prev >= k) p->nodes[e].header.prev--;
447 if(p->nodes[e].header.next >= k) p->nodes[e].header.next--;
448 }
449 last--;
450 }
451 else
452 k++;
453 }
454 // invalidate all nodes beyond the last moved one
455 for(int k=last+1; k<MAX_NODES; k++)
456 p->nodes[k].header.type = DT_LIQUIFY_PATH_INVALIDATED;
457}
458
460{
461 dt_liquify_path_data_t *prev = node_prev(p, this);
462 dt_liquify_path_data_t *next = node_next(p, this);
463
464 if(IS_NULL_PTR(prev) && !IS_NULL_PTR(next))
465 {
466 next->header.prev = -1;
468 }
469 else if(!IS_NULL_PTR(prev))
470 {
471 prev->header.next = this->header.next;
472
473 if(!IS_NULL_PTR(next))
474 next->header.prev = prev->header.idx;
475 }
476
477 this->header.prev = this->header.next = - 1;
478 this->header.type = DT_LIQUIFY_PATH_INVALIDATED;
479 node_gc(p);
480}
481
483{
485
486 // clear next
487 while(n)
488 {
490 n = node_next(p, n);
491 }
492
493 // clear prev
494 n = this;
495 while(n)
496 {
497 n->header.type = DT_LIQUIFY_PATH_INVALIDATED;
498 n = node_prev(p, n);
499 }
500 node_gc(p);
501}
502
572
573typedef struct
574{
577 /* When set, compose through the geometry service instead of a pixel pipe. Exactly one of
578 * `pipe' and `chain' is used: the pipe when a piece is being processed, the chain when the
579 * GUI asks the pixel-less service (develop/geometry/geometry.h). The paths are the same
580 * fold in both cases -- see _distort_paths(). */
583 float to_scale;
587
588static void _distort_paths(const struct dt_iop_module_t *module,
589 const distort_params_t *params, const dt_iop_liquify_params_t *p)
590{
591 int len = 0;
592
593 // count nodes
594
595 for(int k = 0; k < MAX_NODES; k++)
596 {
599 break;
600
601 switch (data->header.type)
602 {
604 len += 2;
605 // fall thru
608 len += 3;
609 break;
610 default:
611 break;
612 }
613 }
614
615 // create buffer with all points
616
617 float *buffer = malloc(sizeof(float) * 2 * len);
618 float *b = buffer;
619
620 for(int k = 0; k < MAX_NODES; k++)
621 {
624 break;
625
626 switch (data->header.type)
627 {
629 *b++ = crealf(data->node.ctrl1) / params->from_scale;
630 *b++ = cimagf(data->node.ctrl1) / params->from_scale;
631 *b++ = crealf(data->node.ctrl2) / params->from_scale;
632 *b++ = cimagf(data->node.ctrl2) / params->from_scale;
633 // fall thru
636 *b++ = crealf(data->warp.point) / params->from_scale;
637 *b++ = cimagf(data->warp.point) / params->from_scale;
638 *b++ = crealf(data->warp.strength) / params->from_scale;
639 *b++ = cimagf(data->warp.strength) / params->from_scale;
640 *b++ = crealf(data->warp.radius) / params->from_scale;
641 *b++ = cimagf(data->warp.radius) / params->from_scale;
642 break;
643 default:
644 break;
645 }
646 }
647 if(!IS_NULL_PTR(params->chain))
648 {
649 /* The geometry service's equivalent of the two branches below. The bound is the caller's
650 * own iop_order, exclusive, so this module is not re-entered and the recursion ends. */
651 if(params->transf_direction == DT_DEV_TRANSFORM_DIR_ALL)
652 {
654 len);
656 len);
657 }
658 else
659 dt_geometry_chain_compose(params->chain, module->iop_order, params->transf_direction, buffer, len);
660 }
661 else if(params->from_distort_transform)
662 {
663 if(params->transf_direction == DT_DEV_TRANSFORM_DIR_ALL)
664 {
665 dt_dev_distort_transform_locked(params->pipe, module->iop_order,
666 DT_DEV_TRANSFORM_DIR_BACK_EXCL, buffer, len);
667 dt_dev_distort_transform_locked(params->pipe, module->iop_order,
668 DT_DEV_TRANSFORM_DIR_FORW_EXCL, buffer, len);
669 }
670 else
671 dt_dev_distort_transform_locked(params->pipe, module->iop_order,
672 params->transf_direction, buffer, len);
673 }
674 else
675 {
676 if(params->transf_direction == DT_DEV_TRANSFORM_DIR_ALL)
677 {
680 }
681 else
682 dt_dev_distort_transform_plus(params->pipe, module->iop_order, params->transf_direction, buffer, len);
683 }
684
685 // record back the transformed points
686
687 b = buffer;
688
689 for(int k = 0; k < MAX_NODES; k++)
690 {
693 break;
694
695 switch (data->header.type)
696 {
698 data->node.ctrl1 = (b[0] + b[1] * I) * params->to_scale;
699 b += 2;
700 data->node.ctrl2 = (b[0] + b[1] * I) * params->to_scale;
701 b += 2;
702 // fall thru
705 data->warp.point = (b[0] + b[1] * I) * params->to_scale;
706 b += 2;
707 data->warp.strength = (b[0] + b[1] * I) * params->to_scale;
708 b += 2;
709 data->warp.radius = (b[0] + b[1] * I) * params->to_scale;
710 b += 2;
711 break;
712 default:
713 break;
714 }
715 }
716
717 dt_free(buffer);
718}
719
720static void distort_paths_raw_to_piece(const struct dt_iop_module_t *module,
721 const dt_dev_pixelpipe_t *pipe,
722 const float roi_in_scale,
724 const gboolean from_distort_transform)
725{
726 const distort_params_t params = { module->dev, pipe, NULL, 1.f, roi_in_scale, DT_DEV_TRANSFORM_DIR_BACK_EXCL, from_distort_transform };
727 _distort_paths(module, &params, p);
728}
729
731static void distort_paths_raw_to_piece_chain(const struct dt_iop_module_t *module,
732 dt_geometry_chain_t *chain, const float roi_in_scale,
734{
735 const distort_params_t params = { module->dev, NULL, chain, 1.f, roi_in_scale,
736 DT_DEV_TRANSFORM_DIR_BACK_EXCL, TRUE };
737 _distort_paths(module, &params, p);
738}
739
740// op-engine code
741
742static inline float complex normalize(const float complex v)
743{
744 if(cabsf(v) < 0.000001f)
745 return 1.0f;
746 return v / cabsf(v);
747}
748
749// calculate the linear blend of scalars a and b
750
751static inline float mix(const float a, const float b, const float t)
752{
753 return a + (b - a) * t;
754}
755
756
757// calculate the linear blend of points p0 and p1
758
759static inline float complex cmix(const float complex p0, const float complex p1, const float t)
760{
761 return p0 + (p1 - p0) * t;
762}
763
764static void mix_warps(dt_liquify_warp_t *result,
765 const dt_liquify_warp_t *warp1,
766 const dt_liquify_warp_t *warp2,
767 const complex float pt,
768 const float t)
769{
770 result->type = warp1->type;
771 result->control1 = mix (warp1->control1, warp2->control1, t);
772 result->control2 = mix (warp1->control2, warp2->control2, t);
773
774 const float radius = mix(cabsf(warp1->radius - warp1->point), cabsf(warp2->radius - warp2->point), t);
775 result->radius = pt + radius;
776
777 const complex float p1 = warp1->strength - warp1->point;
778 const complex float p2 = warp2->strength - warp2->point;
779 float arg1 = cargf(p1);
780 float arg2 = cargf(p2);
781 gboolean invert = FALSE;
782
783 if(arg1 > .0f && arg2 < -(M_PI_F / 2.f))
784 {
785 invert = TRUE;
786 arg1 = M_PI_F - arg1;
787 arg2 = -M_PI_F - arg2;
788 }
789 else if(arg1 < -(M_PI_F / 2.f) && arg2 > .0f)
790 {
791 invert = TRUE;
792 arg1 = -M_PI_F - arg1;
793 arg2 = M_PI_F - arg2;
794 }
795
796 const float r = mix(cabsf(p1), cabsf(p2), t);
797 const float phi = invert ? M_PI_F - mix(arg1, arg2, t) : mix(arg1, arg2, t);
798
799 result->strength = pt + r * cexpf(phi * I);
800 result->point = pt;
801}
802
803// Interpolate a cubic bezier spline into a series of points.
804
805static void interpolate_cubic_bezier(const float complex p0,
806 const float complex p1,
807 const float complex p2,
808 const float complex p3,
809 float complex buffer[],
810 const int n)
811{
812 // convert from bernstein basis to polynomial basis to get faster math
813 // See: http://www.tinaja.com/glib/cubemath.pdf
814 const float complex A = p3 - 3 * p2 + 3 * p1 - p0;
815 const float complex B = 3 * p2 - 6 * p1 + 3 * p0;
816 const float complex C = 3 * p1 - 3 * p0;
817 const float complex D = p0;
818
819 float complex *buf = buffer;
820 const float step = 1.0f / n;
821 float t = step;
822 *buf++ = p0;
823
824 for(int i = 1; i < n - 1; ++i)
825 {
826 *buf++ = ((A * t + B) * t + C) * t + D;
827 t += step;
828 }
829 *buf = p3;
830}
831
833
834/*
835 Get approx. arc length of a curve.
836
837 Used to approximate the arc length of a bezier curve.
838*/
839
840static float get_arc_length(const float complex points[], const int n_points)
841{
842 float length = 0.0f;
843 for(int i = 1; i < n_points; i++)
844 length += cabsf(points[i-1] - points[i]);
845 return length;
846}
847
848typedef struct
849{
850 int i;
851 float length;
853
854/*
855 Interpolate a point on a curve at a specified arc length.
856
857 In a bezier curve the parameter t usually does not correspond to
858 the arc length.
859*/
860
861static float complex point_at_arc_length(const float complex points[], const int n_points,
862 const float arc_length, restart_cookie_t *restart)
863{
864 float length = restart ? restart->length : 0.0f;
865
866 for(int i = restart ? restart->i : 1; i < n_points; i++)
867 {
868 const float prev_length = length;
869 length += cabsf(points[i-1] - points[i]);
870
871 if(length >= arc_length)
872 {
873 const float t = (arc_length - prev_length) / (length - prev_length);
874 if(!IS_NULL_PTR(restart))
875 {
876 restart->i = i;
877 restart->length = prev_length;
878 }
879 return cmix(points[i - 1], points[i], t);
880 }
881 }
882
883 return points[n_points - 1];
884}
885
886/*
887 Build a lookup table for the warp intensity.
888
889 Lookup table for the warp intensity function: f(x). The warp
890 intensity function determines how much a pixel is influenced by the
891 warp depending from its distance from a central point.
892
893 Boundary conditions: f(0) must be 1 and f(@a distance) must be 0.
894 f'(0) and f'(@a distance) must both be 0 or we'll get artifacts on
895 the picture.
896
897 Implementation: a bezier curve with p0 = 0, 1 and p3 = 1, 0. p1 is
898 defined by @a control1, 1 and p2 by @a control1, 0. Because a
899 bezier is parameterized on t, we have to reparameterize on x, which
900 we do by linear interpolation.
901
902 Octave code:
903
904 t = linspace(0,1,100);
905 grid;
906 hold on;
907 for steps = 0:0.1:1
908 cpoints = [0,1; steps,1; steps,0; 1,0];
909 bezier = cbezier2poly(cpoints);
910 x = polyval(bezier(1,:), t);
911 y = polyval(bezier(2,:), t);
912 plot(t, interp1(x, y, t));
913 end
914 hold off;
915*/
916
917static float *build_lookup_table(const int distance, const float control1, const float control2)
918{
919 float complex *clookup = dt_pixelpipe_cache_alloc_align_cache(sizeof(float complex) * (distance + 2), 0);
920 if(IS_NULL_PTR(clookup)) return NULL;
921
922 interpolate_cubic_bezier(I, control1 + I, control2, 1.0, clookup, distance + 2);
923
924 // reparameterize bezier by x and keep only y values
925 float *lookup = dt_pixelpipe_cache_alloc_align_float_cache((size_t)(distance + 2), 0);
927 {
929 return NULL;
930 }
931 float *ptr = lookup;
932 float complex *cptr = clookup + 1;
933 const float complex *cptr_end = cptr + distance;
934 const float step = 1.0f / (float) distance;
935 float x = 0.0f;
936
937 *ptr++ = 1.0f;
938 for(int i = 1; i < distance && cptr < cptr_end; i++)
939 {
940 x += step;
941 while(crealf(*cptr) < x && cptr < cptr_end)
942 cptr++;
943 const float dx1 = crealf(cptr[0] - cptr[-1]);
944 const float dx2 = x - crealf(cptr[-1]);
945 *ptr++ = cimagf(cptr[0]) +(dx2 / dx1) * (cimagf(cptr[0]) - cimagf(cptr[-1]));
946 }
947 *ptr++ = 0.0f;
948
950 return lookup;
951}
952
953static void compute_round_stamp_extent(cairo_rectangle_int_t *const restrict stamp_extent,
954 const dt_liquify_warp_t *const restrict warp)
955{
956
957 const int iradius = round(cabsf(warp->radius - warp->point));
958 assert(iradius > 0);
959
960 stamp_extent->x = stamp_extent->y = -iradius;
961 stamp_extent->x += crealf(warp->point);
962 stamp_extent->y += cimagf(warp->point);
963 stamp_extent->width = stamp_extent->height = 2 * iradius + 1;
964}
965
966/*
967 Compute a round(circular) stamp.
968
969 The stamp is a vector field of warp vectors around a center point.
970
971 In a linear warp the center point gets a warp of @a strength, while
972 points on the circumference of the circle get no warp at all.
973 Between center and circumference the warp magnitude tapers off
974 following a curve (see: build_lookup_table()).
975
976 Note that when applying a linear stamp to a path, we will first rotate its
977 vectors into the direction of the path.
978
979 In a radial warp the center point and the points on the
980 circumference get no warp. Between center and circumference the
981 warp magnitude follows a curve with maximum at radius / 0.5
982
983 Our stamp is stored in a rectangular region.
984*/
985
986static int build_round_stamp(float complex **pstamp,
987 cairo_rectangle_int_t *const restrict stamp_extent,
988 const dt_liquify_warp_t *const restrict warp)
989{
990 const int iradius = round(cabsf(warp->radius - warp->point));
991 assert(iradius > 0);
992
993 stamp_extent->x = stamp_extent->y = -iradius;
994 stamp_extent->width = stamp_extent->height = 2 * iradius + 1;
995
996 // 0.5 is factored in so the warp starts to degenerate when the
997 // strength arrow crosses the warp radius.
998 float complex strength = 0.5f * (warp->strength - warp->point);
999 strength = (warp->status & DT_LIQUIFY_STATUS_INTERPOLATED) ?
1001 const float abs_strength = cabsf(strength);
1002
1003 float complex *restrict stamp =
1004 calloc(sizeof(float complex), (size_t)stamp_extent->width * stamp_extent->height);
1005 if(IS_NULL_PTR(stamp)) return 1;
1006
1007 // lookup table: map of distance from center point => warp
1008 const int table_size = iradius * LOOKUP_OVERSAMPLE;
1009 const float *restrict lookup_table = build_lookup_table(table_size, warp->control1, warp->control2);
1010 if(IS_NULL_PTR(lookup_table))
1011 {
1012 dt_free(stamp);
1013 return 1;
1014 }
1015
1016 // points into buffer at the center of the circle
1017 float complex *const center = stamp + 2 * iradius * iradius + 2 * iradius;
1018
1019 // The expensive operation here is hypotf (). By dividing the
1020 // circle in quadrants and doing only the inside we have to calculate
1021 // hypotf only for PI / 16 = 0.196 of the stamp area.
1022 // We don't do octants to avoid false sharing of cache lines between threads.
1023 // doesn't work for OSX see issue #7349
1025 for(int y = 0; y <= iradius; y++)
1026 {
1027 for(int x = 0; x <= iradius; x++)
1028 {
1029 const float dist = dt_fast_hypotf(x, y);
1030 const int idist = round(dist * LOOKUP_OVERSAMPLE);
1031 if(idist >= table_size)
1032 // idist will only grow bigger in this row
1033 break;
1034
1035 // pointers into the 4 quadrants of the circle
1036 // quadrant count is ccw from positive x-axis
1037 float complex *const q1 = center - y * stamp_extent->width + x;
1038 float complex *const q2 = center - y * stamp_extent->width - x;
1039 float complex *const q3 = center + y * stamp_extent->width - x;
1040 float complex *const q4 = center + y * stamp_extent->width + x;
1041
1042 float abs_lookup = abs_strength * lookup_table[idist] / iradius;
1043
1044 switch (warp->type)
1045 {
1047 *q1 = abs_lookup * ( x - y * I);
1048 *q2 = abs_lookup * (-x - y * I);
1049 *q3 = abs_lookup * (-x + y * I);
1050 *q4 = abs_lookup * ( x + y * I);
1051 break;
1052
1054 *q1 = -abs_lookup * ( x - y * I);
1055 *q2 = -abs_lookup * (-x - y * I);
1056 *q3 = -abs_lookup * (-x + y * I);
1057 *q4 = -abs_lookup * ( x + y * I);
1058 break;
1059
1060 default:
1061 *q1 = *q2 = *q3 = *q4 = strength * lookup_table[idist];
1062 break;
1063 }
1064 }
1065 }
1066
1067 dt_pixelpipe_cache_free_align(lookup_table);
1068 *pstamp = stamp;
1069 return 0;
1070}
1071
1072/*
1073 Applies a stamp at a specified position.
1074
1075 Applies a stamp at the position specified by @a point and adds the
1076 resulting vector field to the global distortion map @a global_map.
1077
1078 The global distortion map is a map of relative pixel displacements
1079 encompassing all our paths.
1080*/
1081
1082static void add_to_global_distortion_map(float complex *global_map,
1083 const cairo_rectangle_int_t *const restrict global_map_extent,
1084 const dt_liquify_warp_t *const restrict warp,
1085 const float complex *const restrict stamp,
1086 const cairo_rectangle_int_t *stamp_extent)
1087{
1088 cairo_rectangle_int_t mmext = *stamp_extent;
1089 mmext.x += (int) round(crealf(warp->point));
1090 mmext.y += (int) round(cimagf(warp->point));
1091 cairo_rectangle_int_t cmmext = mmext;
1092 cairo_region_t *mmreg = cairo_region_create_rectangle(&mmext);
1093 cairo_region_intersect_rectangle(mmreg, global_map_extent);
1094 cairo_region_get_extents(mmreg, &cmmext);
1095 dt_free(mmreg);
1096
1097 #ifdef _OPENMP
1098 #pragma omp parallel for schedule (static) default (shared)
1099 #endif
1100
1101 for(int y = cmmext.y; y < cmmext.y + cmmext.height; y++)
1102 {
1103 const float complex *const srcrow = stamp + ((y - mmext.y) * mmext.width);
1104 float complex *const destrow = global_map + ((y - global_map_extent->y) * global_map_extent->width);
1105
1106 for(int x = cmmext.x; x < cmmext.x + cmmext.width; x++)
1107 {
1108 destrow[x - global_map_extent->x] -= srcrow[x - mmext.x];
1109 }
1110 }
1111}
1112
1113/*
1114 Applies the global distortion map to the picture. The distortion
1115 map maps points to the position from where the new color of the
1116 point should be sampled from. The distortion map is in relative
1117 device coords.
1118*/
1119
1122 const dt_dev_pixelpipe_iop_t *piece,
1123 const float *const restrict in,
1124 float *const restrict out,
1125 const dt_iop_roi_t *const roi_in,
1126 const dt_iop_roi_t *const roi_out,
1127 const int ch,
1128 const float complex *const map,
1129 const cairo_rectangle_int_t *extent)
1130{
1131 const int ch_width = ch * roi_in->width;
1132 const struct dt_interpolation * const interpolation =
1134
1135 #ifdef _OPENMP
1136 #pragma omp parallel for schedule (static) default (shared)
1137 #endif
1138
1139 for(int y = extent->y; y < extent->y + extent->height; y++)
1140 {
1141 // point inside roi_out ?
1142 if(y >= roi_out->y && y < roi_out->y + roi_out->height)
1143 {
1144 const float complex *row = map + (y - extent->y) * extent->width;
1145 float* out_sample = out + ((y - roi_out->y) * roi_out->width +
1146 extent->x - roi_out->x) * ch;
1147 for(int x = extent->x; x < extent->x + extent->width; x++)
1148 {
1149 if(
1150 // point inside roi_out ?
1151 (x >= roi_out->x && x < roi_out->x + roi_out->width) &&
1152 // point actually warped ?
1153 (*row != 0))
1154 {
1155 if(ch == 1)
1156 *out_sample = dt_interpolation_compute_sample(interpolation,
1157 in,
1158 x + crealf(*row) - roi_in->x,
1159 y + cimagf(*row) - roi_in->y,
1160 roi_in->width,
1161 roi_in->height,
1162 ch,
1163 ch_width);
1164 else
1166 interpolation,
1167 in,
1168 out_sample,
1169 x + crealf(*row) - roi_in->x,
1170 y + cimagf(*row) - roi_in->y,
1171 roi_in->width,
1172 roi_in->height,
1173 ch_width);
1174
1175 }
1176 ++row;
1177 out_sample += ch;
1178 }
1179 }
1180 }
1181}
1182
1183// calculate the map extent.
1184
1185static GSList *_get_map_extent(const dt_iop_roi_t *roi_out,
1186 const GList *interpolated,
1187 cairo_rectangle_int_t *map_extent)
1188{
1189 const cairo_rectangle_int_t roi_out_rect = { roi_out->x, roi_out->y, roi_out->width, roi_out->height };
1190 cairo_region_t *roi_out_region = cairo_region_create_rectangle(&roi_out_rect);
1191 cairo_region_t *map_region = cairo_region_create();
1192 GSList *in_roi = NULL;
1193
1194 for(const GList *i = interpolated; i; i = g_list_next(i))
1195 {
1196 const dt_liquify_warp_t *warp = ((dt_liquify_warp_t *) i->data);
1197 cairo_rectangle_int_t r;
1199 // add extent if not entirely outside the roi
1200 if(cairo_region_contains_rectangle(roi_out_region, &r) != CAIRO_REGION_OVERLAP_OUT)
1201 {
1202 cairo_region_union_rectangle(map_region, &r);
1203 in_roi = g_slist_prepend(in_roi, i->data);
1204 }
1205 }
1206
1207 // return the paths and the extent of all paths
1208 cairo_region_get_extents(map_region, map_extent);
1209 cairo_region_destroy(map_region);
1210 cairo_region_destroy(roi_out_region);
1211
1212 return g_slist_reverse(in_roi);
1213}
1214
1215static float complex *create_global_distortion_map(const cairo_rectangle_int_t *map_extent,
1216 const GSList *interpolated,
1217 gboolean inverted)
1218{
1219 const int mapsize = map_extent->width * map_extent->height;
1220 if (mapsize == 0)
1221 {
1222 // there are no pixels for which we need distortion info, so return right away
1223 // caller will see the NULL and bypass any further processing of the points it wants to distort
1224 return NULL;
1225 }
1226
1227 // allocate distortion map big enough to contain all paths
1228 float complex *map = dt_pixelpipe_cache_alloc_align_cache(sizeof(float complex) * mapsize, 0);
1229 if(IS_NULL_PTR(map)) return NULL;
1230 memset(map, 0, sizeof(float complex) * mapsize);
1231
1232 // build map
1233 for(const GSList *i = interpolated; i; i = g_slist_next(i))
1234 {
1235 const dt_liquify_warp_t *warp = ((dt_liquify_warp_t *) i->data);
1236 float complex *stamp = NULL;
1237 cairo_rectangle_int_t r;
1238 if(build_round_stamp(&stamp, &r, warp) != 0)
1239 {
1241 return NULL;
1242 }
1243 add_to_global_distortion_map(map, map_extent, warp, stamp, &r);
1244 dt_free(stamp);
1245 }
1246
1247 if(inverted)
1248 {
1249 float complex * const imap = dt_pixelpipe_cache_alloc_align_cache(sizeof(float complex) * mapsize, 0);
1250 if(IS_NULL_PTR(imap))
1251 {
1253 return NULL;
1254 }
1255 memset(imap, 0, sizeof(float complex) * mapsize);
1256
1257 // copy map into imap(inverted map).
1258 // imap [ n + dx(map[n]) , n + dy(map[n]) ] = -map[n]
1259
1260 #ifdef _OPENMP
1261 #pragma omp parallel for schedule (static) default (shared)
1262 #endif
1263
1264 for(int y = 0; y < map_extent->height; y++)
1265 {
1266 const float complex *const row = map + y * map_extent->width;
1267 for(int x = 0; x < map_extent->width; x++)
1268 {
1269 const float complex d = row[x];
1270 // compute new position (nx,ny) given the displacement d
1271 const int nx = x + (int)crealf(d);
1272 const int ny = y + (int)cimagf(d);
1273
1274 // if the point falls into the extent, set it
1275 if(nx>0 && nx<map_extent->width && ny>0 && ny<map_extent->height)
1276 imap[nx + ny * map_extent->width] = -d;
1277 }
1278 }
1279
1281
1282 // now just do a pass to avoid gap with a displacement of zero, note that we do not need high
1283 // precision here as the inverted distortion mask is only used to compute a final displacement
1284 // of points.
1285
1286 #ifdef _OPENMP
1287 #pragma omp parallel for schedule (static) default (shared)
1288 #endif
1289
1290 for(int y = 0; y < map_extent->height; y++)
1291 {
1292 float complex *const row = imap + y * map_extent->width;
1293 float complex last[2] = { 0, 0 };
1294 for(int x = 0; x < map_extent->width / 2 + 1; x++)
1295 {
1296 float complex *cl = row + x;
1297 float complex *cr = row + map_extent->width - x;
1298 if(x!=0)
1299 {
1300 if(*cl == 0) *cl = last[0];
1301 if(*cr == 0) *cr = last[1];
1302 }
1303 last[0] = *cl; last[1] = *cr;
1304 }
1305 }
1306
1307 map = imap;
1308 }
1309 return map;
1310}
1311
1312static float complex *build_global_distortion_map(struct dt_iop_module_t *module,
1313 const dt_dev_pixelpipe_t *pipe,
1314 const dt_dev_pixelpipe_iop_t *piece,
1315 const dt_iop_roi_t *roi_in,
1316 const dt_iop_roi_t *roi_out,
1317 cairo_rectangle_int_t *map_extent)
1318{
1319 // copy params
1320 dt_iop_liquify_params_t copy_params;
1321 memcpy(&copy_params, (dt_iop_liquify_params_t *)piece->data, sizeof(dt_iop_liquify_params_t));
1322
1323 distort_paths_raw_to_piece(module, pipe, roi_in->scale, &copy_params, FALSE);
1324
1325 GList *interpolated = interpolate_paths(&copy_params);
1326 GSList *interpolated_in_roi = _get_map_extent(roi_out, interpolated, map_extent);
1327
1328 float complex *map = create_global_distortion_map(map_extent, interpolated_in_roi, FALSE);
1329
1330 g_slist_free(interpolated_in_roi);
1331 interpolated_in_roi = NULL;
1332 g_list_free_full(interpolated, dt_free_gpointer);
1333 interpolated = NULL;
1334 return map;
1335}
1336
1337// 1st pass: how large would the output be, given this input roi?
1338// this is always called with the full buffer before processing.
1339void modify_roi_out(struct dt_iop_module_t *module, const struct dt_dev_pixelpipe_t *pipe,
1340 struct dt_dev_pixelpipe_iop_t *piece, dt_iop_roi_t *roi_out,
1341 const dt_iop_roi_t *roi_in)
1342{
1343 // output is same size as input
1344 *roi_out = *roi_in;
1345}
1346
1347// 2nd pass: which roi would this operation need as input to fill the given output region?
1348void modify_roi_in(struct dt_iop_module_t *module, const struct dt_dev_pixelpipe_t *pipe,
1349 struct dt_dev_pixelpipe_iop_t *piece, const dt_iop_roi_t *roi_out,
1350 dt_iop_roi_t *roi_in)
1351{
1352 // Because we move pixels, and we may have to sample a pixel from
1353 // outside roi_in, we need to expand roi_in to contain all our
1354 // paths. But we may ignore paths completely outside of roi_out.
1355
1356 *roi_in = *roi_out;
1357
1358 // copy params
1359 dt_iop_liquify_params_t copy_params;
1360 memcpy(&copy_params, (dt_iop_liquify_params_t*)piece->data, sizeof(dt_iop_liquify_params_t));
1361
1362 // Keep ROI expansion on the same processing pipe as piece->data. Preview-only state can be NULL
1363 // in workers, and mixing pipes here desynchronizes the distortion graph from the current piece.
1364 distort_paths_raw_to_piece(module, pipe, roi_in->scale, &copy_params, FALSE);
1365
1366 cairo_rectangle_int_t pipe_rect =
1367 {
1368 0,
1369 0,
1370 lroundf(piece->buf_in.width * roi_in->scale),
1371 lroundf(piece->buf_in.height * roi_in->scale)
1372 };
1373
1374 cairo_rectangle_int_t roi_in_rect =
1375 {
1376 roi_in->x,
1377 roi_in->y,
1378 roi_in->width,
1379 roi_in->height
1380 };
1381 cairo_region_t *roi_in_region = cairo_region_create_rectangle(&roi_in_rect);
1382
1383 // get extent of all paths
1384 GList *interpolated = interpolate_paths(&copy_params);
1385 cairo_rectangle_int_t extent;
1386 GSList *interpolated_in_roi = _get_map_extent(roi_out, interpolated, &extent);
1387 g_slist_free(interpolated_in_roi);
1388 interpolated_in_roi = NULL;
1389 g_list_free_full(interpolated, dt_free_gpointer);
1390 interpolated = NULL;
1391
1392 // (eventually) extend roi_in
1393 cairo_region_union_rectangle(roi_in_region, &extent);
1394 // and clamp to pipe extent
1395 cairo_region_intersect_rectangle(roi_in_region, &pipe_rect);
1396
1397 // write new extent to roi_in
1398 cairo_region_get_extents(roi_in_region, &roi_in_rect);
1399 roi_in->x = roi_in_rect.x;
1400 roi_in->y = roi_in_rect.y;
1401 roi_in->width = roi_in_rect.width;
1402 roi_in->height = roi_in_rect.height;
1403
1404 // cleanup
1405 cairo_region_destroy(roi_in_region);
1406}
1407
1418static int _liquify_warp_points(const dt_iop_liquify_params_t *const params_in,
1419 dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe,
1420 dt_geometry_chain_t *chain, float *const restrict points,
1421 const size_t points_count, const gboolean inverted)
1422{
1423
1424 // compute the extent of all points (all computations are done in RAW coordinate)
1425 float xmin = FLT_MAX, xmax = FLT_MIN, ymin = FLT_MAX, ymax = FLT_MIN;
1426 __OMP_PARALLEL_FOR__(if(points_count > 100) reduction(min:xmin, ymin) reduction(max:xmax, ymax))
1427 for(size_t i = 0; i < points_count * 2; i += 2)
1428 {
1429 const float x = points[i];
1430 const float y = points[i + 1];
1431 xmin = fmin(xmin, x);
1432 xmax = fmax(xmax, x);
1433 ymin = fmin(ymin, y);
1434 ymax = fmax(ymax, y);
1435 }
1436
1437 cairo_rectangle_int_t extent = { .x = (int)(xmin - .5), .y = (int)(ymin - .5),
1438 .width = (int)(xmax - xmin + 2.5), .height = (int)(ymax - ymin + 2.5) };
1439
1440 if(extent.width > 0 && extent.height > 0)
1441 {
1442 // copy params
1443 dt_iop_liquify_params_t copy_params;
1444 memcpy(&copy_params, params_in, sizeof(dt_iop_liquify_params_t));
1445
1446 if(!IS_NULL_PTR(chain))
1447 distort_paths_raw_to_piece_chain(self, chain, 1.f, &copy_params);
1448 else
1449 distort_paths_raw_to_piece(self, pipe, 1.f, &copy_params, TRUE);
1450
1451 // create the distortion map for this extent
1452
1453 GList *interpolated = interpolate_paths(&copy_params);
1454
1455 // we need to adjust the extent to be the union enclosing all the points (currently in extent) and
1456 // the warps that are in (possibly partly) in this same region.
1457
1458 dt_iop_roi_t roi_in = { .x = extent.x, .y = extent.y, .width = extent.width, .height = extent.height };
1459 GSList *interpolated_in_roi = _get_map_extent(&roi_in, interpolated, &extent);
1460
1461 float complex *map = create_global_distortion_map(&extent, interpolated_in_roi, inverted);
1462 g_slist_free(interpolated_in_roi);
1463 interpolated_in_roi = NULL;
1464 g_list_free_full(interpolated, dt_free_gpointer);
1465 interpolated = NULL;
1466
1467 if(IS_NULL_PTR(map)) return 0;
1468
1469 const int map_size = extent.width * extent.height;
1470 const int x_last = extent.x + extent.width;
1471 const int y_last = extent.y + extent.height;
1472
1473 // apply distortion to all points (this is a simple displacement given by a vector at this same point in the map)
1474 __OMP_PARALLEL_FOR__(if(points_count > 100))
1475 for(size_t i = 0; i < points_count; i++)
1476 {
1477 float *px = &points[i*2];
1478 float *py = &points[i*2+1];
1479 const float x = *px;
1480 const float y = *py;
1481 const int map_offset = ((int)(x - 0.5) - extent.x) + ((int)(y - 0.5) - extent.y) * extent.width;
1482
1483 if(x >= extent.x && x < x_last && y >= extent.y && y < y_last && map_offset >= 0 && map_offset < map_size)
1484 {
1485 const float complex dist = map[map_offset];
1486 *px += crealf(dist);
1487 *py += cimagf(dist);
1488 }
1489 }
1490
1492 }
1493
1494 return 1;
1495}
1496
1498{
1499 g->dragging.layer = layer;
1500 g->dragging.elem = elem;
1501}
1502
1504{
1505 g->dragging = NOWHERE;
1506}
1507
1509{
1510 return !IS_NULL_PTR(g->dragging.elem);
1511}
1512
1514 float *const restrict points, size_t points_count)
1515{
1516 // Recurse on the caller pipe so we reuse the same node list and piece data in preview, export,
1517 // thumbnail, and mask evaluation paths.
1518 return _liquify_warp_points((const dt_iop_liquify_params_t *)piece->data, self, pipe, NULL, points,
1519 points_count, TRUE);
1520}
1521
1523 float *const restrict points, size_t points_count)
1524{
1525 return _liquify_warp_points((const dt_iop_liquify_params_t *)piece->data, self, pipe, NULL, points,
1526 points_count, FALSE);
1527}
1528
1529/* --- the geometry service's view of this module (develop/geometry/geometry.h) ---------
1530 *
1531 * modify_roi_out() is the identity, so there is no size map: liquify moves pixels without
1532 * changing how many there are. What it does need is the one thing no other record needs -- the
1533 * chain composed around it. Its warps are stored in RAW sensor coordinates, so before it can
1534 * rasterise anything it has to push its own path nodes through every module upstream of itself,
1535 * which on the pixel pipe means re-entering the pipe walker mid-walk and here means re-entering
1536 * the chain (dt_geometry_chain_compose(), bounded BACK_EXCL of this module's own iop_order so
1537 * the recursion terminates).
1538 *
1539 * The cost is inherited, not introduced: each query rasterises a warp map over the extent of
1540 * the points it was given, so it is O(area), not O(points), exactly as the pipe's own
1541 * distort_transform() has always been.
1542 */
1543
1544static int _liquify_geometry_transform(const void *data, const dt_geometry_record_t *const record,
1545 dt_geometry_chain_t *chain, float *points, size_t points_count)
1546{
1547 const dt_iop_liquify_geometry_t *const g = (const dt_iop_liquify_geometry_t *)data;
1548 return _liquify_warp_points(&g->params, g->self, NULL, chain, points, points_count, TRUE);
1549}
1550
1551static int _liquify_geometry_backtransform(const void *data, const dt_geometry_record_t *const record,
1552 dt_geometry_chain_t *chain, float *points, size_t points_count)
1553{
1554 const dt_iop_liquify_geometry_t *const g = (const dt_iop_liquify_geometry_t *)data;
1555 return _liquify_warp_points(&g->params, g->self, NULL, chain, points, points_count, FALSE);
1556}
1557
1559 .map_size = NULL, // modify_roi_out() is the identity: liquify changes no dimensions
1560 .transform = _liquify_geometry_transform,
1561 .backtransform = _liquify_geometry_backtransform,
1562};
1563
1564gboolean geometry_record(struct dt_iop_module_t *self, const void *params, dt_geometry_record_t *record)
1565{
1568 if(IS_NULL_PTR(g)) return FALSE;
1569
1570 /* commit_params() is a straight copy of the parameters, so the record carries that copy. The
1571 * module pointer is kept because the fold needs its iop_order to bound the recursion and its
1572 * dev to reach the paths; it is the same module the chain is being built from, so it cannot
1573 * outlive the record. */
1574 memcpy(&g->params, params, sizeof(dt_iop_liquify_params_t));
1575 g->self = self;
1576
1577 record->data = g;
1578 record->free_data = dt_free_gpointer;
1580 return TRUE;
1581}
1582
1583void distort_mask(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe, struct dt_dev_pixelpipe_iop_t *piece,
1584 const float *const in, float *const out, const dt_iop_roi_t *const roi_in,
1585 const dt_iop_roi_t *const roi_out)
1586{
1587 (void)pipe;
1588 // 1. copy the whole image (we'll change only a small part of it)
1590 for(int i = 0; i < roi_out->height; i++)
1591 {
1592 float *destrow = out + (size_t) i * roi_out->width;
1593 const float *srcrow = in + (size_t) (roi_in->width * (i + roi_out->y - roi_in->y) + roi_out->x - roi_in->x);
1594
1595 memcpy(destrow, srcrow, sizeof(float) * roi_out->width);
1596 }
1597
1598 // 2. build the distortion map
1599
1600 cairo_rectangle_int_t map_extent;
1601 float complex *map = build_global_distortion_map(self, pipe, piece, roi_in, roi_out, &map_extent);
1602 if(IS_NULL_PTR(map))
1603 {
1604 if(map_extent.width != 0 && map_extent.height != 0) return;
1605 return;
1606 }
1607
1608 // 3. apply the map
1609
1610 if(map_extent.width != 0 && map_extent.height != 0)
1611 apply_global_distortion_map(self, piece, in, out, roi_in, roi_out, 1, map, &map_extent);
1612
1614
1615}
1616
1618int process(struct dt_iop_module_t *module, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece,
1619 const void *const in,
1620 void *const out)
1621{
1622 const dt_iop_roi_t *const roi_in = &piece->roi_in;
1623 const dt_iop_roi_t *const roi_out = &piece->roi_out;
1624 // 1. copy the whole image (we'll change only a small part of it)
1625
1626 const int ch = 4;
1627 const int height = MIN(roi_in->height, roi_out->height);
1628 const int width = MIN(roi_in->width, roi_out->width);
1630 for(int i = 0; i < height; i++)
1631 {
1632 float *destrow = (float *)out + (size_t)ch * i * roi_out->width;
1633 const float *srcrow = (float *)in + (size_t)ch * (roi_in->width * (i + roi_out->y - roi_in->y) +
1634 roi_out->x - roi_in->x);
1635
1636 memcpy(destrow, srcrow, sizeof(float) * ch * width);
1637 }
1638
1639 // 2. build the distortion map
1640
1641 cairo_rectangle_int_t map_extent;
1642 float complex *map = build_global_distortion_map(module, pipe, piece, roi_in, roi_out, &map_extent);
1643 if(IS_NULL_PTR(map))
1644 {
1645 if(map_extent.width != 0 && map_extent.height != 0) return 1;
1646 return 0;
1647 }
1648
1649 // 3. apply the map
1650
1651 if(map_extent.width != 0 && map_extent.height != 0)
1652 apply_global_distortion_map(module, piece, in, out, roi_in, roi_out, 1, map, &map_extent);
1653
1655
1656 return 0;
1657}
1658
1659#ifdef HAVE_OPENCL
1660
1661// compute bicubic kernel. See: https://en.wikipedia.org/wiki/Bicubic_interpolation#Bicubic_convolution_algorithm
1662
1663static inline float bicubic(const float a, const float x)
1664{
1665 const float absx = fabsf(x);
1666 if(absx <= 1) return ((a + 2) * absx - (a + 3)) * absx * absx + 1;
1667 if(absx < 2) return ((a * absx - 5 * a) * absx + 8 * a) * absx - 4 * a;
1668 return 0.0f;
1669}
1670
1671// compute Mitchell-Netravali cubic kernel (B=C=1/3): sharp but effectively
1672// halo-free, unlike Catmull-Rom bicubic and Lanczos which overshoot at edges.
1673
1674static inline float mitchell(const float x)
1675{
1676 const float absx = fabsf(x);
1677 const float x2 = absx * absx;
1678 const float x3 = x2 * absx;
1679 if(absx < 1.0f) return (7.0f / 6.0f) * x3 - 2.0f * x2 + 8.0f / 9.0f;
1680 if(absx < 2.0f) return -(7.0f / 18.0f) * x3 + 2.0f * x2 - (10.0f / 3.0f) * absx + 16.0f / 9.0f;
1681 return 0.0f;
1682}
1683
1684typedef struct
1685{
1686 int size;
1689
1690typedef cl_mem cl_mem_t;
1691typedef cl_int cl_int_t;
1692
1694 const dt_dev_pixelpipe_t *pipe,
1695 const dt_dev_pixelpipe_iop_t *piece,
1696 const cl_mem_t dev_in,
1697 const cl_mem_t dev_out,
1698 const dt_iop_roi_t *roi_in,
1699 const dt_iop_roi_t *roi_out,
1700 const float complex *map,
1701 const cairo_rectangle_int_t *map_extent)
1702{
1703 cl_int_t err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
1704
1706 const int devid = pipe->devid;
1707
1709 dt_liquify_kernel_descriptor_t kdesc = { .size = 0, .resolution = 100 };
1710 float *k = NULL;
1711
1712 switch (interpolation->id)
1713 {
1715 kdesc.size = 1;
1716 kdesc.resolution = 1;
1717 k = malloc(sizeof(float) * 2);
1718 if(k)
1719 {
1720 k[0] = 1.0f;
1721 k[1] = 0.0f;
1722 }
1723 break;
1725 kdesc.size = 2;
1726 k = malloc(sizeof(float) * ((size_t)kdesc.size * kdesc.resolution + 1));
1727 if(k)
1728 for(int i = 0; i <= kdesc.size * kdesc.resolution; ++i)
1729 k[i] = bicubic(0.5f, (float) i / kdesc.resolution);
1730 break;
1732 kdesc.size = 2;
1733 k = malloc(sizeof(float) * ((size_t)kdesc.size * kdesc.resolution + 1));
1734 if(k)
1735 for(int i = 0; i <= kdesc.size * kdesc.resolution; ++i)
1736 k[i] = mitchell((float) i / kdesc.resolution);
1737 break;
1738 default:
1739 return FALSE;
1740 }
1741
1743 (devid, sizeof(dt_iop_roi_t), (void *) roi_in);
1744
1746 (devid, sizeof(dt_iop_roi_t), (void *) roi_out);
1747
1749 (devid, sizeof(float complex) * map_extent->width * map_extent->height, (void *) map);
1750
1752 (devid, sizeof(cairo_rectangle_int_t), (void *) map_extent);
1753
1755 (devid, sizeof(dt_liquify_kernel_descriptor_t), (void *) &kdesc);
1756
1758 (devid, sizeof(float) * (kdesc.size * kdesc.resolution + 1), (void *) k);
1759
1760 if(IS_NULL_PTR(dev_roi_in) || IS_NULL_PTR(dev_roi_out) || IS_NULL_PTR(dev_map) || IS_NULL_PTR(dev_map_extent)
1761 || IS_NULL_PTR(dev_kdesc) || IS_NULL_PTR(dev_kernel) || IS_NULL_PTR(k))
1762 goto error;
1763
1764 dt_opencl_set_kernel_arg(devid, gd->warp_kernel, 0, sizeof(cl_mem), &dev_in);
1765 dt_opencl_set_kernel_arg(devid, gd->warp_kernel, 1, sizeof(cl_mem), &dev_out);
1766 dt_opencl_set_kernel_arg(devid, gd->warp_kernel, 2, sizeof(cl_mem), &dev_roi_in);
1767 dt_opencl_set_kernel_arg(devid, gd->warp_kernel, 3, sizeof(cl_mem), &dev_roi_out);
1768 dt_opencl_set_kernel_arg(devid, gd->warp_kernel, 4, sizeof(cl_mem), &dev_map);
1769 dt_opencl_set_kernel_arg(devid, gd->warp_kernel, 5, sizeof(cl_mem), &dev_map_extent);
1770
1771 dt_opencl_set_kernel_arg(devid, gd->warp_kernel, 6, sizeof(cl_mem), &dev_kdesc);
1772 dt_opencl_set_kernel_arg(devid, gd->warp_kernel, 7, sizeof(cl_mem), &dev_kernel);
1773
1774 const size_t sizes[] = { ROUNDUPDWD(map_extent->width, devid), ROUNDUPDHT(map_extent->height, devid) };
1775 err = dt_opencl_enqueue_kernel_2d(devid, gd->warp_kernel, sizes);
1776
1777error:
1778
1779 dt_opencl_release_mem_object(dev_kernel);
1781 dt_opencl_release_mem_object(dev_map_extent);
1783 dt_opencl_release_mem_object(dev_roi_out);
1784 dt_opencl_release_mem_object(dev_roi_in);
1785 dt_free(k);
1786
1787 return err;
1788}
1789
1790int process_cl(struct dt_iop_module_t *module, const dt_dev_pixelpipe_t *pipe,
1791 const dt_dev_pixelpipe_iop_t *piece,
1792 const cl_mem_t dev_in,
1793 const cl_mem_t dev_out)
1794{
1795 const dt_iop_roi_t *const roi_in = &piece->roi_in;
1796 const dt_iop_roi_t *const roi_out = &piece->roi_out;
1797 cl_int_t err = -999;
1798 const int devid = pipe->devid;
1799 const int height = MIN(roi_in->height, roi_out->height);
1800 const int width = MIN(roi_in->width, roi_out->width);
1801
1802 // 1. copy the whole image (we'll change only a small part of it)
1803 {
1804 size_t src[] = { roi_out->x - roi_in->x, roi_out->y - roi_in->y, 0 };
1805 size_t dest[] = { 0, 0, 0 };
1806 size_t extent[] = { width, height, 1 };
1807 err = dt_opencl_enqueue_copy_image(devid, dev_in, dev_out, src, dest, extent);
1808 if(err != CL_SUCCESS) goto error;
1809 }
1810
1811 // 2. build the distortion map
1812 cairo_rectangle_int_t map_extent;
1813 const float complex *map = build_global_distortion_map(module, pipe, piece, roi_in, roi_out, &map_extent);
1814 if(IS_NULL_PTR(map))
1815 {
1816 if(map_extent.width != 0 && map_extent.height != 0) return FALSE;
1817 return TRUE;
1818 }
1819
1820 // 3. apply the map
1821 if(map_extent.width != 0 && map_extent.height != 0)
1822 err = apply_global_distortion_map_cl(module, pipe, piece, dev_in, dev_out, roi_in, roi_out, map, &map_extent);
1824 if(err != CL_SUCCESS) goto error;
1825
1826 return TRUE;
1827
1828error:
1829 dt_print(DT_DEBUG_OPENCL, "[opencl_liquify] couldn't enqueue kernel! %d\n", err);
1830 return FALSE;
1831}
1832
1833#endif
1834
1836{
1837 // called once at startup
1838 const int program = 17; // from programs.conf
1840 module->data = gd;
1841 gd->warp_kernel = dt_opencl_create_kernel(program, "warp_kernel");
1842}
1843
1845{
1846 // called once at shutdown
1849 dt_free(module->data);
1850}
1851
1853{
1854 // module is disabled by default
1855 module->default_enabled = 0;
1856 module->params_size = sizeof(dt_iop_liquify_params_t);
1857
1858 // all allocated to 0, which is the default
1859 module->params = calloc(1, module->params_size);
1860 module->default_params = calloc(1, module->params_size);
1861}
1862
1864{
1866 piece->data_size = sizeof(dt_iop_liquify_params_t);
1867}
1868
1870{
1871 dt_free_align(piece->data);
1872 piece->data = NULL;
1873}
1874
1875/* commit is the synch point between core and gui, so it copies params to pipe data. */
1876
1877void commit_params(struct dt_iop_module_t *module,
1878 dt_iop_params_t *params,
1879 dt_dev_pixelpipe_t *pipe,
1881{
1882 memcpy(piece->data, params, module->params_size);
1883}
1884
1885// calculate the dot product of 2 vectors.
1886
1887static float cdot(const float complex p0, const float complex p1)
1888{
1889#ifdef FP_FAST_FMA
1890 return fma(crealf(p0), crealf(p1), cimagf(p0) * cimagf(p1));
1891#else
1892 return crealf(p0) * crealf(p1) + cimagf(p0) * cimagf(p1);
1893#endif
1894}
1895
1896static void draw_rectangle(cairo_t *cr, const float complex pt, const double theta, const double size)
1897{
1898 const double x = creal(pt), y = cimag(pt);
1899 cairo_save(cr);
1900 cairo_translate(cr, x, y);
1901 cairo_rotate(cr, theta);
1902 cairo_rectangle(cr, -size / 2.0, -size / 2.0, size, size);
1903 cairo_restore(cr);
1904}
1905
1906static void draw_triangle(cairo_t *cr, const float complex pt, const double theta, const double size)
1907{
1908 const double x = creal(pt), y = cimag(pt);
1909 cairo_save(cr);
1910 cairo_translate(cr, x, y);
1911 cairo_rotate(cr, theta);
1912 cairo_move_to(cr, -size, -size / 2.0);
1913 cairo_line_to(cr, 0, 0 );
1914 cairo_line_to(cr, -size, +size / 2.0);
1915 cairo_close_path(cr);
1916 cairo_restore(cr);
1917}
1918
1919static void draw_circle(cairo_t *cr, const float complex pt, const double diameter)
1920{
1921 const double x = creal(pt), y = cimag(pt);
1922 cairo_save(cr);
1923 cairo_new_sub_path(cr);
1924 cairo_arc(cr, x, y, diameter / 2.0, 0, 2 * DT_M_PI);
1925 cairo_restore(cr);
1926}
1927
1928static void set_source_rgba(cairo_t *cr, dt_liquify_rgba_t rgba)
1929{
1930 cairo_set_source_rgba(cr, rgba.red, rgba.green, rgba.blue, rgba.alpha);
1931}
1932
1933static float get_ui_width(const float scale, const dt_liquify_ui_width_enum_t w)
1934{
1935 assert(w >= 0 && w < DT_LIQUIFY_UI_WIDTH_LAST);
1936 return scale * DT_PIXEL_APPLY_DPI(dt_liquify_ui_widths[w]);
1937}
1938
1939#define GET_UI_WIDTH(a) (get_ui_width(scale, DT_LIQUIFY_UI_WIDTH_##a))
1940
1941static void set_line_width(cairo_t *cr, double scale, dt_liquify_ui_width_enum_t w)
1942{
1943 const double width = get_ui_width(scale, w);
1944 cairo_set_line_width(cr, width);
1945}
1946
1947static gboolean detect_drag(const dt_iop_liquify_gui_data_t *g, const double scale, const float complex pt)
1948{
1949 // g->last_button1_pressed_pos is valid only while BUTTON1 is down
1950 return g->last_button1_pressed_pos != -1.0 &&
1951 cabsf(pt - g->last_button1_pressed_pos) >= (GET_UI_WIDTH(MIN_DRAG) / scale);
1952}
1953
1955{
1956 guint warp = 0, node = 0;
1957 for(int k=0; k<MAX_NODES; k++)
1958 if(g->params.nodes[k].header.type == DT_LIQUIFY_PATH_INVALIDATED)
1959 break;
1960 else
1961 {
1962 node++;
1963 if(g->params.nodes[k].header.type == DT_LIQUIFY_PATH_MOVE_TO_V1)
1964 warp++;
1965 }
1966 char str_warp[10], str_node[20];
1967 snprintf(str_warp, sizeof(str_warp), "%d", warp);
1968 snprintf(str_node, sizeof(str_node), "%d", node);
1969 gtk_label_set_text(g->label_warp, str_warp);
1970 gtk_label_set_text(g->label_node, str_node);
1971}
1972
1974{
1975 GList *l = NULL;
1976 for(int k=0; k<MAX_NODES; k++)
1977 {
1978 const dt_liquify_path_data_t *data = &p->nodes[k];
1980 break;
1981
1982 const float complex *p2 = &data->warp.point;
1983 const dt_liquify_warp_t *warp2 = &data->warp;
1984
1986 {
1987 if(data->header.next == -1)
1988 {
1989 dt_liquify_warp_t *w = malloc(sizeof(dt_liquify_warp_t));
1990 *w = *warp2;
1991 l = g_list_append(l, w);
1992 }
1993 continue;
1994 }
1995
1996 const dt_liquify_path_data_t *prev = node_prev(p, data);
1997 const dt_liquify_warp_t *warp1 = &prev->warp;
1998 const float complex *p1 = &prev->warp.point;
2000 {
2001 const float total_length = cabsf(*p1 - *p2);
2002 float arc_length = 0.0f;
2003 while(arc_length < total_length)
2004 {
2005 dt_liquify_warp_t *w = malloc(sizeof(dt_liquify_warp_t));
2006 const float t = arc_length / total_length;
2007 const float complex pt = cmix(*p1, *p2, t);
2008 mix_warps(w, warp1, warp2, pt, t);
2010 arc_length += cabsf(w->radius - w->point) * STAMP_RELOCATION;
2011 l = g_list_append(l, w);
2012 }
2013 continue;
2014 }
2015
2017 {
2018 float complex *buffer = malloc(sizeof(float complex) * INTERPOLATION_POINTS);
2020 data->node.ctrl1,
2021 data->node.ctrl2,
2022 *p2,
2023 buffer,
2025 const float total_length = get_arc_length(buffer, INTERPOLATION_POINTS);
2026 float arc_length = 0.0f;
2027 restart_cookie_t restart = { 1, 0.0 };
2028
2029 while(arc_length < total_length)
2030 {
2031 dt_liquify_warp_t *w = malloc(sizeof(dt_liquify_warp_t));
2032 const float t = arc_length / total_length;
2033 const float complex pt = point_at_arc_length(buffer, INTERPOLATION_POINTS, arc_length, &restart);
2034 mix_warps(w, warp1, warp2, pt, t);
2036 arc_length += cabsf(w->radius - w->point) * STAMP_RELOCATION;
2037 l = g_list_append(l, w);
2038 }
2039 dt_free(buffer);
2040 continue;
2041 }
2042 }
2043 return l;
2044}
2045
2046#define FG_COLOR set_source_rgba(cr, fg_color)
2047#define BG_COLOR set_source_rgba(cr, bg_color)
2048#define VERYTHINLINE set_line_width (cr, scale / 2.0f, DT_LIQUIFY_UI_WIDTH_THINLINE)
2049#define THINLINE set_line_width (cr, scale, DT_LIQUIFY_UI_WIDTH_THINLINE)
2050#define THICKLINE set_line_width (cr, scale, DT_LIQUIFY_UI_WIDTH_THICKLINE)
2051
2052static void _draw_paths(dt_iop_module_t *module,
2053 cairo_t *cr,
2054 const float scale,
2056 GList *layers)
2057{
2059
2060 cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
2061
2062 // do not display any iterpolated items as slow when:
2063 // - we are dragging (pan)
2064 // - the button one is pressed
2065 // - exception for DT_LIQUIFY_LAYER_STRENGTHPOINT where we want to see the
2066 // interpolated strength lines.
2067 GList *interpolated = (is_dragging(g) || g->last_button1_pressed_pos != -1)
2068 && (g->last_hit.layer != DT_LIQUIFY_LAYER_STRENGTHPOINT)
2069 ? NULL
2071
2072 for(const GList *l = layers; l; l = g_list_next(l))
2073 {
2074 const dt_liquify_layer_enum_t layer = (dt_liquify_layer_enum_t) GPOINTER_TO_INT(l->data);
2075 dt_liquify_rgba_t fg_color = dt_liquify_layers[layer].fg;
2076 dt_liquify_rgba_t bg_color = dt_liquify_layers[layer].bg;
2077
2078 if(dt_liquify_layers[layer].opacity < 1.0)
2079 cairo_push_group(cr);
2080
2081 for(int k=0; k<MAX_NODES; k++)
2082 {
2083 // this is an empty bin, old invalidated node, nothing more to do
2084 if(p->nodes[k].header.type == DT_LIQUIFY_PATH_INVALIDATED)
2085 break;
2086
2087 dt_liquify_path_data_t *data = &p->nodes[k];
2088 const dt_liquify_path_data_t *prev = node_prev(p, data);
2089
2091 && !data->header.selected)
2092 continue;
2093
2095 && (IS_NULL_PTR(prev) || !prev->header.selected))
2096 continue;
2097
2098 fg_color = dt_liquify_layers[layer].fg;
2099 bg_color = dt_liquify_layers[layer].bg;
2100
2101 if(data->header.selected == layer)
2102 fg_color = DT_LIQUIFY_COLOR_SELECTED;
2103
2104 if(data->header.hovered == dt_liquify_layers[layer].hover_master)
2105 fg_color = DT_LIQUIFY_COLOR_HOVER;
2106
2107 cairo_new_path(cr);
2108
2109 const float complex point = data->warp.point;
2110
2112 cairo_move_to(cr, crealf(point), cimagf(point));
2113
2114 if(layer == DT_LIQUIFY_LAYER_RADIUS)
2115 {
2116 for(const GList *i = interpolated; i; i = g_list_next(i))
2117 {
2118 const dt_liquify_warp_t *pwarp = ((dt_liquify_warp_t *) i->data);
2119 draw_circle(cr, pwarp->point, 2.0f * cabsf(pwarp->radius - pwarp->point));
2120 }
2121 draw_circle(cr, point, 2.0f * cabsf(data->warp.radius - data->warp.point));
2122 FG_COLOR;
2123 cairo_fill(cr);
2124 }
2125 else if(layer == DT_LIQUIFY_LAYER_HARDNESS1)
2126 {
2127 for(const GList *i = interpolated; i; i = g_list_next(i))
2128 {
2129 const dt_liquify_warp_t *pwarp = ((dt_liquify_warp_t *) i->data);
2130 draw_circle(cr, pwarp->point, 2.0f * cabsf(pwarp->radius - pwarp->point) * pwarp->control1);
2131 }
2132 FG_COLOR;
2133 cairo_fill(cr);
2134 }
2135 else if(layer == DT_LIQUIFY_LAYER_HARDNESS2)
2136 {
2137 for(const GList *i = interpolated; i; i = g_list_next(i))
2138 {
2139 const dt_liquify_warp_t *pwarp = ((dt_liquify_warp_t *) i->data);
2140 draw_circle(cr, pwarp->point, 2.0f * cabsf(pwarp->radius - pwarp->point) * pwarp->control2);
2141 }
2142 FG_COLOR;
2143 cairo_fill(cr);
2144 }
2145 else if(layer == DT_LIQUIFY_LAYER_WARPS)
2146 {
2148 for(const GList *i = interpolated; i; i = g_list_next(i))
2149 {
2150 const dt_liquify_warp_t *pwarp = ((dt_liquify_warp_t *) i->data);
2151 cairo_move_to(cr, crealf(pwarp->point), cimagf(pwarp->point));
2152 cairo_line_to(cr, crealf(pwarp->strength), cimagf(pwarp->strength));
2153 }
2154 cairo_stroke(cr);
2155
2156 for(const GList *i = interpolated; i; i = g_list_next(i))
2157 {
2158 const dt_liquify_warp_t *pwarp = ((dt_liquify_warp_t *) i->data);
2159 const float rot = get_rot(pwarp->type);
2160 draw_circle(cr, pwarp->point, GET_UI_WIDTH(GIZMO_SMALL));
2161 draw_triangle(cr, pwarp->strength,
2162 cargf(pwarp->strength - pwarp->point) + rot,
2163 GET_UI_WIDTH(GIZMO_SMALL) / 3.0);
2164 }
2165 BG_COLOR;
2166 cairo_fill_preserve(cr);
2167 FG_COLOR;
2168 cairo_stroke(cr);
2169 }
2170 else if(layer == DT_LIQUIFY_LAYER_PATH)
2171 {
2174 {
2175 assert(prev);
2176 cairo_move_to(cr, crealf(prev->warp.point), cimagf(prev->warp.point));
2178 cairo_line_to(cr, crealf(point), cimagf(point));
2180 {
2181 cairo_curve_to(cr, crealf(data->node.ctrl1), cimagf(data->node.ctrl1),
2182 crealf(data->node.ctrl2), cimagf(data->node.ctrl2),
2183 crealf(point), cimagf(point));
2184 }
2186 cairo_stroke_preserve(cr);
2188 cairo_stroke(cr);
2189 }
2190 }
2191 else if(layer == DT_LIQUIFY_LAYER_CENTERPOINT)
2192 {
2196 {
2197 const float w = GET_UI_WIDTH(GIZMO);
2198 switch (data->header.node_type)
2199 {
2201 draw_triangle(cr, point - w / 2.0 * I, -DT_M_PI / 2.0, w);
2202 break;
2204 draw_rectangle(cr, point, DT_M_PI / 4.0, w);
2205 break;
2207 draw_rectangle(cr, point, 0, w);
2208 break;
2210 draw_circle(cr, point, w);
2211 break;
2212 default:
2213 break;
2214 }
2216 cairo_fill_preserve(cr);
2217 FG_COLOR;
2218 cairo_stroke(cr);
2219 }
2220 }
2221
2223 {
2226 {
2228 cairo_move_to(cr, crealf(prev->warp.point), cimagf(prev->warp.point));
2229 cairo_line_to(cr, crealf(data->node.ctrl1), cimagf(data->node.ctrl1));
2230 cairo_stroke(cr);
2231 }
2234 {
2236 cairo_move_to(cr, crealf(data->warp.point), cimagf(data->warp.point));
2237 cairo_line_to(cr, crealf(data->node.ctrl2), cimagf(data->node.ctrl2));
2238 cairo_stroke(cr);
2239 }
2240 if(layer == DT_LIQUIFY_LAYER_CTRLPOINT1 &&
2242 {
2244 draw_circle(cr, data->node.ctrl1, GET_UI_WIDTH(GIZMO_SMALL));
2245 cairo_fill_preserve(cr);
2246 FG_COLOR;
2247 cairo_stroke(cr);
2248 }
2249 if(layer == DT_LIQUIFY_LAYER_CTRLPOINT2 &&
2251 {
2253 draw_circle(cr, data->node.ctrl2, GET_UI_WIDTH(GIZMO_SMALL));
2254 cairo_fill_preserve(cr);
2255 FG_COLOR;
2256 cairo_stroke(cr);
2257 }
2258 }
2259
2260 const dt_liquify_warp_t *warp = &data->warp;
2261
2263 {
2264 draw_circle(cr, point, 2.0 * cabsf(warp->radius - point));
2266 cairo_stroke_preserve(cr);
2268 cairo_stroke(cr);
2269 }
2270
2271 if(layer == DT_LIQUIFY_LAYER_RADIUSPOINT)
2272 {
2274 draw_circle(cr, warp->radius, GET_UI_WIDTH(GIZMO_SMALL));
2275 cairo_fill_preserve(cr);
2276 FG_COLOR;
2277 cairo_stroke(cr);
2278 }
2279
2281 {
2282 draw_circle(cr, point, 2.0 * cabsf(warp->radius - point) * warp->control1);
2284 cairo_stroke_preserve(cr);
2286 cairo_stroke(cr);
2287 }
2288
2290 {
2291 draw_circle(cr, point, 2.0 * cabsf(warp->radius - point) * warp->control2);
2293 cairo_stroke_preserve(cr);
2295 cairo_stroke(cr);
2296 }
2297
2299 {
2300 draw_triangle(cr, cmix(point, warp->radius, warp->control1),
2301 cargf(warp->radius - point),
2302 GET_UI_WIDTH(GIZMO_SMALL));
2304 cairo_fill_preserve(cr);
2305 FG_COLOR;
2306 cairo_stroke(cr);
2307 }
2308
2310 {
2311 draw_triangle(cr, cmix(point, warp->radius, warp->control2),
2312 cargf(-(warp->radius - point)),
2313 GET_UI_WIDTH(GIZMO_SMALL));
2315 cairo_fill_preserve(cr);
2316 FG_COLOR;
2317 cairo_stroke(cr);
2318 }
2319
2321 {
2322 cairo_move_to(cr, crealf(point), cimagf(point));
2324 {
2325 const float complex pt = cmix(point, warp->strength,
2326 1.0 - 0.5
2327 * (GET_UI_WIDTH(GIZMO_SMALL)
2328 / cabsf(warp->strength - point)));
2329 cairo_line_to(cr, crealf(pt), cimagf(pt));
2330 }
2331 else
2332 draw_circle(cr, point, 2.0 * cabsf(warp->strength - warp->point));
2334 cairo_stroke_preserve(cr);
2336 cairo_stroke(cr);
2337 }
2338
2340 {
2341 cairo_move_to(cr, crealf(warp->strength), cimagf(warp->strength));
2342 const float rot = get_rot(warp->type);
2343 draw_triangle(cr, warp->strength,
2344 cargf(warp->strength - warp->point) + rot,
2345 GET_UI_WIDTH(GIZMO_SMALL));
2347 cairo_fill_preserve(cr);
2348 FG_COLOR;
2349 cairo_stroke(cr);
2350 }
2351 }
2352
2353 if(dt_liquify_layers[layer].opacity < 1.0)
2354 {
2355 cairo_pop_group_to_source(cr);
2356 cairo_paint_with_alpha(cr, dt_liquify_layers[layer].opacity);
2357 }
2358 }
2359
2360 g_list_free_full(interpolated, dt_free_gpointer);
2361 interpolated = NULL;
2362}
2363
2364/*
2365 Find the nearest point on a cubic bezier curve.
2366
2367 Return the curve parameter t of the point on a cubic bezier curve
2368 that is nearest to another arbitrary point. Uses interpolation.
2369
2370 FIXME: Implement a faster method, see:
2371 http://tog.acm.org/resources/GraphicsGems/gems/NearestPoint.c
2372*/
2373
2374static float find_nearest_on_curve_t(const float complex p0,
2375 const float complex p1,
2376 const float complex p2,
2377 const float complex p3,
2378 const float complex x,
2379 const int n)
2380{
2381 float min_t = 0.0f, min_dist = cabsf(x - p0);
2382
2383 for(int i = 0; i < n; i++)
2384 {
2385 const float t = (1.0 * i) / n;
2386 const float t1 = 1.0 - t;
2387 const float complex ip =
2388 t1 * t1 * t1 * p0 +
2389 3 * t1 * t1 * t * p1 +
2390 3 * t1 * t * t * p2 +
2391 t * t * t * p3;
2392
2393 const float dist = cabsf(x - ip);
2394 if(dist < min_dist)
2395 {
2396 min_dist = dist;
2397 min_t = t;
2398 }
2399 }
2400 return min_t;
2401}
2402
2403/*
2404 Find the nearest point on a line.
2405
2406 Return the line parameter t of the point on a line that is nearest
2407 to another arbitrary point.
2408*/
2409
2410static float find_nearest_on_line_t(const float complex p0, const float complex p1, const float complex x)
2411{
2412 // scalar projection
2413 const float b = cabsf(p1 - p0); // |b|
2414 const float dotab = cdot(x - p0, p1 - p0); // |a| * |b| * cos(phi)
2415 return dotab / (b * b); // |a| / |b| * cos(phi)
2416}
2417
2418// split a cubic bezier at t into two cubic beziers.
2419
2420static void casteljau(const float complex *p0, float complex *p1, float complex *p2, float complex *p3, const float t)
2421{
2422 const float complex p01 = *p0 + (*p1 - *p0) * t;
2423 const float complex p12 = *p1 + (*p2 - *p1) * t;
2424 const float complex p23 = *p2 + (*p3 - *p2) * t;
2425
2426 const float complex p012 = p01 + (p12 - p01) * t;
2427 const float complex p123 = p12 + (p23 - p12) * t;
2428
2429 const float complex p0123 = p012 + (p123 - p012) * t;
2430
2431 *p1 = p01;
2432 *p2 = p012;
2433 *p3 = p0123;
2434}
2435
2436#define CHECK_HIT_PT(point) \
2437 const float d = cabsf(point - (*pt)); \
2438 if(d < distance) \
2439 { \
2440 distance = d; \
2441 hit.layer = layer; \
2442 hit.elem = data; \
2443 }
2444
2447 GList *layers,
2448 const float complex *pt)
2449{
2451
2452 float distance = FLT_MAX;
2453
2454 for(const GList *l = layers; l; l = g_list_next(l))
2455 {
2456 const dt_liquify_layer_enum_t layer = (dt_liquify_layer_enum_t) GPOINTER_TO_INT(l->data);
2457
2459 continue;
2460
2461 for(int k=0; k<MAX_NODES; k++)
2462 {
2463 dt_liquify_path_data_t *data = &p->nodes[k];
2464 const dt_liquify_path_data_t *prev = node_prev(p, data);
2465
2466 if(p->nodes[k].header.type == DT_LIQUIFY_PATH_INVALIDATED)
2467 break;
2468
2470 && !data->header.selected)
2471 continue;
2472
2474 && (IS_NULL_PTR(prev) || !prev->header.selected))
2475 continue;
2476
2477 const dt_liquify_warp_t *warp = &data->warp;
2478 const float complex point = data->warp.point;
2479
2480 if(layer == DT_LIQUIFY_LAYER_PATH)
2481 {
2483 {
2484 // remove 5% from start and end of line as non sensible area
2485 // this is to avoid wrong interaction for center point on both
2486 // sides.
2487 const float complex deadzone = (point - prev->warp.point) / 20.0f;
2488 const float complex lp1 = prev->warp.point + deadzone;
2489 const float complex lp2 = point - deadzone;
2490 const float t = find_nearest_on_line_t(lp1, lp2, *pt);
2491
2492 if(t > 0.0f && t < 1.0f)
2493 {
2494 const float complex linepoint = cmix(lp1, lp2, t);
2495 const float d = cabsf(linepoint - *pt);
2496 if(d < distance)
2497 {
2498 distance = d;
2499 hit.layer = layer;
2500 hit.elem = data;
2501 }
2502 }
2503 }
2504 else if(data->header.type == DT_LIQUIFY_PATH_CURVE_TO_V1)
2505 {
2506 // remove 5% from start and end of line as non sensible area
2507 // this is to avoid wrong interaction for center point on both
2508 // sides.
2509 const float complex deadzone = (point - prev->warp.point) / 20.0f;
2510 const float complex lp1 = prev->warp.point + deadzone;
2511 const float complex lp2 = point - deadzone;
2512 const float t = find_nearest_on_curve_t(lp1, data->node.ctrl1, data->node.ctrl2, lp2, *pt, INTERPOLATION_POINTS);
2513
2514 if(t > 0.0f && t < 1.0f)
2515 {
2516 float complex curvepoint = lp2;
2517 float complex p1 = data->node.ctrl1;
2518 float complex p2 = data->node.ctrl2;
2519 casteljau(&lp1, &p1, &p2, &curvepoint, t);
2520
2521 const float d = cabsf(curvepoint - *pt);
2522 if(d < distance)
2523 {
2524 distance = d;
2525 hit.layer = layer;
2526 hit.elem = data;
2527 }
2528 }
2529 }
2530 }
2531 else if(layer == DT_LIQUIFY_LAYER_CENTERPOINT)
2532 {
2536 {
2538 }
2539 }
2540 else if(layer == DT_LIQUIFY_LAYER_RADIUSPOINT)
2541 {
2542 CHECK_HIT_PT(warp->radius);
2543 }
2544 else if(layer == DT_LIQUIFY_LAYER_HARDNESSPOINT1)
2545 {
2546 CHECK_HIT_PT(cmix(point, warp->radius, warp->control1));
2547 }
2548 else if(layer == DT_LIQUIFY_LAYER_HARDNESSPOINT2)
2549 {
2550 CHECK_HIT_PT(cmix(point, warp->radius, warp->control2));
2551 }
2552 else if(layer == DT_LIQUIFY_LAYER_STRENGTHPOINT)
2553 {
2554 const float complex p = warp->point - warp->strength;
2555 CHECK_HIT_PT(warp->strength + (float)DT_PIXEL_APPLY_DPI(5) * (p / cabsf(p)));
2556 }
2557
2559 {
2560 if(layer == DT_LIQUIFY_LAYER_CTRLPOINT1 &&
2562 {
2563 CHECK_HIT_PT(data->node.ctrl1);
2564 }
2565 if(layer == DT_LIQUIFY_LAYER_CTRLPOINT2 &&
2567 {
2568 CHECK_HIT_PT(data->node.ctrl2);
2569 }
2570 }
2571 }
2572 }
2573
2574 if(distance < DT_GUI_MOUSE_EFFECT_RADIUS * 0.5)
2575 return hit;
2576 else
2577 return NOWHERE;
2578}
2579
2580static void draw_paths(struct dt_iop_module_t *module, cairo_t *cr, const float scale, dt_iop_liquify_params_t *params)
2581{
2583 GList *layers = NULL;
2584
2585 for(dt_liquify_layer_enum_t layer = 0; layer < DT_LIQUIFY_LAYER_LAST; ++layer)
2586 {
2587 if(gtk_toggle_button_get_active(g->btn_point_tool)
2589 layers = g_list_prepend(layers, GINT_TO_POINTER(layer));
2590 if(gtk_toggle_button_get_active(g->btn_line_tool)
2592 layers = g_list_prepend(layers, GINT_TO_POINTER(layer));
2593 if(gtk_toggle_button_get_active(g->btn_curve_tool)
2595 layers = g_list_prepend(layers, GINT_TO_POINTER(layer));
2596 if(gtk_toggle_button_get_active(g->btn_node_tool)
2598 layers = g_list_prepend(layers, GINT_TO_POINTER(layer));
2599 }
2600 layers = g_list_reverse(layers); // list was built in reverse order, so un-reverse it
2601
2602 _draw_paths(module, cr, scale, params, layers);
2603
2604 g_list_free(layers);
2605 layers = NULL;
2606}
2607
2610 float complex pt)
2611{
2613 GList *layers = NULL;
2614
2615 for(dt_liquify_layer_enum_t layer = 0; layer < DT_LIQUIFY_LAYER_LAST; ++layer)
2616 {
2618 layers = g_list_prepend(layers, GINT_TO_POINTER(layer));
2619 }
2620 layers = g_list_reverse(layers); // list was built in reverse order, so un-reverse it
2621
2622 hit = _hit_paths(module, params, layers, &pt);
2623 g_list_free(layers);
2624 layers = NULL;
2625 return hit;
2626}
2627
2682static void smooth_path_linsys(size_t n,
2683 const float complex *k,
2684 float complex *c1,
2685 float complex *c2,
2686 const int *equation)
2687{
2688 --n;
2689 float *a = malloc(sizeof(float) * n); // subdiagonal
2690 float *b = malloc(sizeof(float) * n); // main diagonal
2691 float *c = malloc(sizeof(float) * n); // superdiagonal
2692 float complex *d = malloc(sizeof(float complex) * n); // right hand side
2693
2694 // Build the tridiagonal matrix.
2695
2696 for(int i = 0; i < n; i++)
2697 {
2698 switch (equation[i])
2699 {
2700 #define ABCD(A,B,C,D) { { a[i] = A; b[i] = B; c[i] = C; d[i] = D; continue; } }
2701 case 1: ABCD(0, 2, 1, k[i] + 2 * k[i+1] ); break;
2702 case 2: ABCD(1, 4, 1, 4 * k[i] + 2 * k[i+1] ); break;
2703 case 3: ABCD(2, 7, 0, 8 * k[i] + k[i+1] ); break;
2704 case 4: ABCD(0, 1, 0, c1[i] ); break;
2705 case 5: ABCD(0, 1, 0, c1[i] ); break;
2706 case 6: ABCD(1, 4, 0, 4 * k[i] + c2[i] ); break;
2707 case 7: ABCD(0, 1, 0, c1[i] ); break;
2708 case 8: ABCD(0, 3, 0, 2 * k[i] + k[i+1] ); break;
2709 case 9: ABCD(0, 2, 0, k[i] + c2[i] ); break;
2710 #undef ABCD
2711 }
2712 }
2713
2714 // Solve with the Thomas algorithm to compute c1's. See:
2715 // http://en.wikipedia.org/wiki/Tridiagonal_matrix_algorithm
2716
2717 for(int i = 1; i < n; i++)
2718 {
2719 const float m = a[i] / b[i-1];
2720 b[i] = b[i] - m * c[i-1];
2721 d[i] = d[i] - m * d[i-1];
2722 }
2723
2724 c1[n-1] = d[n-1] / b[n-1];
2725 for(int i = n - 2; i >= 0; i--)
2726 c1[i] = (d[i] - c[i] * c1[i+1]) / b[i];
2727
2728 // Now compute the c2's.
2729
2730 for(int i = 0; i < n; i++)
2731 {
2732 switch (equation[i])
2733 {
2734 // keep end: c2 does not change
2735 case 5:
2736 case 6:
2737 case 9: break;
2738
2739 // straight end: put c2[i] halfway between c1[i] and k[i+1]
2740 case 3:
2741 case 7:
2742 case 8: c2[i] = (c1[i] + k[i+1]) / 2; break;
2743
2744 // smooth end: c2 and c1 are symmetrical around the knot
2745 default: c2[i] = 2 * k[i+1] - c1[i+1];
2746 }
2747 }
2748
2749 dt_free(a);
2750 dt_free(b);
2751 dt_free(c);
2752 dt_free(d);
2753}
2754
2756{
2757 int count = 1;
2758 while(n->header.next != -1)
2759 {
2760 count++;
2761 n = &p->nodes[n->header.next];
2762 }
2763 return count;
2764}
2765
2767{
2768 for(int k = 0; k < MAX_NODES; k++)
2769 {
2770 if(params->nodes[k].header.type == DT_LIQUIFY_PATH_INVALIDATED)
2771 break;
2772
2773 if(params->nodes[k].header.prev != -1)
2774 continue;
2775
2776 dt_liquify_path_data_t *node = &params->nodes[k];
2777
2778 const size_t n = path_length(params, node);
2779
2780 if(n < 2)
2781 continue;
2782
2783 float complex *pt = calloc(n, sizeof(float complex));
2784 float complex *c1 = calloc(n, sizeof(float complex));
2785 float complex *c2 = calloc(n, sizeof(float complex));
2786 int *eqn = calloc(n, sizeof(int));
2787 size_t idx = 0;
2788
2789 while(node)
2790 {
2792 const dt_liquify_path_data_t *p = node_prev(params, node);
2793 const dt_liquify_path_data_t *n = node_next(params, node);
2794 const dt_liquify_path_data_t *nn = n ? node_next(params, n) : NULL;
2795
2796 pt[idx] = node->warp.point;
2797 if(d->header.type == DT_LIQUIFY_PATH_CURVE_TO_V1)
2798 {
2799 c1[idx-1] = d->node.ctrl1;
2800 c2[idx-1] = d->node.ctrl2;
2801 }
2802
2803 const int autosmooth = d->header.node_type == DT_LIQUIFY_NODE_TYPE_AUTOSMOOTH;
2804 const int next_autosmooth = n && n->header.node_type == DT_LIQUIFY_NODE_TYPE_AUTOSMOOTH;
2805 const int firstseg = !p || d->header.type != DT_LIQUIFY_PATH_CURVE_TO_V1;
2806 const int lastseg = !nn || nn->header.type != DT_LIQUIFY_PATH_CURVE_TO_V1;
2807 const int lineseg = n && n->header.type == DT_LIQUIFY_PATH_LINE_TO_V1;
2808
2809 // Program the linear system with equations:
2810 //
2811 // START END
2812 // --------------------------
2813 // 1: straight smooth
2814 // 2: smooth smooth
2815 // 3: smooth straight
2816 // 4: keep smooth
2817 // 5: keep keep
2818 // 6: smooth keep
2819 // 7: keep straight
2820 // 8: straight straight (== line)
2821 // 9: straight keep
2822
2823 if(lineseg) eqn[idx] = 5;
2824 else if(!autosmooth && !next_autosmooth) eqn[idx] = 5;
2825 else if(firstseg && lastseg && !autosmooth && next_autosmooth) eqn[idx] = 7;
2826 else if(firstseg && lastseg && autosmooth && next_autosmooth) eqn[idx] = 8;
2827 else if(firstseg && lastseg && autosmooth && !next_autosmooth) eqn[idx] = 9;
2828 else if(firstseg && autosmooth && !next_autosmooth) eqn[idx] = 5;
2829 else if(firstseg && autosmooth) eqn[idx] = 1;
2830 else if(lastseg && autosmooth && next_autosmooth) eqn[idx] = 3;
2831 else if(lastseg && !autosmooth && next_autosmooth) eqn[idx] = 7;
2832 else if(autosmooth && !next_autosmooth) eqn[idx] = 6;
2833 else if(!autosmooth && next_autosmooth) eqn[idx] = 4;
2834 else eqn[idx] = 2;
2835
2836 ++idx;
2837 node = node_next(params, node);
2838 }
2839
2840 smooth_path_linsys(n, pt, c1, c2, eqn);
2841
2842 // write calculated control points back to list structure
2843 node = &params->nodes[k];
2844 node = node_next(params, node);
2845 idx = 0;
2846 while(node)
2847 {
2849 if(d->header.type == DT_LIQUIFY_PATH_CURVE_TO_V1)
2850 {
2851 d->node.ctrl1 = c1[idx];
2852 d->node.ctrl2 = c2[idx];
2853 }
2854 ++idx;
2855 node = node_next(params, node);
2856 }
2857
2858 dt_free(pt);
2859 dt_free(c1);
2860 dt_free(c2);
2861 dt_free(eqn);
2862 }
2863}
2864
2866{
2867 for(int k=0; k<MAX_NODES; k++)
2868 if(p->nodes[k].header.type == DT_LIQUIFY_PATH_INVALIDATED)
2869 break;
2870 else if(p->nodes[k].header.hovered)
2871 return &p->nodes[k];
2872 return NULL;
2873}
2874
2875static void init_warp(dt_liquify_warp_t *warp, float complex point)
2876{
2878 warp->point = point;
2879 warp->radius = point;
2880 warp->strength = point;
2881 warp->control1 = 0.5;
2882 warp->control2 = 0.75;
2884}
2885
2886static dt_liquify_path_data_t *alloc_move_to(dt_iop_module_t *module, float complex start_point)
2887{
2889 dt_liquify_path_data_t* m = (dt_liquify_path_data_t*)node_alloc(&g->params, &g->node_index);
2890 if(m)
2891 {
2893 m->header.node_type = DT_LIQUIFY_NODE_TYPE_AUTOSMOOTH;
2894 init_warp(&m->warp, start_point);
2895 }
2896 return (dt_liquify_path_data_t *)m;
2897}
2898
2899static dt_liquify_path_data_t *alloc_line_to(dt_iop_module_t *module, float complex end_point)
2900{
2902 dt_liquify_path_data_t* l = (dt_liquify_path_data_t*)node_alloc(&g->params, &g->node_index);
2903 if(!IS_NULL_PTR(l))
2904 {
2907 init_warp(&l->warp, end_point);
2908 }
2909 return (dt_liquify_path_data_t *)l;
2910}
2911
2912static dt_liquify_path_data_t *alloc_curve_to(dt_iop_module_t *module, float complex end_point)
2913{
2915 dt_liquify_path_data_t* c = (dt_liquify_path_data_t*)node_alloc(&g->params, &g->node_index);
2916 if(!IS_NULL_PTR(c))
2917 {
2918 c->header.type = DT_LIQUIFY_PATH_CURVE_TO_V1;
2919 c->header.node_type = DT_LIQUIFY_NODE_TYPE_AUTOSMOOTH;
2920 c->node.ctrl1 = c->node.ctrl2 = 0.0;
2921 init_warp(&c->warp, end_point);
2922 }
2923 return (dt_liquify_path_data_t *)c;
2924}
2925
2927{
2928 for(int k=0; k<MAX_NODES; k++)
2929 if(p->nodes[k].header.type == DT_LIQUIFY_PATH_INVALIDATED)
2930 break;
2931 else
2932 p->nodes[k].header.selected = 0;
2933}
2934
2935static float get_zoom_scale(const dt_develop_t *develop)
2936{
2937 return dt_dev_get_zoom_scale(develop, 1);
2938}
2939
2941 cairo_t *cr,
2942 int32_t width,
2943 int32_t height,
2944 int32_t pointerx,
2945 int32_t pointery)
2946{
2947 if(IS_NULL_PTR(module))
2948 return;
2949 dt_develop_t *develop = module->dev;
2950 if(IS_NULL_PTR(develop))
2951 return;
2953 if(IS_NULL_PTR(g))
2954 return;
2955
2957 const float bb_width = geometry.processed_width;
2958 const float bb_height = geometry.processed_height;
2959 if(bb_width < 1.0 || bb_height < 1.0)
2960 return;
2961
2962 // get a copy of all iop params
2965 smooth_paths_linsys(&g->params);
2966 dt_iop_liquify_params_t copy_params;
2967 memcpy(&copy_params, &g->params, sizeof(dt_iop_liquify_params_t));
2969
2970 /* Distort all points, through the geometry service. Nothing is resynchronised first: the
2971 * chain is rebuilt wherever a pipe flag is raised, and this fold excludes the module itself
2972 * (BACK_EXCL then FORW_EXCL), so the live params being dragged are not what it reads. If the
2973 * chain cannot answer, its compose leaves the points in RAW coordinates -- which would draw
2974 * the whole path in the wrong place -- so draw nothing instead. */
2976 return;
2977
2978 const distort_params_t d_params = { develop, NULL, develop->geometry_chain, 1.0, 1.0,
2980 _distort_paths(module, &d_params, &copy_params);
2981
2982 // You're not supposed to understand this
2983 const float zoom_scale = get_zoom_scale(develop) * dt_dev_viewport_scaling(develop);
2984
2985 if(dt_dev_rescale_roi_to_input(develop, cr, width, height))
2986 return;
2987
2988 draw_paths(module, cr, 1.0 / zoom_scale, &copy_params);
2989}
2990
2991static gboolean btn_make_radio_callback(GtkToggleButton *btn, GdkEventButton *event, dt_iop_module_t *module);
2992
2993void gui_focus(struct dt_iop_module_t *module, gboolean in)
2994{
2995 if(!in)
2996 {
2998 btn_make_radio_callback(NULL, NULL, module);
2999 }
3000}
3001
3002static void sync_pipe(struct dt_iop_module_t *module, gboolean history)
3003{
3004 if(history)
3005 {
3007 // something definitive has happened like button release ... so
3008 // redraw pipe
3009 memcpy(module->params, &g->params, sizeof(dt_iop_liquify_params_t));
3010 dt_dev_add_history_item(module->dev, module, TRUE, TRUE);
3011 }
3012 else
3013 {
3014 // only moving mouse around, pointing at things or dragging ... so
3015 // give some cairo feedback, but don't redraw pipe
3017 }
3018}
3019
3020/*
3021 right-click on node: Delete node.
3022 right-click on path: Delete whole path.
3023
3024 ctrl+click on node: Cycle symmetrical, smooth, cusp, autosmooth
3025 ctrl+click on path: Add node
3026 ctrl+alt+click on path: Change line / bezier
3027
3028 ctrl+click on strength: Cycle linear, grow, shrink
3029*/
3030
3031static void get_point_scale(struct dt_iop_module_t *module, float x, float y, float complex *pt, float *scale)
3032{
3033 float pts[2] = { (float)x, (float)y };
3040
3041 *scale = get_zoom_scale(module->dev);
3042 *pt = pts[0] + pts[1] * I;
3043}
3044
3045static gboolean _is_movable_layer(const dt_liquify_layer_enum_t layer)
3046{
3047 switch(layer)
3048 {
3057 return TRUE;
3058 default:
3059 return FALSE;
3060 }
3061}
3062
3063int mouse_moved(struct dt_iop_module_t *module,
3064 double x,
3065 double y,
3066 double pressure,
3067 int which)
3068{
3070 gboolean handled = FALSE;
3071 float complex pt = 0.0f;
3072 float scale = 0.0f;
3073
3074 get_point_scale(module, x, y, &pt, &scale);
3075
3077
3078 const float complex prev_mouse_pos = g->last_mouse_pos;
3079 g->last_mouse_pos = pt;
3080
3081 // Don't hit test while dragging, you'd only hit the dragged thing
3082 // anyway.
3083
3084 if(!is_dragging(g))
3085 {
3086 dt_liquify_hit_t hit = _hit_test_paths(module, &g->params, pt);
3087 // Only override the cursor for a movable layer -- otherwise leave darkroom's own default
3088 // cursor (dot/crosshair/left_ptr, already queued per position by
3089 // _darkroom_set_default_cursor before this handler runs) instead of forcing "default".
3090 if(_is_movable_layer(hit.layer))
3092 dt_liquify_path_data_t *last_hovered = find_hovered(&g->params);
3093 if(hit.elem != last_hovered
3094 || (!IS_NULL_PTR(last_hovered) && !IS_NULL_PTR(hit.elem)
3095 && hit.elem->header.hovered != last_hovered->header.hovered))
3096 {
3097 if(!IS_NULL_PTR(hit.elem))
3098 hit.elem->header.hovered = hit.layer;
3099 if(!IS_NULL_PTR(last_hovered))
3100 last_hovered->header.hovered = 0;
3101 // change in hover display
3103 handled = TRUE;
3104 goto done;
3105 }
3106
3107 const gboolean dragged = detect_drag(g, scale, pt);
3108
3109 if(dragged && !IS_NULL_PTR(g->last_hit.elem))
3110 {
3111 // start dragging
3112 start_drag(g, g->last_hit.layer, g->last_hit.elem);
3113 // nothing more to do, we will refresh on the next call anyway
3114 // this makes the initial move of a node a bit more fluid.
3115 handled = TRUE;
3116 goto done;
3117 }
3118
3119 if(!IS_NULL_PTR(g->last_hit.elem))
3120 {
3121 // an item is selected, so this mouvement is handled and must
3122 // not trigger any panning.
3123 handled = TRUE;
3124 }
3125 }
3126 else // we are dragging
3127 {
3129 dt_liquify_path_data_t *d = g->dragging.elem;
3130 dt_liquify_path_data_t *n = node_next(&g->params, d);
3131 dt_liquify_path_data_t *p = node_prev(&g->params, d);
3132
3133 const float complex *start_pt = &d->warp.point;
3134
3135 switch (g->dragging.layer)
3136 {
3138 {
3139 if(IS_NULL_PTR(p)) break;
3140 const float complex delta = prev_mouse_pos == -1 ? 0.0f : pt - prev_mouse_pos;
3141 if(cabsf(delta) < 1e-6f) break;
3142
3143 dt_liquify_path_data_t *moved_nodes[2] = { p, d };
3144 for(int i = 0; i < 2; i++)
3145 {
3146 dt_liquify_path_data_t *node = moved_nodes[i];
3147 dt_liquify_path_data_t *node_next_ptr = node_next(&g->params, node);
3148 dt_liquify_path_data_t *node_prev_ptr = node_prev(&g->params, node);
3149
3151 node->node.ctrl2 += delta;
3152 if(!IS_NULL_PTR(node_next_ptr) && node_next_ptr->header.type == DT_LIQUIFY_PATH_CURVE_TO_V1)
3153 node_next_ptr->node.ctrl1 += delta;
3154 if(!IS_NULL_PTR(node_prev_ptr) && node_prev_ptr->header.type == DT_LIQUIFY_PATH_CURVE_TO_V1)
3155 node_prev_ptr->node.ctrl2 += delta;
3156
3157 node->warp.radius += delta;
3158 node->warp.strength += delta;
3159 node->warp.point += delta;
3160 }
3161 break;
3162 }
3163
3165 switch (d->header.type)
3166 {
3168 d->node.ctrl2 += pt - d->warp.point;
3169 // fall thru
3172 if(!IS_NULL_PTR(n) && n->header.type == DT_LIQUIFY_PATH_CURVE_TO_V1)
3173 n->node.ctrl1 += pt - d->warp.point;
3174 if(!IS_NULL_PTR(p) && p->header.type == DT_LIQUIFY_PATH_CURVE_TO_V1)
3175 p->node.ctrl2 += pt - d->warp.point;
3176 d->warp.radius += pt - d->warp.point;
3177 d->warp.strength += pt - d->warp.point;
3178 d->warp.point = pt;
3179 break;
3180 default:
3181 break;
3182 }
3183 break;
3184
3186 switch (d->header.type)
3187 {
3189 d->node.ctrl1 = pt;
3190 if(!IS_NULL_PTR(p) && p->header.type == DT_LIQUIFY_PATH_CURVE_TO_V1)
3191 {
3192 switch (p->header.node_type)
3193 {
3195 p->node.ctrl2 = p->warp.point +
3196 cabsf(p->warp.point - p->node.ctrl2) *
3197 cexpf(cargf(p->warp.point - pt) * I);
3198 break;
3200 p->node.ctrl2 = 2 * p->warp.point - pt;
3201 break;
3202 default:
3203 break;
3204 }
3205 }
3206 break;
3207 default:
3208 break;
3209 }
3210 break;
3211
3213 switch (d->header.type)
3214 {
3216 d->node.ctrl2 = pt;
3217 if(!IS_NULL_PTR(n) && n->header.type == DT_LIQUIFY_PATH_CURVE_TO_V1)
3218 {
3219 switch (d->header.node_type)
3220 {
3222 n->node.ctrl1 = d->warp.point +
3223 cabsf(d->warp.point - n->node.ctrl1) *
3224 cexpf(cargf(d->warp.point - pt) * I);
3225 break;
3227 n->node.ctrl1 = 2 * d->warp.point - pt;
3228 break;
3229 default:
3230 break;
3231 }
3232 }
3233 break;
3234 default:
3235 break;
3236 }
3237 break;
3238
3240 d->warp.radius = pt;
3241 dt_conf_set_float(CONF_RADIUS, cabsf(d->warp.radius - d->warp.point));
3242 break;
3243
3245 d->warp.strength = pt;
3246 dt_conf_set_float(CONF_STRENGTH, cabsf(d->warp.strength - d->warp.point));
3247 dt_conf_set_float(CONF_ANGLE, cargf(d->warp.strength - d->warp.point));
3248 break;
3249
3251 d->warp.control1 = MIN(1.0, cabsf(pt - *start_pt) / cabsf(d->warp.radius - *start_pt));
3252 break;
3253
3255 d->warp.control2 = MIN(1.0, cabsf(pt - *start_pt) / cabsf(d->warp.radius - *start_pt));
3256 break;
3257
3258 default:
3259 break;
3260 }
3261 handled = TRUE;
3262 }
3263
3264done:
3265 if(!handled && which != 0 && (!IS_NULL_PTR(g->temp) || !IS_NULL_PTR(g->last_hit.elem)))
3266 {
3267 // A drag/edit sequence is in progress, keep consuming motion events
3268 // so darkroom pan does not steal the interaction.
3269 handled = TRUE;
3270 }
3272 if(handled)
3273 {
3274 sync_pipe(module, FALSE);
3275 }
3276 return handled;
3277}
3278
3279static float dt_conf_get_sanitize_float(const char *name, float min, float max, float default_value)
3280{
3281 const float value = dt_conf_get_float(name);
3282 float new_value = CLAMP(value, min, max);
3283
3284 if (default_value != 0.0f && new_value != value) new_value = 0.25f * default_value + 0.75f * value;
3285
3286 dt_conf_set_float(name, new_value);
3287 return new_value;
3288}
3289
3290static void get_stamp_params(dt_iop_module_t *module, float *radius, float *r_strength, float *phi)
3291{
3292 GtkWidget *widget = dt_gui_main_window();
3293 GtkAllocation allocation;
3294 gtk_widget_get_allocation(widget, &allocation);
3295 const int last_win_min = MIN(allocation.width, allocation.height);
3296
3297 const dt_develop_t *dev = module->dev;
3298 const float iwd_min = MIN(dt_dev_geometry_raw_width(dev), dt_dev_geometry_raw_height(dev));
3299 const float proc_wdht_min = MIN(dt_dev_geometry_processed_width(dev), dt_dev_geometry_processed_height(dev));
3300 const float scale = 1.f / (get_zoom_scale(dev));
3301 const float im_scale = 0.09f * iwd_min * last_win_min * scale / proc_wdht_min;
3302
3303 *radius = dt_conf_get_sanitize_float(CONF_RADIUS, 0.1f*im_scale, 3.0f*im_scale, im_scale);
3304 *r_strength = dt_conf_get_sanitize_float(CONF_STRENGTH, 0.5f * *radius, 2.0f * *radius, 1.5f * *radius);
3306}
3307/*
3308 add support for changing the radius and the strength vector for the temp node
3309 */
3310int scrolled(struct dt_iop_module_t *module, double x, double y, int up, uint32_t state)
3311{
3313
3314 // add an option to allow skip mouse events while editing masks
3315 const gboolean incr = dt_mask_scroll_increases(up);
3316
3317 if(g->temp)
3318 {
3319 dt_liquify_warp_t *warp = &g->temp->warp;
3320 const float complex strength_v = warp->strength - warp->point;
3321 if(dt_modifier_is(state, 0))
3322 {
3323 // change size
3324 float radius = 0.0f, r = 0.0f, phi = 0.0f;
3325 get_stamp_params(module, &radius, &r, &phi);
3326
3327 float factor = 1.0f;
3328 if(incr)
3329 factor *= 1.0f / 0.97f;
3330 else if(!incr && cabsf(warp->radius - warp->point) > 10.0f)
3331 factor *= 0.97f;
3332
3333 r *= factor;
3334 radius *= factor;
3335
3336 warp->radius = warp->point + (radius * factor);
3337 warp->strength = warp->point + r * cexpf(phi * I);
3338
3341 return 1;
3342 }
3344 {
3345 // change the strength direction
3346 float phi = cargf(strength_v);
3347 const float r = cabsf(strength_v);
3348
3349 if(incr)
3350 phi += DT_M_PI_F / 16.0f;
3351 else
3352 phi -= DT_M_PI_F / 16.0f;
3353
3354 warp->strength = warp->point + r * cexpf(phi * I);
3357 return 1;
3358 }
3359 else if(dt_modifier_is(state, GDK_SHIFT_MASK))
3360 {
3361 // change the strength
3362 const float phi = cargf(strength_v);
3363 float r = cabsf(strength_v);
3364
3365 if(incr)
3366 r *= 1.0f / 0.97f;
3367 else
3368 r *= 0.97f;
3369
3370 warp->strength = warp->point + r * cexpf(phi * I);
3373 return 1;
3374 }
3375 }
3376
3377 return 0;
3378}
3379
3381 double x,
3382 double y,
3383 double pressure,
3384 int which,
3385 int type,
3386 uint32_t state)
3387{
3389 int handled = 0;
3390 float complex pt = 0.0f;
3391 float scale = 0.0f;
3392
3393 get_point_scale(module, x, y, &pt, &scale);
3394
3396
3397 g->last_mouse_pos = pt;
3398 g->last_mouse_mods = state;
3399 if(which == 1)
3400 g->last_button1_pressed_pos = pt;
3401
3402 if(!is_dragging(g))
3403 // while dragging you would always hit the dragged thing
3404 g->last_hit = _hit_test_paths(module, &g->params, pt);
3405
3406 if(which == 2) goto done;
3407
3408 // Point tool
3409
3410 if(which == 1 && gtk_toggle_button_get_active(g->btn_point_tool))
3411 {
3412 // always end dragging before manipulating the path list to avoid
3413 // dangling pointers
3414 end_drag(g);
3415
3416 if(IS_NULL_PTR(g->temp)) goto done;
3417 g->status |= DT_LIQUIFY_STATUS_NEW;
3418 g->status &= ~DT_LIQUIFY_STATUS_PREVIEW;
3419
3421 g->last_hit = NOWHERE;
3422 handled = 1;
3423 goto done;
3424 }
3425
3426 // Line tool or curve tool
3427
3428 if(which == 1 && (gtk_toggle_button_get_active(g->btn_line_tool)
3429 || gtk_toggle_button_get_active(g->btn_curve_tool)))
3430 {
3431 // always end dragging before manipulating the path list to avoid
3432 // dangling pointers
3433 end_drag(g);
3434 if(IS_NULL_PTR(g->temp))
3435 {
3436 if(g->last_hit.layer == DT_LIQUIFY_LAYER_CENTERPOINT)
3437 {
3438 // continue path
3439 g->temp = g->last_hit.elem;
3440 }
3441 else
3442 {
3443 goto done;
3444 }
3445 }
3446
3447 g->last_hit = NOWHERE;
3448 if(gtk_toggle_button_get_active(g->btn_curve_tool))
3449 {
3451 }
3452 g->status |= DT_LIQUIFY_STATUS_NEW;
3453 g->status &= ~DT_LIQUIFY_STATUS_PREVIEW;
3454 handled = 1;
3455 goto done;
3456 }
3457
3458 // Node tool
3459
3460 if(gtk_toggle_button_get_active(g->btn_node_tool))
3461 {
3462 if(which == 1 && dt_modifier_is(g->last_mouse_mods, DT_PRIMARY_MASK) &&
3463 (g->last_hit.layer == DT_LIQUIFY_LAYER_CENTERPOINT))
3464 {
3465 // cycle node type: smooth -> cusp etc.
3466 dt_liquify_path_data_t *node = g->last_hit.elem;
3468 handled = 1;
3469 goto done;
3470 }
3471 if(which == 1 && dt_modifier_is(g->last_mouse_mods, DT_PRIMARY_MASK) &&
3472 (g->last_hit.layer == DT_LIQUIFY_LAYER_STRENGTHPOINT))
3473 {
3474 // cycle warp type: linear -> radial etc.
3475 if(g->last_hit.elem->header.type == DT_LIQUIFY_PATH_MOVE_TO_V1)
3476 {
3477 dt_liquify_warp_t *warp = &g->last_hit.elem->warp;
3478 warp->type = (warp->type + 1) % DT_LIQUIFY_WARP_TYPE_LAST;
3479 }
3480 handled = 1;
3481 goto done;
3482 }
3483 }
3484
3485 if(!handled && (which == 1 || which == 3) && (!IS_NULL_PTR(g->temp) || !IS_NULL_PTR(g->last_hit.elem)))
3486 {
3487 // Even when the actual edit is finalized on button release, this press
3488 // starts an interaction sequence and must remain captured by liquify.
3489 handled = 1;
3490 }
3491
3492done:
3494 return handled;
3495}
3496
3498{
3500
3501 // create initial shape at the center
3502 float complex pt = 0.0f;
3503 float scale = 1.0f;
3504 get_point_scale(module, 0.5f * dt_dev_viewport_box_width(module->dev), 0.5f * dt_dev_viewport_box_height(module->dev), &pt, &scale);
3505 float radius = 0.0f, r = 1.0f, phi = 0.0f;
3506 get_stamp_params(module, &radius, &r, &phi);
3507 // start a new path
3508 g->temp = alloc_move_to(module, pt);
3509 g->temp->warp.radius = pt + radius;
3510 g->temp->warp.strength = pt + r * cexpf(phi * I);
3511 g->status |= DT_LIQUIFY_STATUS_PREVIEW;
3512 g->status |= DT_LIQUIFY_STATUS_NEW;
3513
3514 g->just_started = TRUE;
3515
3516
3518 g->last_hit = NOWHERE;
3519}
3520
3522 double x,
3523 double y,
3524 int which,
3525 uint32_t state)
3526{
3528 int handled = 0;
3529 float complex pt = 0.0f;
3530 float scale = 0.0f;
3531
3532 get_point_scale(module, x, y, &pt, &scale);
3533
3535
3536 g->last_mouse_pos = pt;
3537
3538 const gboolean dragged = detect_drag(g, scale, pt);
3539
3540 if(which == 1 && g->temp && (g->status & DT_LIQUIFY_STATUS_NEW))
3541 {
3542 end_drag(g);
3543 if(gtk_toggle_button_get_active(g->btn_point_tool))
3544 {
3545 g->temp = NULL; // a point is done
3546 btn_make_radio_callback(g->btn_node_tool, NULL, module);
3547 handled = 2;
3548 }
3549 else if(gtk_toggle_button_get_active(g->btn_line_tool))
3550 {
3551 const int prev_index = g->node_index;
3552 const float complex strength = (g->temp->warp.strength - g->temp->warp.point);
3553 const float radius = cabsf(g->temp->warp.radius - g->temp->warp.point);
3554 g->temp = alloc_line_to(module, pt);
3555 if(IS_NULL_PTR(g->temp)) goto done;
3556 g->temp->warp.radius = pt + radius;
3557 g->temp->warp.strength = pt + strength;
3558 // links
3559 g->temp->header.prev = prev_index;
3560 node_get(&g->params, prev_index)->header.next = g->node_index;
3562 g->just_started = FALSE;
3563 handled = 1;
3564 }
3565 else if(gtk_toggle_button_get_active(g->btn_curve_tool))
3566 {
3567 const int prev_index = g->node_index;
3568 const float complex strength = (g->temp->warp.strength - g->temp->warp.point);
3569 const float radius = cabsf(g->temp->warp.radius - g->temp->warp.point);
3570 g->temp = alloc_curve_to(module, pt);
3571 if(IS_NULL_PTR(g->temp)) goto done;
3572 g->temp->warp.radius = pt + radius;
3573 g->temp->warp.strength = pt + strength;
3574 // links
3575 g->temp->header.prev = prev_index;
3576 node_get(&g->params, prev_index)->header.next = g->node_index;
3578 g->just_started = FALSE;
3579 handled = 1;
3580 }
3581 g->status &= ~DT_LIQUIFY_STATUS_NEW;
3582 goto done;
3583 }
3584
3585 if(which == 1 && is_dragging(g))
3586 {
3587 end_drag(g);
3588 handled = 2;
3589 goto done;
3590 }
3591
3592 // right click == cancel or delete
3593 if(which == 3)
3594 {
3596 end_drag(g);
3597
3598 // cancel line or curve creation
3599 if(g->temp)
3600 {
3601 node_delete(&g->params, g->temp);
3602 g->temp = NULL;
3603 g->status &= ~DT_LIQUIFY_STATUS_PREVIEW;
3604 btn_make_radio_callback(g->btn_node_tool, NULL, module);
3605 handled = 2;
3606 goto done;
3607 }
3608
3609 // right click on background toggles node tool
3610 if(g->last_hit.layer == DT_LIQUIFY_LAYER_BACKGROUND)
3611 {
3612 btn_make_radio_callback(g->btn_node_tool, NULL, module);
3613 handled = 1;
3614 goto done;
3615 }
3616
3617 // delete node
3618 if(g->last_hit.layer == DT_LIQUIFY_LAYER_CENTERPOINT)
3619 {
3620 node_delete(&g->params, g->last_hit.elem);
3621 g->last_hit = NOWHERE;
3622 handled = 2;
3623 goto done;
3624 }
3625 // delete shape
3626 if(g->last_hit.layer == DT_LIQUIFY_LAYER_PATH)
3627 {
3628 path_delete(&g->params, g->last_hit.elem);
3629 g->last_hit = NOWHERE;
3630 handled = 2;
3631 goto done;
3632 }
3633 goto done;
3634 }
3635
3636 // Node tool
3637
3638 if(gtk_toggle_button_get_active(g->btn_node_tool))
3639 {
3640 if(which == 1 && dt_modifier_is(g->last_mouse_mods, 0) && !dragged)
3641 {
3642 // select/unselect start/endpoint and clear previous selections
3643 if(g->last_hit.layer == DT_LIQUIFY_LAYER_CENTERPOINT)
3644 {
3645 const int oldsel = !!g->last_hit.elem->header.selected;
3646 unselect_all(&g->params);
3647 g->last_hit.elem->header.selected = oldsel ? 0 : g->last_hit.layer;
3648 handled = 1;
3649 goto done;
3650 }
3651 // unselect all
3652 if(g->last_hit.layer == DT_LIQUIFY_LAYER_BACKGROUND)
3653 {
3654 unselect_all(&g->params);
3655 handled = 1;
3656 goto done;
3657 }
3658 }
3659 if(which == 1 && dt_modifier_is(g->last_mouse_mods, GDK_SHIFT_MASK) && !dragged)
3660 {
3661 // select/unselect start/endpoint and keep previous selections
3662 if(g->last_hit.layer == DT_LIQUIFY_LAYER_CENTERPOINT)
3663 {
3664 const int oldsel = !!g->last_hit.elem->header.selected;
3665 g->last_hit.elem->header.selected = oldsel ? 0 : g->last_hit.layer;
3666 handled = 1;
3667 goto done;
3668 }
3669 }
3670 if(which == 1 && dt_modifier_is(g->last_mouse_mods, DT_PRIMARY_MASK) && !dragged)
3671 {
3672 // add node
3673 if(g->last_hit.layer == DT_LIQUIFY_LAYER_PATH)
3674 {
3675 dt_liquify_path_data_t *e = g->last_hit.elem;
3676 dt_liquify_path_data_t *prev = node_prev(&g->params, e);
3678 {
3679 // add node to curve
3681
3683 if(IS_NULL_PTR(curve2)) goto done;
3684
3685 curve2->node.ctrl1 = curve1->node.ctrl1;
3686 curve2->node.ctrl2 = curve1->node.ctrl2;
3687
3688 dt_liquify_warp_t *warp1 = &prev->warp;
3689 dt_liquify_warp_t *warp2 = &curve2->warp;
3690 dt_liquify_warp_t *warp3 = &e->warp;
3691
3692 const float t = find_nearest_on_curve_t(warp1->point, curve1->node.ctrl1, curve1->node.ctrl2,
3693 warp3->point, pt, INTERPOLATION_POINTS);
3694
3695 float complex midpoint = warp3->point;
3696 casteljau(&warp1->point, &curve1->node.ctrl1, &curve1->node.ctrl2, &midpoint, t);
3697 midpoint = warp1->point;
3698 casteljau(&warp3->point, &curve2->node.ctrl2, &curve2->node.ctrl1, &midpoint, 1.0 - t);
3699
3700 mix_warps(warp2, warp1, warp3, midpoint, t);
3701
3702 node_insert_before(&g->params, e, (dt_liquify_path_data_t *)curve2);
3703
3704 handled = 2;
3705 goto done;
3706 }
3708 {
3709 // add node to line
3710 dt_liquify_warp_t *warp1 = &prev->warp;
3711 dt_liquify_warp_t *warp3 = &e->warp;
3712 const float t = find_nearest_on_line_t(warp1->point, warp3->point, pt);
3713
3715 if(IS_NULL_PTR(tmp)) goto done;
3716
3717 dt_liquify_warp_t *warp2 = &tmp->warp;
3718 const float complex midpoint = cmix(warp1->point, warp3->point, t);
3719
3720 mix_warps(warp2, warp1, warp3, midpoint, t);
3721 node_insert_before(&g->params, e, tmp);
3722
3723 handled = 2;
3724 goto done;
3725 }
3726 }
3727 }
3728 if(which == 1
3729 && dt_modifier_is(g->last_mouse_mods, GDK_MOD1_MASK | DT_PRIMARY_MASK)
3730 && !dragged)
3731 {
3732 if(g->last_hit.layer == DT_LIQUIFY_LAYER_PATH)
3733 {
3734 // change segment
3735 dt_liquify_path_data_t *e = g->last_hit.elem;
3736 dt_liquify_path_data_t *prev = node_prev(&g->params, e);
3738 {
3739 // curve -> line
3742 e->header.selected = e->header.hovered = 0;
3743 handled = 2;
3744 goto done;
3745 }
3747 {
3748 // line -> curve
3749 const float complex p0 = prev->warp.point;
3750 const float complex p1 = e->warp.point;
3754 c->node.ctrl1 = (2 * p0 + p1) / 3.0;
3755 c->node.ctrl2 = ( p0 + 2 * p1) / 3.0;
3756
3757 handled = 2;
3758 goto done;
3759 }
3760 }
3761 }
3762 }
3763
3764done:
3765 if(which == 1)
3766 g->last_button1_pressed_pos = -1;
3767 g->last_hit = NOWHERE;
3769 if(handled)
3770 {
3772 sync_pipe(module, handled == 2);
3773 }
3774 return handled;
3775}
3776
3777int key_pressed(struct dt_iop_module_t *self, GdkEventKey *event)
3778{
3779 if(IS_NULL_PTR(event)) return 0;
3780
3782 if(IS_NULL_PTR(g)) return 0;
3783 guint key = dt_keys_mainpad_alternatives(event->keyval);
3784
3785
3786 const gboolean creating = gtk_toggle_button_get_active(g->btn_point_tool)
3787 || gtk_toggle_button_get_active(g->btn_line_tool)
3788 || gtk_toggle_button_get_active(g->btn_curve_tool)
3790
3791 // Delete last created node while creating a shape.
3792 if(key == GDK_KEY_BackSpace)
3793 {
3794 if(!creating)
3795 return 0;
3796
3797 const gboolean create_tool_active = gtk_toggle_button_get_active(g->btn_point_tool)
3798 || gtk_toggle_button_get_active(g->btn_line_tool)
3799 || gtk_toggle_button_get_active(g->btn_curve_tool);
3800 gboolean restart_shape = FALSE;
3801
3803
3804 dt_liquify_path_data_t *last = NULL;
3805 for(int k = 0; k < MAX_NODES; k++)
3806 {
3807 if(g->params.nodes[k].header.type == DT_LIQUIFY_PATH_INVALIDATED)
3808 break;
3809 last = &g->params.nodes[k];
3810 }
3811
3812 if(IS_NULL_PTR(last) && IS_NULL_PTR(g->temp))
3813 {
3814 restart_shape = create_tool_active;
3816 if(restart_shape)
3817 {
3818 _start_new_shape(self);
3820 sync_pipe(self, TRUE);
3821 }
3822 return 1;
3823 }
3824
3825 dt_liquify_path_data_t *to_delete = !IS_NULL_PTR(g->temp) ? g->temp : last;
3826
3827 end_drag(g);
3828 const int prev_index = to_delete->header.prev;
3829 node_delete(&g->params, to_delete);
3830 g->temp = prev_index >= 0 ? node_get(&g->params, prev_index) : NULL;
3831 g->node_index = !IS_NULL_PTR(g->temp) ? g->temp->header.idx : 0;
3832 g->last_hit = NOWHERE;
3833
3834 if(!IS_NULL_PTR(g->temp))
3835 {
3836 // Continue creation from the current endpoint by restoring the live drag preview.
3838 g->status &= ~DT_LIQUIFY_STATUS_NEW;
3839 }
3840 else
3841 {
3842 restart_shape = create_tool_active;
3844 }
3845
3847
3848 if(restart_shape)
3849 _start_new_shape(self);
3850
3852 sync_pipe(self, TRUE);
3853 return 1;
3854 }
3855
3856 // Delete selected node outside creation mode.
3857 if(key == GDK_KEY_Delete)
3858 {
3859 if(creating)
3860 return 0;
3861
3863
3865 for(int k = 0; k < MAX_NODES; k++)
3866 {
3867 if(g->params.nodes[k].header.type == DT_LIQUIFY_PATH_INVALIDATED)
3868 break;
3869 if(g->params.nodes[k].header.selected == DT_LIQUIFY_LAYER_CENTERPOINT)
3870 {
3871 selected = &g->params.nodes[k];
3872 break;
3873 }
3874 }
3875
3877 {
3879 return 0;
3880 }
3881
3882 end_drag(g);
3883 const int deleted_idx = selected->header.idx;
3884 const int next_idx = selected->header.next;
3885 const int prev_idx = selected->header.prev;
3886 node_delete(&g->params, selected);
3887 g->temp = NULL;
3888 g->last_hit = NOWHERE;
3889
3890 unselect_all(&g->params);
3891 int target_idx = (next_idx != -1) ? next_idx : prev_idx;
3892 if(target_idx > deleted_idx)
3893 target_idx--;
3894
3895 if(target_idx >= 0)
3896 {
3897 dt_liquify_path_data_t *target = node_get(&g->params, target_idx);
3898 if(!IS_NULL_PTR(target) && target->header.type != DT_LIQUIFY_PATH_INVALIDATED)
3900 }
3901
3903
3905 sync_pipe(self, TRUE);
3906 return 1;
3907 }
3908
3909 // Quit current creation or edition on Escape or Enter key
3910 if(key == GDK_KEY_Escape || key == GDK_KEY_Return)
3911 {
3912
3913 if(!creating)
3914 return 0;
3915
3917
3919 end_drag(g);
3920
3921 if(g->temp)
3922 {
3923 node_delete(&g->params, g->temp);
3924 g->temp = NULL;
3925 }
3926
3928 g->last_hit = NOWHERE;
3929
3931
3932 gtk_toggle_button_set_active(g->btn_point_tool, FALSE);
3933 gtk_toggle_button_set_active(g->btn_line_tool, FALSE);
3934 gtk_toggle_button_set_active(g->btn_curve_tool, FALSE);
3935 gtk_toggle_button_set_active(g->btn_node_tool, TRUE);
3936
3937 dt_control_hinter_message(dt_control_get_global(), _("click to edit nodes"));
3940 sync_pipe(self, TRUE);
3941
3942 return 1;
3943 }
3944
3945 return 0;
3946}
3947
3948// we need this only because darktable has no radiobutton support
3949
3950static gboolean btn_make_radio_callback(GtkToggleButton *btn, GdkEventButton *event, dt_iop_module_t *module)
3951{
3953
3954 // if currently dragging and a form (line or node) has been started, does nothing (expect resetting the toggle button status).
3955 if(is_dragging(g) && g->temp && node_prev(&g->params, g->temp))
3956 {
3957 return TRUE;
3958 }
3959
3961
3962 // if we are on a preview, it means that a form (point, line, curve) has been started, but no node has yet been placed.
3963 // in this case we abort the current preview and let the new tool activated.
3964 if(g->status & DT_LIQUIFY_STATUS_PREVIEW)
3965 {
3966 node_delete(&g->params, g->temp);
3967 g->temp = NULL;
3968 g->status &= ~DT_LIQUIFY_STATUS_PREVIEW;
3969 }
3970
3971 // now, let's enable and start a new form safely
3972 if(IS_NULL_PTR(btn) || !gtk_toggle_button_get_active(btn))
3973 {
3974 gtk_toggle_button_set_active(g->btn_point_tool, btn == g->btn_point_tool);
3975 gtk_toggle_button_set_active(g->btn_line_tool, btn == g->btn_line_tool);
3976 gtk_toggle_button_set_active(g->btn_curve_tool, btn == g->btn_curve_tool);
3977 gtk_toggle_button_set_active(g->btn_node_tool, btn == g->btn_node_tool);
3978
3979 if(btn == g->btn_point_tool)
3981 (dt_control_get_global(), _("click and drag to add point\nscroll to change size - "
3982 "shift+scroll to change strength - ctrl+scroll to change direction"));
3983 else if(btn == g->btn_line_tool)
3985 (dt_control_get_global(), _("click to add line\nscroll to change size - "
3986 "shift+scroll to change strength - ctrl+scroll to change direction"));
3987 else if(btn == g->btn_curve_tool)
3989 (dt_control_get_global(), _("click to add curve\nscroll to change size - "
3990 "shift+scroll to change strength - ctrl+scroll to change direction"));
3991 else if(btn == g->btn_node_tool)
3992 dt_control_hinter_message(dt_control_get_global(), _("click to edit nodes"));
3993
3994 // start the preview mode to show the shape that will be created
3995
3996 if(btn == g->btn_point_tool || btn == g->btn_line_tool || btn == g->btn_curve_tool)
3997 {
3998 _start_new_shape(module);
3999 }
4000
4001 if(!IS_NULL_PTR(btn)) dt_iop_request_focus(module);
4002 }
4003 else
4004 {
4005 gtk_toggle_button_set_active(btn, FALSE);
4006 }
4007
4008 sync_pipe(module, FALSE);
4009
4010 return TRUE;
4011}
4012
4014{
4016 memcpy(&g->params, module->params, sizeof(dt_iop_liquify_params_t));
4018}
4019
4021{
4023
4024 // A dummy surface for calculations only, no drawing.
4025 cairo_surface_t *cs = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1);
4026 cairo_surface_destroy(cs);
4027
4028 g->dragging = NOWHERE;
4029 g->temp = NULL;
4030 g->status = 0;
4031 g->last_mouse_pos =
4032 g->last_button1_pressed_pos = -1;
4033 g->last_hit = NOWHERE;
4034 g->node_index = 0;
4035
4036 self->gui->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
4037
4038 GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
4039 gtk_widget_set_tooltip_text(hbox, _("use a tool to add warps.\nright-click to remove a warp."));
4040 gtk_box_pack_start(GTK_BOX(self->gui->widget), hbox, TRUE, TRUE, 0);
4041
4042
4043 GtkWidget *lbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
4044 gtk_box_pack_start(GTK_BOX(hbox), lbox, FALSE, TRUE, 0);
4045
4046 GtkWidget *labelbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
4047 GtkWidget *label = dt_ui_label_new(_("Warps: "));
4048 gtk_box_pack_start(GTK_BOX(labelbox), label, FALSE, TRUE, 0);
4049 g->label_warp = GTK_LABEL(dt_ui_label_new("-"));
4050 gtk_box_pack_start(GTK_BOX(labelbox), GTK_WIDGET(g->label_warp), FALSE, TRUE, 0);
4051 gtk_box_pack_start(GTK_BOX(lbox), labelbox, FALSE, TRUE, 0);
4052
4053 GtkWidget *labelbox2 = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
4054 GtkWidget *label2 = dt_ui_label_new(_("Nodes: "));
4055 gtk_box_pack_start(GTK_BOX(labelbox2), label2, FALSE, TRUE, 0);
4056 g->label_node = GTK_LABEL(dt_ui_label_new("-"));
4057 gtk_box_pack_start(GTK_BOX(labelbox2), GTK_WIDGET(g->label_node), FALSE, TRUE, 0);
4058 gtk_box_pack_start(GTK_BOX(lbox), labelbox2, FALSE, TRUE, 0);
4059
4060 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
4061 gtk_box_pack_start(GTK_BOX(self->gui->widget), hbox, TRUE, TRUE, 0);
4062
4063 g->btn_node_tool = GTK_TOGGLE_BUTTON(dt_iop_togglebutton_new(self, NULL, N_("edit, add and delete nodes"), NULL,
4064 G_CALLBACK(btn_make_radio_callback), TRUE, 0, 0,
4066
4067 g->btn_curve_tool = GTK_TOGGLE_BUTTON(dt_iop_togglebutton_new(self, N_("shapes"), N_("draw curves"), N_("draw multiple curves"),
4068 G_CALLBACK(btn_make_radio_callback), TRUE, 0, 0,
4070
4071 g->btn_line_tool = GTK_TOGGLE_BUTTON(dt_iop_togglebutton_new(self, N_("shapes"), N_("draw lines"), N_("draw multiple lines"),
4072 G_CALLBACK(btn_make_radio_callback), TRUE, 0, 0,
4074
4075 g->btn_point_tool = GTK_TOGGLE_BUTTON(dt_iop_togglebutton_new(self, N_("shapes"), N_("draw points"), N_("draw multiple points"),
4076 G_CALLBACK(btn_make_radio_callback), TRUE, 0, 0,
4078
4079 dt_liquify_layers[DT_LIQUIFY_LAYER_PATH].hint = _("drag: move segment - ctrl+click: add node - right click: remove path\n"
4080 "ctrl+alt+click: toggle line/curve");
4081 dt_liquify_layers[DT_LIQUIFY_LAYER_CENTERPOINT].hint = _("click and drag to move - click: show/hide feathering controls\n"
4082 "ctrl+click: autosmooth, cusp, smooth, symmetrical"
4083 " - right click to remove");
4084 dt_liquify_layers[DT_LIQUIFY_LAYER_CTRLPOINT1].hint = _("drag to change shape of path");
4085 dt_liquify_layers[DT_LIQUIFY_LAYER_CTRLPOINT2].hint = _("drag to change shape of path");
4086 dt_liquify_layers[DT_LIQUIFY_LAYER_RADIUSPOINT].hint = _("drag to adjust warp radius");
4087 dt_liquify_layers[DT_LIQUIFY_LAYER_HARDNESSPOINT1].hint = _("drag to adjust hardness (center)");
4088 dt_liquify_layers[DT_LIQUIFY_LAYER_HARDNESSPOINT2].hint = _("drag to adjust hardness (feather)");
4089 dt_liquify_layers[DT_LIQUIFY_LAYER_STRENGTHPOINT].hint = _("drag to adjust warp strength\n"
4090 "ctrl+click: linear, grow, and shrink");
4091}
4092
4094{
4096 g->dragging = NOWHERE;
4097 g->temp = NULL;
4098 g->status = 0;
4099 btn_make_radio_callback(NULL, NULL, self);
4100}
4101
4103{
4105}
4106
4107
4108// clang-format off
4109// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
4110// vim: shiftwidth=2 expandtab tabstop=2 cindent
4111// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
4112// clang-format on
Handle default and user-set shortcuts (accelerators)
#define DT_PRIMARY_MASK
GtkWidget * dt_gui_main_window(void)
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:283
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
dt_collection_t * dt_collection_get_global(void)
Definition collection.c:138
void dt_collection_hint_message(const dt_collection_t *collection)
Definition collection.c:912
static float lookup(read_only image2d_t lut, const float x)
@ IOP_CS_RGB
static const float x
const int t
const float v
#define B(y, x)
#define A(y, x)
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
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:431
void dt_conf_set_float(const char *name, float val)
float dt_conf_get_float(const char *name)
Float for name, clamped to its declared bounds.
void dt_control_queue_redraw_center()
Request a redraw of the centre view.
Definition control.c:924
void dt_control_hinter_message(const struct dt_control_t *s, const char *message)
Definition control.c:981
void dt_control_queue_cursor_by_name(const char *curs_str)
Queue a GTK named cursor for the next cursor commit.
Definition control.c:407
struct dt_control_t * dt_control_get_global(void)
Definition darktable.c:651
#define M_PI_F
int32_t dt_dev_geometry_processed_height(const dt_develop_t *dev)
int32_t dt_dev_geometry_raw_width(const dt_develop_t *dev)
int32_t dt_dev_geometry_raw_height(const dt_develop_t *dev)
dt_dev_image_geometry_t dt_dev_geometry_snapshot(const dt_develop_t *dev)
int32_t dt_dev_geometry_processed_width(const dt_develop_t *dev)
#define dt_dev_add_history_item(dev, module, enable, redraw)
void dt_iop_params_t
Definition dev_history.h:43
int32_t dt_dev_viewport_box_height(const dt_develop_t *dev)
float dt_dev_viewport_scaling(const dt_develop_t *dev)
int32_t dt_dev_viewport_box_width(const dt_develop_t *dev)
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)
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:1776
int dt_dev_distort_backtransform_gui(dt_develop_t *dev, const double iop_order, const int transf_direction, float *points, size_t points_count)
The inverse of dt_dev_distort_transform_gui(), same rules.
Definition develop.c:1699
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:1797
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:2081
float dt_dev_get_zoom_scale(const dt_develop_t *dev, const gboolean preview)
Definition develop.c:989
void dt_dev_coordinates_image_norm_to_image_abs(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1203
void dt_dev_coordinates_widget_to_image_norm(dt_develop_t *dev, float *points, size_t num_points)
Coordinate conversion helpers between widget, normalized image, and absolute image spaces.
Definition develop.c:1144
@ DT_DEV_TRANSFORM_DIR_BACK_EXCL
Definition develop.h:111
@ DT_DEV_TRANSFORM_DIR_ALL
Definition develop.h:107
@ DT_DEV_TRANSFORM_DIR_FORW_EXCL
Definition develop.h:109
GtkWidget * geometry
its size, under the preview
GHashTable * selected
set of checked row labels, mirrored to conf on every change
@ TYPE_FLOAT
Definition format.h:56
static guint dt_keys_mainpad_alternatives(const guint key_val)
Remap keypad keys to usual mainpad ones.
Definition gdkkeys.h:118
int dt_geometry_chain_compose(dt_geometry_chain_t *chain, const double iop_order, const int direction, float *points, const size_t points_count)
Compose the chain over points, for a record evaluator that needs the transform stack around its own m...
Definition geometry.c:412
gboolean dt_geometry_chain_authoritative(const dt_geometry_chain_t *chain)
Can this chain answer questions yet?
Definition geometry.c:324
Where things are on the image, answered without a pipeline.
const char ** dt_iop_set_description(dt_iop_module_t *module, const char *main_text, const char *purpose, const char *input, const char *process, const char *output)
Definition imageop.c:1893
void dt_iop_gui_leave_critical_section(dt_iop_module_t *const module)
Release what dt_iop_gui_enter_critical_section() took. Also a no-op headless.
void dt_iop_request_focus(dt_iop_module_t *module)
Move darkroom focus to module, or clear it with NULL.
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:186
void dt_iop_gui_enter_critical_section(dt_iop_module_t *const module)
Take the module's GUI lock, serialising access to its dt_iop_gui_data_t.
@ IOP_GROUP_EFFECTS
Definition imageop.h:161
@ IOP_TAG_DISTORT
Definition imageop.h:170
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)
#define IOP_GUI_FREE
Definition imageop_gui.h:96
static dt_iop_gui_data_t * dt_iop_gui_data(const struct dt_iop_module_t *m)
The module's GUI data blob, NULL-safe for headless callers: IOP process() implementations read it for...
Definition imageop_gui.h:81
#define IOP_GUI_ALLOC(module)
Definition imageop_gui.h:93
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
GtkWidget * dt_ui_label_new(const gchar *str)
Definition label.c:125
int operation_tags()
Definition liquify.c:351
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:1877
dt_liquify_path_data_enum_t
Definition liquify.c:238
@ DT_LIQUIFY_PATH_LINE_TO_V1
Definition liquify.c:241
@ DT_LIQUIFY_PATH_INVALIDATED
Definition liquify.c:239
@ DT_LIQUIFY_PATH_CURVE_TO_V1
Definition liquify.c:242
@ DT_LIQUIFY_PATH_MOVE_TO_V1
Definition liquify.c:240
static void _start_new_shape(dt_iop_module_t *module)
Definition liquify.c:3497
static int _liquify_geometry_backtransform(const void *data, const dt_geometry_record_t *const record, dt_geometry_chain_t *chain, float *points, size_t points_count)
Definition liquify.c:1551
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:1790
void init(dt_iop_module_t *module)
Definition liquify.c:1852
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:1583
static void casteljau(const float complex *p0, float complex *p1, float complex *p2, float complex *p3, const float t)
Definition liquify.c:2420
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:2940
const char ** description(struct dt_iop_module_t *self)
Definition liquify.c:331
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:1121
int default_group()
Definition liquify.c:341
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:1522
static void get_stamp_params(dt_iop_module_t *module, float *radius, float *r_strength, float *phi)
Definition liquify.c:3290
#define VERYTHINLINE
Definition liquify.c:2048
void gui_reset(dt_iop_module_t *self)
Definition liquify.c:4093
#define CONF_ANGLE
Definition liquify.c:95
float dt_liquify_ui_widths[]
Definition liquify.c:196
cl_int cl_int_t
Definition liquify.c:1691
static dt_liquify_path_data_t * alloc_line_to(dt_iop_module_t *module, float complex end_point)
Definition liquify.c:2899
static gboolean _is_movable_layer(const dt_liquify_layer_enum_t layer)
Definition liquify.c:3045
#define COLOR_DEBUG
Definition liquify.c:144
dt_liquify_layer_t dt_liquify_layers[]
Definition liquify.c:160
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:2445
#define CONF_STRENGTH
Definition liquify.c:96
static gboolean is_dragging(const dt_iop_liquify_gui_data_t *g)
Definition liquify.c:1508
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:1513
static dt_liquify_path_data_t * alloc_move_to(dt_iop_module_t *module, float complex start_point)
Definition liquify.c:2886
void cleanup_pipe(struct dt_iop_module_t *module, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition liquify.c:1869
static void get_point_scale(struct dt_iop_module_t *module, float x, float y, float complex *pt, float *scale)
Definition liquify.c:3031
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:2052
static void node_gc(dt_iop_liquify_params_t *p)
Definition liquify.c:428
static float mix(const float a, const float b, const float t)
Definition liquify.c:751
int scrolled(struct dt_iop_module_t *module, double x, double y, int up, uint32_t state)
Definition liquify.c:3310
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:1693
static void distort_paths_raw_to_piece_chain(const struct dt_iop_module_t *module, dt_geometry_chain_t *chain, const float roi_in_scale, dt_iop_liquify_params_t *p)
distort_paths_raw_to_piece() for the geometry service: same fold, no pipe.
Definition liquify.c:731
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:2580
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:3380
static void draw_triangle(cairo_t *cr, const float complex pt, const double theta, const double size)
Definition liquify.c:1906
int mouse_moved(struct dt_iop_module_t *module, double x, double y, double pressure, int which)
Definition liquify.c:3063
dt_liquify_layer_flag_enum_t
Definition liquify.c:125
@ DT_LIQUIFY_LAYER_FLAG_CURVE_TOOL
show if line tool active
Definition liquify.c:131
@ DT_LIQUIFY_LAYER_FLAG_POINT_TOOL
show if point tool active
Definition liquify.c:129
@ DT_LIQUIFY_LAYER_FLAG_HIT_TEST
include layer in hit testing
Definition liquify.c:126
@ DT_LIQUIFY_LAYER_FLAG_ANY_TOOL
Definition liquify.c:133
@ DT_LIQUIFY_LAYER_FLAG_NODE_SELECTED
show if node is selected
Definition liquify.c:128
@ DT_LIQUIFY_LAYER_FLAG_LINE_TOOL
show if line tool active
Definition liquify.c:130
@ DT_LIQUIFY_LAYER_FLAG_PREV_SELECTED
show if previous node is selected
Definition liquify.c:127
@ DT_LIQUIFY_LAYER_FLAG_NODE_TOOL
show if node tool active
Definition liquify.c:132
__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:1618
static int _liquify_warp_points(const dt_iop_liquify_params_t *const params_in, dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, dt_geometry_chain_t *chain, float *const restrict points, const size_t points_count, const gboolean inverted)
Warp points, given this module's parameters brought into its own input space.
Definition liquify.c:1418
dt_liquify_node_type_enum_t
Definition liquify.c:218
@ DT_LIQUIFY_NODE_TYPE_LAST
Definition liquify.c:223
@ DT_LIQUIFY_NODE_TYPE_CUSP
Definition liquify.c:219
@ DT_LIQUIFY_NODE_TYPE_SYMMETRICAL
Definition liquify.c:221
@ DT_LIQUIFY_NODE_TYPE_AUTOSMOOTH
Definition liquify.c:222
@ DT_LIQUIFY_NODE_TYPE_SMOOTH
Definition liquify.c:220
void gui_update(dt_iop_module_t *module)
Refresh GUI controls from current params and configuration.
Definition liquify.c:4013
static float get_rot(const dt_liquify_warp_type_enum_t warp_type)
Definition liquify.c:373
static float complex * create_global_distortion_map(const cairo_rectangle_int_t *map_extent, const GSList *interpolated, gboolean inverted)
Definition liquify.c:1215
#define THINLINE
Definition liquify.c:2049
static void node_delete(dt_iop_liquify_params_t *p, dt_liquify_path_data_t *this)
Definition liquify.c:459
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:986
static float complex normalize(const float complex v)
Definition liquify.c:742
static float cdot(const float complex p0, const float complex p1)
Definition liquify.c:1887
static void set_line_width(cairo_t *cr, double scale, dt_liquify_ui_width_enum_t w)
Definition liquify.c:1941
#define CHECK_HIT_PT(point)
Definition liquify.c:2436
const char * name()
Definition liquify.c:326
static void smooth_path_linsys(size_t n, const float complex *k, float complex *c1, float complex *c2, const int *equation)
Definition liquify.c:2682
static gboolean detect_drag(const dt_iop_liquify_gui_data_t *g, const double scale, const float complex pt)
Definition liquify.c:1947
dt_liquify_warp_type_enum_t
Definition liquify.c:210
@ DT_LIQUIFY_WARP_TYPE_RADIAL_GROW
Definition liquify.c:212
@ DT_LIQUIFY_WARP_TYPE_LINEAR
Definition liquify.c:211
@ DT_LIQUIFY_WARP_TYPE_LAST
Definition liquify.c:214
@ DT_LIQUIFY_WARP_TYPE_RADIAL_SHRINK
Definition liquify.c:213
static const dt_liquify_rgba_t DT_LIQUIFY_COLOR_SELECTED
Definition liquify.c:145
static void draw_rectangle(cairo_t *cr, const float complex pt, const double theta, const double size)
Definition liquify.c:1896
static void unselect_all(dt_iop_liquify_params_t *p)
Definition liquify.c:2926
static float mitchell(const float x)
Definition liquify.c:1674
dt_liquify_ui_width_enum_t
Definition liquify.c:184
@ DT_LIQUIFY_UI_WIDTH_GIZMO_SMALL
Definition liquify.c:189
@ DT_LIQUIFY_UI_WIDTH_DOUBLELINE
Definition liquify.c:187
@ DT_LIQUIFY_UI_WIDTH_LAST
Definition liquify.c:193
@ DT_LIQUIFY_UI_WIDTH_DEFAULT_STRENGTH
Definition liquify.c:191
@ DT_LIQUIFY_UI_WIDTH_THICKLINE
Definition liquify.c:186
@ DT_LIQUIFY_UI_WIDTH_THINLINE
Definition liquify.c:185
@ DT_LIQUIFY_UI_WIDTH_DEFAULT_RADIUS
Definition liquify.c:190
@ DT_LIQUIFY_UI_WIDTH_GIZMO
Definition liquify.c:188
@ DT_LIQUIFY_UI_WIDTH_MIN_DRAG
Definition liquify.c:192
void gui_init(dt_iop_module_t *self)
Definition liquify.c:4020
static dt_liquify_path_data_t * find_hovered(dt_iop_liquify_params_t *p)
Definition liquify.c:2865
static float dt_conf_get_sanitize_float(const char *name, float min, float max, float default_value)
Definition liquify.c:3279
static float * build_lookup_table(const int distance, const float control1, const float control2)
Definition liquify.c:917
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:720
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:861
static void smooth_paths_linsys(dt_iop_liquify_params_t *params)
Definition liquify.c:2766
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:1082
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:588
static float get_zoom_scale(const dt_develop_t *develop)
Definition liquify.c:2935
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:2608
static void end_drag(dt_iop_liquify_gui_data_t *g)
Definition liquify.c:1503
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:1348
void gui_cleanup(dt_iop_module_t *self)
Definition liquify.c:4102
static const dt_liquify_rgba_t DT_LIQUIFY_COLOR_HOVER
Definition liquify.c:146
static dt_liquify_path_data_t * node_alloc(dt_iop_liquify_params_t *p, int *node_index)
Definition liquify.c:381
static float get_ui_width(const float scale, const dt_liquify_ui_width_enum_t w)
Definition liquify.c:1933
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:953
void cleanup_global(dt_iop_module_so_t *module)
Definition liquify.c:1844
#define THICKLINE
Definition liquify.c:2050
static dt_liquify_path_data_t * node_prev(dt_iop_liquify_params_t *p, const dt_liquify_path_data_t *n)
Definition liquify.c:395
static float bicubic(const float a, const float x)
Definition liquify.c:1663
dt_liquify_status_enum_t
Definition liquify.c:227
@ DT_LIQUIFY_STATUS_LAST
Definition liquify.c:232
@ DT_LIQUIFY_STATUS_NONE
Definition liquify.c:228
@ DT_LIQUIFY_STATUS_PREVIEW
Definition liquify.c:231
@ DT_LIQUIFY_STATUS_NEW
Definition liquify.c:229
@ DT_LIQUIFY_STATUS_INTERPOLATED
Definition liquify.c:230
int default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece)
Definition liquify.c:356
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:361
#define COLOR_NULL
Definition liquify.c:141
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:805
static GList * interpolate_paths(dt_iop_liquify_params_t *p)
Definition liquify.c:1973
int flags()
Definition liquify.c:346
static dt_liquify_path_data_t * node_get(dt_iop_liquify_params_t *p, const int index)
Definition liquify.c:403
static const dt_geometry_vtable_t _liquify_geometry_vtable
Definition liquify.c:1558
const float STAMP_RELOCATION
Definition liquify.c:92
const int LOOKUP_OVERSAMPLE
Definition liquify.c:90
const int INTERPOLATION_POINTS
Definition liquify.c:91
cl_mem cl_mem_t
Definition liquify.c:1690
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:1497
#define FG_COLOR
Definition liquify.c:2046
static float find_nearest_on_line_t(const float complex p0, const float complex p1, const float complex x)
Definition liquify.c:2410
static void path_delete(dt_iop_liquify_params_t *p, dt_liquify_path_data_t *this)
Definition liquify.c:482
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:2374
#define MAX_NODES
Definition liquify.c:88
static GSList * _get_map_extent(const dt_iop_roi_t *roi_out, const GList *interpolated, cairo_rectangle_int_t *map_extent)
Definition liquify.c:1185
int button_released(struct dt_iop_module_t *module, double x, double y, int which, uint32_t state)
Definition liquify.c:3521
static int _liquify_geometry_transform(const void *data, const dt_geometry_record_t *const record, dt_geometry_chain_t *chain, float *points, size_t points_count)
Definition liquify.c:1544
static int path_length(dt_iop_liquify_params_t *p, dt_liquify_path_data_t *n)
Definition liquify.c:2755
#define ABCD(A, B, C, D)
#define LGREY
Definition liquify.c:143
static dt_liquify_path_data_t * node_next(dt_iop_liquify_params_t *p, const dt_liquify_path_data_t *n)
Definition liquify.c:411
#define GET_UI_WIDTH(a)
Definition liquify.c:1939
static const dt_liquify_hit_t NOWHERE
Definition liquify.c:291
void init_pipe(struct dt_iop_module_t *module, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition liquify.c:1863
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:1312
static float complex cmix(const float complex p0, const float complex p1, const float t)
Definition liquify.c:759
static gboolean btn_make_radio_callback(GtkToggleButton *btn, GdkEventButton *event, dt_iop_module_t *module)
Definition liquify.c:3950
static void draw_circle(cairo_t *cr, const float complex pt, const double diameter)
Definition liquify.c:1919
static void update_warp_count(const dt_iop_liquify_gui_data_t *g)
Definition liquify.c:1954
dt_liquify_layer_enum_t
Definition liquify.c:101
@ DT_LIQUIFY_LAYER_STRENGTHPOINT
Definition liquify.c:120
@ DT_LIQUIFY_LAYER_HARDNESSPOINT2_HANDLE
Definition liquify.c:112
@ DT_LIQUIFY_LAYER_HARDNESSPOINT1
Definition liquify.c:118
@ DT_LIQUIFY_LAYER_HARDNESSPOINT2
Definition liquify.c:119
@ DT_LIQUIFY_LAYER_CTRLPOINT1
Definition liquify.c:115
@ DT_LIQUIFY_LAYER_PATH
Definition liquify.c:107
@ DT_LIQUIFY_LAYER_HARDNESS2
Definition liquify.c:105
@ DT_LIQUIFY_LAYER_LAST
Definition liquify.c:121
@ DT_LIQUIFY_LAYER_HARDNESS1
Definition liquify.c:104
@ DT_LIQUIFY_LAYER_RADIUSPOINT
Definition liquify.c:117
@ DT_LIQUIFY_LAYER_RADIUS
Definition liquify.c:103
@ DT_LIQUIFY_LAYER_WARPS
Definition liquify.c:106
@ DT_LIQUIFY_LAYER_HARDNESSPOINT1_HANDLE
Definition liquify.c:111
@ DT_LIQUIFY_LAYER_CTRLPOINT2
Definition liquify.c:116
@ DT_LIQUIFY_LAYER_CENTERPOINT
Definition liquify.c:114
@ DT_LIQUIFY_LAYER_RADIUSPOINT_HANDLE
Definition liquify.c:110
@ DT_LIQUIFY_LAYER_CTRLPOINT1_HANDLE
Definition liquify.c:108
@ DT_LIQUIFY_LAYER_STRENGTHPOINT_HANDLE
Definition liquify.c:113
@ DT_LIQUIFY_LAYER_CTRLPOINT2_HANDLE
Definition liquify.c:109
@ DT_LIQUIFY_LAYER_BACKGROUND
Definition liquify.c:102
static void init_warp(dt_liquify_warp_t *warp, float complex point)
Definition liquify.c:2875
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:419
#define GREY
Definition liquify.c:142
void init_global(dt_iop_module_so_t *module)
Definition liquify.c:1835
gboolean geometry_record(struct dt_iop_module_t *self, const void *params, dt_geometry_record_t *record)
Definition liquify.c:1564
static void set_source_rgba(cairo_t *cr, dt_liquify_rgba_t rgba)
Definition liquify.c:1928
void gui_focus(struct dt_iop_module_t *module, gboolean in)
Definition liquify.c:2993
#define BG_COLOR
Definition liquify.c:2047
static void sync_pipe(struct dt_iop_module_t *module, gboolean history)
Definition liquify.c:3002
static dt_liquify_path_data_t * alloc_curve_to(dt_iop_module_t *module, float complex end_point)
Definition liquify.c:2912
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:764
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:1339
static float get_arc_length(const float complex points[], const int n_points)
Definition liquify.c:840
#define CONF_RADIUS
Definition liquify.c:94
int key_pressed(struct dt_iop_module_t *self, GdkEventKey *event)
Definition liquify.c:3777
_lib_location_type_t type
Definition location.c:1
@ DT_DEBUG_OPENCL
Definition logging.h:57
void dt_print(dt_debug_thread_t thread, const char *msg,...) __attribute__((format(printf
Print to stdout when thread is enabled, prefixed with seconds since startup.
float *const restrict const size_t k
float *const restrict const size_t const size_t ch
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition macros.h:96
#define DT_M_PI_F
Definition math.h:54
#define DT_M_PI
Definition math.h:55
#define M_PI
Definition math.h:47
#define dt_free_align(ptr)
Release memory from dt_alloc_align() and set ptr to NULL.
Definition mem_alloc.h:214
static void * dt_calloc_align(size_t size)
dt_alloc_align() followed by a zero fill.
Definition mem_alloc.h:225
static void dt_free_gpointer(gpointer ptr)
g_free() one pointer, with the signature GDestroyNotify wants.
Definition mem_alloc.h:184
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
char * key
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
size_t size
Definition mipmap_cache.c:3
#define DT_MODULE_INTROSPECTION(MODVER, PARAMSTYPE)
DT_MODULE() for a module whose params struct is introspected.
int dt_opencl_enqueue_kernel_2d(const int dev, const int kernel, const size_t *sizes)
Definition opencl.c:2554
int dt_opencl_create_kernel(const int prog, const char *name)
Definition opencl.c:2448
void * dt_opencl_copy_host_to_device_constant(const int devid, const size_t size, void *host)
Definition opencl.c:2750
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:2679
void dt_opencl_free_kernel(const int kernel)
Definition opencl.c:2491
int dt_opencl_set_kernel_arg(const int dev, const int kernel, const int num, const size_t size, const void *arg)
Definition opencl.c:2545
void dt_opencl_release_mem_object(cl_mem mem)
Definition opencl.c:2805
#define ROUNDUPDHT(a, b)
Definition opencl.h:86
#define ROUNDUPDWD(a, b)
Definition opencl.h:85
#define __OMP_PARALLEL_FOR__(...)
Definition openmp.h:95
const float factor
Definition pdf.h:91
#define dt_pixelpipe_cache_alloc_align_float_cache(pixels, id)
#define dt_pixelpipe_cache_alloc_align_cache(size, id)
#define dt_pixelpipe_cache_free_align(mem)
static const dt_aligned_pixel_simd_t value
Definition simd.h:144
const float uint32_t state[4]
const float r
dt_geometry_chain_t * chain
Definition liquify.c:581
dt_develop_t * develop
Definition liquify.c:575
gboolean from_distort_transform
Definition liquify.c:585
const dt_dev_pixelpipe_t * pipe
Definition liquify.c:576
Objective facts about the image a dev is working on.
struct dt_iop_module_t *void * data
struct dt_geometry_chain_t * geometry_chain
Definition develop.h:491
One module instance's contribution, as data.
Definition geometry.h:99
const dt_geometry_vtable_t * vtable
Definition geometry.h:110
void(* free_data)(void *data)
Definition geometry.h:112
A module's geometry, evaluated. Pure functions of the record's own data.
Definition geometry.h:75
void(* map_size)(const void *data, const dt_iop_roi_t *const in, dt_iop_roi_t *out)
Full-resolution input rect -> output rect. Mirrors modify_roi_out() at scale 1.
Definition geometry.h:77
enum dt_interpolation_type id
unsigned int channels
Definition format.h:83
dt_iop_buffer_type_t datatype
Definition format.h:85
What the geometry service's record carries: the parameters, and the module whose iop_order bounds the...
Definition liquify.c:568
dt_iop_module_t * self
Definition liquify.c:570
dt_iop_liquify_params_t params
Definition liquify.c:569
dt_iop_liquify_params_t params
Definition liquify.c:305
GdkModifierType last_mouse_mods
GDK modifiers at the time mouse button was pressed.
Definition liquify.c:310
dt_liquify_path_data_t * temp
Points to the element under construction or NULL.
Definition liquify.c:315
dt_liquify_hit_t dragging
Element being dragged with mouse button.
Definition liquify.c:313
GtkToggleButton * btn_curve_tool
Definition liquify.c:319
float complex last_button1_pressed_pos
Definition liquify.c:309
dt_liquify_hit_t last_hit
Element last hit with mouse button.
Definition liquify.c:312
float complex last_mouse_pos
Definition liquify.c:308
dt_liquify_status_enum_t status
Various flags.
Definition liquify.c:316
GtkWidget * widget
Definition imageop_gui.h:47
dt_iop_global_data_t * data
Definition imageop.h:238
struct dt_iop_module_gui_t * gui
Definition imageop.h:346
struct dt_develop_t * dev
Definition imageop.h:311
dt_iop_global_data_t * global_data
Definition imageop.h:337
int32_t params_size
Definition imageop.h:335
dt_iop_params_t * params
Definition imageop.h:333
Region of interest passed through the pixelpipe.
Definition format.h:49
double scale
Definition format.h:51
int width
Definition format.h:50
int height
Definition format.h:50
dt_liquify_layer_enum_t layer
Definition liquify.c:287
dt_liquify_path_data_t * elem
Definition liquify.c:288
const char * hint
hint displayed when hovering
Definition liquify.c:157
dt_liquify_layer_enum_t hover_master
hover whenever master layer hovers, eg. to
Definition liquify.c:150
dt_liquify_rgba_t bg
the background color for this layer
Definition liquify.c:152
dt_liquify_layer_flag_enum_t flags
various flags for layer
Definition liquify.c:156
dt_liquify_rgba_t fg
the foreground color for this layer
Definition liquify.c:151
float complex ctrl1
Definition liquify.c:272
float complex ctrl2
Definition liquify.c:273
dt_liquify_node_t node
Definition liquify.c:282
dt_liquify_warp_t warp
Definition liquify.c:281
dt_liquify_path_header_t header
Definition liquify.c:280
dt_liquify_layer_enum_t hovered
Definition liquify.c:250
dt_liquify_layer_enum_t selected
Definition liquify.c:249
dt_liquify_node_type_enum_t node_type
Definition liquify.c:248
dt_liquify_path_data_enum_t type
Definition liquify.c:247
float complex point
Definition liquify.c:261
float complex radius
a point (the effective radius scalar is: cabs(radius - point))
Definition liquify.c:263
float control2
range 0.0 .. 1.0 == radius
Definition liquify.c:265
float complex strength
a point (the effective strength vector is: strength - point)
Definition liquify.c:262
dt_liquify_warp_type_enum_t type
Definition liquify.c:266
dt_liquify_status_enum_t status
Definition liquify.c:267
float control1
range 0.0 .. 1.0 == radius
Definition liquify.c:264
#define __DT_CLONE_TARGETS__
#define MIN(a, b)
Definition thinplate.c:32
static gboolean dt_modifier_is(GdkModifierType state, const GdkModifierType desired_modifier_mask)
#define DT_GUI_MOUSE_EFFECT_RADIUS
#define DT_GUI_BOX_SPACING
#define DT_PIXEL_APPLY_DPI(value)
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)