@@ -15,14 +15,23 @@ enum BorderShape {
1515 Rectangle { width : u32 , height : u32 } ,
1616}
1717
18+ #[ derive( Debug , Clone , Copy ) ]
19+ pub enum MixPositionWithPadding {
20+ TopLeft ( ( u32 , u32 ) ) , // top padding and left padding
21+ TopRight ( ( u32 , u32 ) ) , // top padding and right padding
22+ BottomLeft ( ( u32 , u32 ) ) , // bottom padding and left padding
23+ BottomRight ( ( u32 , u32 ) ) , // bottom padding and right padding
24+ }
25+
1826#[ derive( Debug , Clone , Copy , Derivative , Setters ) ]
1927#[ derivative( Default ) ]
2028#[ setters( prefix = "with_" ) ]
2129#[ non_exhaustive]
2230pub struct ShapeBase {
23- /// Position in background_image [0, 1] range
24- #[ derivative( Default ( value = "(0.5, 0.5)" ) ) ]
25- pub pos : ( f32 , f32 ) ,
31+ /// Position of the shape on the background image
32+ /// Specifies padding from the background edges
33+ #[ derivative( Default ( value = "MixPositionWithPadding::BottomRight((32, 32))" ) ) ]
34+ pub pos : MixPositionWithPadding ,
2635
2736 /// Border width in pixels
2837 #[ derivative( Default ( value = "2" ) ) ]
@@ -60,7 +69,6 @@ pub struct ShapeCircle {
6069pub struct ShapeRectangle {
6170 pub base : ShapeBase ,
6271
63- /// Size (width, height) in pixels
6472 #[ derivative( Default ( value = "(100, 100)" ) ) ]
6573 pub size : ( u32 , u32 ) ,
6674}
@@ -106,10 +114,30 @@ where
106114 let ( bg_width, bg_height) = background. dimensions ( ) ;
107115 let radius = circle. radius ;
108116 let diameter = ( radius * 2 ) as u32 ;
109- let center_x =
110- ( ( circle. base . pos . 0 * bg_width as f32 ) as u32 ) . clamp ( radius, bg_width as u32 - radius) ;
111- let center_y =
112- ( ( circle. base . pos . 1 * bg_height as f32 ) as u32 ) . clamp ( radius, bg_height as u32 - radius) ;
117+
118+ // Calculate center position based on MixPositionWithPadding
119+ let ( center_x, center_y) = match circle. base . pos {
120+ MixPositionWithPadding :: TopLeft ( ( pad_top, pad_left) ) => {
121+ let cx = ( pad_left + radius) . min ( bg_width - radius) ;
122+ let cy = ( pad_top + radius) . min ( bg_height - radius) ;
123+ ( cx. max ( radius) , cy. max ( radius) )
124+ }
125+ MixPositionWithPadding :: TopRight ( ( pad_top, pad_right) ) => {
126+ let cx = bg_width. saturating_sub ( pad_right + radius) . max ( radius) ;
127+ let cy = ( pad_top + radius) . min ( bg_height - radius) ;
128+ ( cx. min ( bg_width - radius) , cy. max ( radius) )
129+ }
130+ MixPositionWithPadding :: BottomLeft ( ( pad_bottom, pad_left) ) => {
131+ let cx = ( pad_left + radius) . min ( bg_width - radius) ;
132+ let cy = bg_height. saturating_sub ( pad_bottom + radius) . max ( radius) ;
133+ ( cx. max ( radius) , cy. min ( bg_height - radius) )
134+ }
135+ MixPositionWithPadding :: BottomRight ( ( pad_bottom, pad_right) ) => {
136+ let cx = bg_width. saturating_sub ( pad_right + radius) . max ( radius) ;
137+ let cy = bg_height. saturating_sub ( pad_bottom + radius) . max ( radius) ;
138+ ( cx. min ( bg_width - radius) , cy. min ( bg_height - radius) )
139+ }
140+ } ;
113141
114142 let cropped_camera = crop_image_by_pixel_type (
115143 camera_image,
@@ -181,10 +209,31 @@ where
181209 P : Pixel < Subpixel = u8 > + Copy ,
182210{
183211 let ( bg_width, bg_height) = background. dimensions ( ) ;
184- let x = ( rect. base . pos . 0 * bg_width as f32 ) as u32 ;
185- let y = ( rect. base . pos . 1 * bg_height as f32 ) as u32 ;
186212 let ( width, height) = rect. size ;
187213
214+ // Calculate top-left position based on MixPositionWithPadding
215+ let ( x, y) = match rect. base . pos {
216+ MixPositionWithPadding :: TopLeft ( ( pad_top, pad_left) ) => (
217+ pad_left. min ( bg_width. saturating_sub ( width) ) ,
218+ pad_top. min ( bg_height. saturating_sub ( height) ) ,
219+ ) ,
220+ MixPositionWithPadding :: TopRight ( ( pad_top, pad_right) ) => {
221+ let x = bg_width. saturating_sub ( width + pad_right) ;
222+ let y = pad_top. min ( bg_height. saturating_sub ( height) ) ;
223+ ( x, y)
224+ }
225+ MixPositionWithPadding :: BottomLeft ( ( pad_bottom, pad_left) ) => {
226+ let x = pad_left. min ( bg_width. saturating_sub ( width) ) ;
227+ let y = bg_height. saturating_sub ( height + pad_bottom) ;
228+ ( x, y)
229+ }
230+ MixPositionWithPadding :: BottomRight ( ( pad_bottom, pad_right) ) => {
231+ let x = bg_width. saturating_sub ( width + pad_right) ;
232+ let y = bg_height. saturating_sub ( height + pad_bottom) ;
233+ ( x, y)
234+ }
235+ } ;
236+
188237 let cropped_camera = crop_image_by_pixel_type (
189238 camera_image,
190239 width,
@@ -193,15 +242,14 @@ where
193242 rect. base . clip_pos ,
194243 ) ?;
195244
245+ let width = width. min ( bg_width. saturating_sub ( x) ) ;
246+ let height = height. min ( bg_height. saturating_sub ( y) ) ;
247+
196248 for cam_y in 0 ..height {
197249 for cam_x in 0 ..width {
198250 let bg_x = x + cam_x;
199251 let bg_y = y + cam_y;
200252
201- if bg_x >= bg_width || bg_y >= bg_height {
202- continue ;
203- }
204-
205253 let cam_pixel = cropped_camera. get_pixel ( cam_x, cam_y) ;
206254 background. put_pixel ( bg_x, bg_y, * cam_pixel) ;
207255 }
@@ -314,25 +362,40 @@ where
314362 // If the cropped image is smaller than target, pad with black
315363 if actual_crop_width < target_width || actual_crop_height < target_height {
316364 let mut result = ImageBuffer :: new ( target_width, target_height) ;
365+
366+ // Calculate offset position in target canvas based on clip_pos
367+ let offset_x =
368+ ( ( target_width - actual_crop_width) as f32 * clip_pos. 0 . clamp ( 0.0 , 1.0 ) ) . round ( ) as u32 ;
369+ let offset_y = ( ( target_height - actual_crop_height) as f32 * clip_pos. 1 . clamp ( 0.0 , 1.0 ) )
370+ . round ( ) as u32 ;
371+
317372 for y in 0 ..target_height {
318373 for x in 0 ..target_width {
319- if x < actual_crop_width && y < actual_crop_height {
320- let idx = ( y * actual_crop_width + x) as usize * channel_count;
321- let mut channels = [ 0u8 ; 4 ] ;
322- for c in 0 ..channel_count {
323- channels[ c] = cropped_buffer[ idx + c] ;
324- }
325- // Fill remaining channels with 255 (alpha) or 0
326- for c in channel_count..4 {
327- channels[ c] = if c == 3 { 255 } else { 0 } ;
374+ // Check if current pixel is within the image area (offset-adjusted)
375+ let rel_x = x. checked_sub ( offset_x) ;
376+ let rel_y = y. checked_sub ( offset_y) ;
377+
378+ if let ( Some ( rx) , Some ( ry) ) = ( rel_x, rel_y) {
379+ if rx < actual_crop_width && ry < actual_crop_height {
380+ let idx = ( ry * actual_crop_width + rx) as usize * channel_count;
381+ let mut channels = [ 0u8 ; 4 ] ;
382+ for c in 0 ..channel_count {
383+ channels[ c] = cropped_buffer[ idx + c] ;
384+ }
385+ // Fill remaining channels with 255 (alpha) or 0
386+ for c in channel_count..4 {
387+ channels[ c] = if c == 3 { 255 } else { 0 } ;
388+ }
389+ let pixel = P :: from_slice ( & channels[ ..channel_count] ) ;
390+ result. put_pixel ( x, y, * pixel) ;
391+ continue ;
328392 }
329- let pixel = P :: from_slice ( & channels[ ..channel_count] ) ;
330- result. put_pixel ( x, y, * pixel) ;
331- } else {
332- let black = [ 0u8 ; 4 ] ;
333- let pixel = P :: from_slice ( & black[ ..channel_count] ) ;
334- result. put_pixel ( x, y, * pixel) ;
335393 }
394+
395+ // Pad with black
396+ let black = [ 0u8 ; 4 ] ;
397+ let pixel = P :: from_slice ( & black[ ..channel_count] ) ;
398+ result. put_pixel ( x, y, * pixel) ;
336399 }
337400 }
338401 Ok ( result)
0 commit comments