Skip to content

Commit 98eefaa

Browse files
committed
Implements rotation for paths, brush strokes and gradients.
- Uses CTRL+drag for all shape rotations (already used by ellipse rotation). - Changes CTRL+click to add a node to a path or brush stroke to SHIFT+click (to prevent ambiguity w/ CTRL+drag). - Allows independent rotation of source/target shapes in retouch (heal and clone) w/ CTRL+SHIFT+drag.
1 parent 057afd6 commit 98eefaa

9 files changed

Lines changed: 1206 additions & 101 deletions

File tree

data/kernels/retouch.cl

Lines changed: 107 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,16 +59,66 @@ retouch_copy_buffer_to_image(global float4 *in,
5959
__write_only image2d_t out,
6060
global dt_iop_roi_t *roi_out,
6161
const int xoffs,
62-
const int yoffs)
62+
const int yoffs,
63+
const float angle,
64+
const float cx,
65+
const float cy)
6366
{
6467
const int x = get_global_id(0);
6568
const int y = get_global_id(1);
6669

6770
if(x >= roi_out->width || y >= roi_out->height) return;
68-
if(x + xoffs >= roi_in->width || y + yoffs >= roi_in->height) return;
6971

70-
const int idx = mad24(y + yoffs, roi_in->width, x + xoffs);
71-
write_imagef(out, (int2)(x, y), in[idx]);
72+
// skip the rotation math for negligibly small angles (cheaper, and avoids
73+
// resampling artifacts on a near-zero rotation from repeated UI actions)
74+
if (fabs(angle) < 0.01f)
75+
{
76+
if(x + xoffs >= roi_in->width || y + yoffs >= roi_in->height || x + xoffs < 0 || y + yoffs < 0) return;
77+
const int idx = mad24(y + yoffs, roi_in->width, x + xoffs);
78+
write_imagef(out, (int2)(x, y), in[idx]);
79+
}
80+
else
81+
{
82+
// same rotation as the on-screen source outline, so the overlay marks the
83+
// region actually copied (see rt_copy_in_to_out in retouch.c)
84+
const float c = dtcl_cos(angle);
85+
const float s = dtcl_sin(angle);
86+
// (cx, cy) is the mask centroid (rotation pivot), in roi_out-local coords
87+
const float cx_source = cx + xoffs;
88+
const float cy_source = cy + yoffs;
89+
90+
const float sx = x + xoffs;
91+
const float sy = y + yoffs;
92+
const float rx = sx - cx_source;
93+
const float ry = sy - cy_source;
94+
const float ix = cx_source + rx * c - ry * s;
95+
const float iy = cy_source + rx * s + ry * c;
96+
97+
// Edge-clamp (replicate) out-of-bounds samples instead of zero-filling:
98+
// rt_compute_roi_in grows roi_in to the rotation-aware source area, but not
99+
// past the image border, so a rotated source near the edge can still sample
100+
// just outside roi_in. Replicating avoids a hard black seam.
101+
const float ixc = clamp(ix, 0.0f, (float)(roi_in->width - 1));
102+
const float iyc = clamp(iy, 0.0f, (float)(roi_in->height - 1));
103+
104+
int x0 = (int)ixc;
105+
int y0 = (int)iyc;
106+
x0 = clamp(x0, 0, roi_in->width - 2);
107+
y0 = clamp(y0, 0, roi_in->height - 2);
108+
const float dx0 = ixc - x0;
109+
const float dy0 = iyc - y0;
110+
111+
float4 in00 = in[mad24(y0, roi_in->width, x0)];
112+
float4 in10 = in[mad24(y0, roi_in->width, x0 + 1)];
113+
float4 in01 = in[mad24(y0 + 1, roi_in->width, x0)];
114+
float4 in11 = in[mad24(y0 + 1, roi_in->width, x0 + 1)];
115+
116+
float4 val = in00 * (1.0f - dx0) * (1.0f - dy0) +
117+
in10 * dx0 * (1.0f - dy0) +
118+
in01 * (1.0f - dx0) * dy0 +
119+
in11 * dx0 * dy0;
120+
write_imagef(out, (int2)(x, y), val);
121+
}
72122
}
73123

74124
kernel void
@@ -77,15 +127,65 @@ retouch_copy_buffer_to_buffer(global float4 *in,
77127
global float4 *out,
78128
global dt_iop_roi_t *roi_out,
79129
const int xoffs,
80-
const int yoffs)
130+
const int yoffs,
131+
const float angle,
132+
const float cx,
133+
const float cy)
81134
{
82135
const int x = get_global_id(0);
83136
const int y = get_global_id(1);
84137

85138
if(x >= roi_out->width || y >= roi_out->height) return;
86-
if(x + xoffs >= roi_in->width || y + yoffs >= roi_in->height) return;
87139

88-
out[mad24(y, roi_out->width, x)] = in[mad24(y + yoffs, roi_in->width, x + xoffs)];
140+
// skip the rotation math for negligibly small angles (cheaper, and avoids
141+
// resampling artifacts on a near-zero rotation from repeated UI actions)
142+
if (fabs(angle) < 0.01f)
143+
{
144+
if(x + xoffs >= roi_in->width || y + yoffs >= roi_in->height || x + xoffs < 0 || y + yoffs < 0) return;
145+
out[mad24(y, roi_out->width, x)] = in[mad24(y + yoffs, roi_in->width, x + xoffs)];
146+
}
147+
else
148+
{
149+
// same rotation as the on-screen source outline, so the overlay marks the
150+
// region actually copied (see rt_copy_in_to_out in retouch.c)
151+
const float c = dtcl_cos(angle);
152+
const float s = dtcl_sin(angle);
153+
// (cx, cy) is the mask centroid (rotation pivot), in roi_out-local coords
154+
const float cx_source = cx + xoffs;
155+
const float cy_source = cy + yoffs;
156+
157+
const float sx = x + xoffs;
158+
const float sy = y + yoffs;
159+
const float rx = sx - cx_source;
160+
const float ry = sy - cy_source;
161+
const float ix = cx_source + rx * c - ry * s;
162+
const float iy = cy_source + rx * s + ry * c;
163+
164+
// Edge-clamp (replicate) out-of-bounds samples instead of zero-filling:
165+
// rt_compute_roi_in grows roi_in to the rotation-aware source area, but not
166+
// past the image border, so a rotated source near the edge can still sample
167+
// just outside roi_in. Replicating avoids a hard black seam.
168+
const float ixc = clamp(ix, 0.0f, (float)(roi_in->width - 1));
169+
const float iyc = clamp(iy, 0.0f, (float)(roi_in->height - 1));
170+
171+
int x0 = (int)ixc;
172+
int y0 = (int)iyc;
173+
x0 = clamp(x0, 0, roi_in->width - 2);
174+
y0 = clamp(y0, 0, roi_in->height - 2);
175+
const float dx0 = ixc - x0;
176+
const float dy0 = iyc - y0;
177+
178+
float4 in00 = in[mad24(y0, roi_in->width, x0)];
179+
float4 in10 = in[mad24(y0, roi_in->width, x0 + 1)];
180+
float4 in01 = in[mad24(y0 + 1, roi_in->width, x0)];
181+
float4 in11 = in[mad24(y0 + 1, roi_in->width, x0 + 1)];
182+
183+
float4 val = in00 * (1.0f - dx0) * (1.0f - dy0) +
184+
in10 * dx0 * (1.0f - dy0) +
185+
in01 * (1.0f - dx0) * dy0 +
186+
in11 * dx0 * dy0;
187+
out[mad24(y, roi_out->width, x)] = val;
188+
}
89189
}
90190

91191
kernel void

src/develop/masks.h

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -346,8 +346,8 @@ typedef struct dt_masks_form_t
346346
dt_masks_type_t type;
347347
const dt_masks_functions_t *functions;
348348

349-
// position of the source (used only for clone)
350-
float source[2];
349+
// position of the source (used only for clone). [0]=dx, [1]=dy, [2]=angle
350+
float source[3];
351351
// name of the form
352352
char name[128];
353353
// id used to store the form
@@ -401,6 +401,14 @@ typedef struct dt_masks_form_gui_t
401401
gboolean form_selected;
402402
gboolean border_selected;
403403
gboolean source_selected;
404+
gboolean source_rotating;
405+
gboolean counter_rotate_source;
406+
// joint rotation grabbed from the source shape: the mouse circles the source,
407+
// so its angular sweep must be measured about the source centroid (not the
408+
// destination centroid) to keep the rotation gain symmetric with grabbing the
409+
// target. The applied angle is identical either way; only the pivot used to
410+
// read the mouse motion differs.
411+
gboolean rotate_about_source;
404412
gboolean pivot_selected;
405413
gboolean select_only_border;
406414
dt_masks_edit_mode_t edit_mode;
@@ -1133,6 +1141,26 @@ void dt_masks_closest_point(const int count,
11331141
float *x,
11341142
float *y);
11351143

1144+
/* Rotate the control points of a path/brush outline in screen space and project
1145+
them back to normalized image coordinates. `gpt_points` is the gui display
1146+
buffer (interleaved x,y) whose first `nb*3` pairs are the control points,
1147+
stored per node as ctrl1, corner, ctrl2; `points_count` is its number of
1148+
(x,y) pairs. Each control point is rotated by (cos_a, sin_a) around the screen
1149+
pivot (cx, cy), back-transformed through the pipe in a single batch, and
1150+
written to `out` (normalized, same interleaving, nb*6 floats). Shared by the
1151+
path and brush rotate gestures. */
1152+
void dt_masks_rotate_ctrl_points(dt_develop_t *dev,
1153+
const float *const gpt_points,
1154+
const int points_count,
1155+
const int nb,
1156+
const float cx,
1157+
const float cy,
1158+
const float cos_a,
1159+
const float sin_a,
1160+
const float iwidth,
1161+
const float iheight,
1162+
float *const out);
1163+
11361164
/* draw a line from -> to with an arrow at the end.
11371165
if touch_dest is true then the arrow will be at the
11381166
(to_x, to_y) location, otherwise a small space will

0 commit comments

Comments
 (0)