@@ -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
74124kernel 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
91191kernel void
0 commit comments