Skip to content

Commit d5a7889

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 d5a7889

9 files changed

Lines changed: 1208 additions & 101 deletions

File tree

data/kernels/retouch.cl

Lines changed: 103 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,16 +59,64 @@ 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+
if (angle == 0.0f)
73+
{
74+
if(x + xoffs >= roi_in->width || y + yoffs >= roi_in->height || x + xoffs < 0 || y + yoffs < 0) return;
75+
const int idx = mad24(y + yoffs, roi_in->width, x + xoffs);
76+
write_imagef(out, (int2)(x, y), in[idx]);
77+
}
78+
else
79+
{
80+
// same rotation as the on-screen source outline, so the overlay marks the
81+
// region actually copied (see rt_copy_in_to_out in retouch.c)
82+
const float c = cos(angle);
83+
const float s = sin(angle);
84+
// (cx, cy) is the mask centroid (rotation pivot), in roi_out-local coords
85+
const float cx_source = cx + xoffs;
86+
const float cy_source = cy + yoffs;
87+
88+
const float sx = x + xoffs;
89+
const float sy = y + yoffs;
90+
const float rx = sx - cx_source;
91+
const float ry = sy - cy_source;
92+
const float ix = cx_source + rx * c - ry * s;
93+
const float iy = cy_source + rx * s + ry * c;
94+
95+
// Edge-clamp (replicate) out-of-bounds samples instead of zero-filling:
96+
// rt_compute_roi_in grows roi_in to the rotation-aware source area, but not
97+
// past the image border, so a rotated source near the edge can still sample
98+
// just outside roi_in. Replicating avoids a hard black seam.
99+
const float ixc = fmin(fmax(ix, 0.0f), (float)(roi_in->width - 1));
100+
const float iyc = fmin(fmax(iy, 0.0f), (float)(roi_in->height - 1));
101+
102+
int x0 = (int)ixc;
103+
int y0 = (int)iyc;
104+
x0 = clamp(x0, 0, roi_in->width - 2);
105+
y0 = clamp(y0, 0, roi_in->height - 2);
106+
const float dx0 = ixc - x0;
107+
const float dy0 = iyc - y0;
108+
109+
float4 in00 = in[mad24(y0, roi_in->width, x0)];
110+
float4 in10 = in[mad24(y0, roi_in->width, x0 + 1)];
111+
float4 in01 = in[mad24(y0 + 1, roi_in->width, x0)];
112+
float4 in11 = in[mad24(y0 + 1, roi_in->width, x0 + 1)];
113+
114+
float4 val = in00 * (1.0f - dx0) * (1.0f - dy0) +
115+
in10 * dx0 * (1.0f - dy0) +
116+
in01 * (1.0f - dx0) * dy0 +
117+
in11 * dx0 * dy0;
118+
write_imagef(out, (int2)(x, y), val);
119+
}
72120
}
73121

74122
kernel void
@@ -77,15 +125,63 @@ retouch_copy_buffer_to_buffer(global float4 *in,
77125
global float4 *out,
78126
global dt_iop_roi_t *roi_out,
79127
const int xoffs,
80-
const int yoffs)
128+
const int yoffs,
129+
const float angle,
130+
const float cx,
131+
const float cy)
81132
{
82133
const int x = get_global_id(0);
83134
const int y = get_global_id(1);
84135

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

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

91187
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)