@@ -4,7 +4,6 @@ use crate::{
44 tracks:: video_frame_cache:: VideoImage ,
55} ;
66use image:: { RgbaImage , imageops} ;
7- use rayon:: prelude:: * ;
87use video_utils:: convert:: resize_rgba_image_contain;
98
109#[ derive(
@@ -108,8 +107,6 @@ impl CropFilter {
108107 Ok ( ( ) )
109108 }
110109
111- /// Apply circular crop with anti-aliasing for smooth edges.
112- /// The circle/ellipse is centered within the crop bounds and scaled to fill the area.
113110 fn apply_circular_crop (
114111 & self ,
115112 image : & mut RgbaImage ,
@@ -120,57 +117,39 @@ impl CropFilter {
120117 target_width : u32 ,
121118 target_height : u32 ,
122119 ) -> Result < ( ) > {
123- // 1. Extract the rectangular crop region
120+ // 1. Crop the rectangular region first
124121 let cropped = imageops:: crop ( image, px_left, px_top, px_width, px_height) ;
125- let cropped_image = cropped. to_image ( ) ;
126-
127- // 2. Resize cropped image to target dimensions (same as rectangle crop)
128- let resized = resize_rgba_image_contain ( cropped_image, target_width, target_height, false ) ?;
129-
130- // 3. Create output image
131- let mut result = RgbaImage :: new ( target_width, target_height) ;
132-
133- // 4. Calculate circle parameters based on TARGET size
134- let center_x = target_width as f32 / 2.0 ;
135- let center_y = target_height as f32 / 2.0 ;
136- // For true circle shape, use minimum dimension
137- let min_radius = ( target_width as f32 / 2.0 ) . min ( target_height as f32 / 2.0 ) ;
138- let radius_x = min_radius;
139- let radius_y = min_radius;
140-
141- // 5. Anti-aliasing edge distance
142- let aa_edge_distance = 1.0 ;
143-
144- // 6. Sample from resized image and apply circular mask
145- result
146- . par_enumerate_pixels_mut ( )
147- . for_each ( |( px, py, pixel) | {
148- // Get source pixel from resized image (same coordinates)
149- let src_pixel = resized. get_pixel ( px, py) ;
150-
151- // Check if inside the circle
152- let dx = px as f32 - center_x;
153- let dy = py as f32 - center_y;
154- let ellipse_dist = ( dx * dx) / ( radius_x * radius_x) + ( dy * dy) / ( radius_y * radius_y) ;
155-
156- if ellipse_dist <= 1.0 - aa_edge_distance / min_radius {
157- // Fully inside the circle
158- * pixel = * src_pixel;
159- } else if ellipse_dist <= 1.0 + aa_edge_distance / min_radius {
160- // Anti-aliasing zone
161- let aa_factor = ( 1.0 - ( ellipse_dist - 1.0 ) * min_radius / aa_edge_distance)
162- . clamp ( 0.0 , 1.0 ) ;
163- pixel[ 0 ] = src_pixel[ 0 ] ;
164- pixel[ 1 ] = src_pixel[ 1 ] ;
165- pixel[ 2 ] = src_pixel[ 2 ] ;
166- pixel[ 3 ] = ( ( src_pixel[ 3 ] as f32 * aa_factor) as u8 ) . min ( src_pixel[ 3 ] ) ;
167- } else {
168- // Outside the circle - transparent
169- pixel[ 3 ] = 0 ;
122+ let mut cropped_image = cropped. to_image ( ) ;
123+
124+ // 2. Calculate circle parameters (use center of the cropped region)
125+ let cx = px_width as f32 / 2.0 ;
126+ let cy = px_height as f32 / 2.0 ;
127+ let radius = px_width. min ( px_height) as f32 / 2.0 ;
128+
129+ // 3. Apply circular mask (pixels outside circle become transparent)
130+ for y in 0 ..px_height {
131+ for x in 0 ..px_width {
132+ let dx = x as f32 - cx;
133+ let dy = y as f32 - cy;
134+ let dist = ( dx * dx + dy * dy) . sqrt ( ) ;
135+
136+ if dist > radius {
137+ // Outside circle: set transparent
138+ let pixel = cropped_image. get_pixel_mut ( x, y) ;
139+ pixel[ 3 ] = 0 ; // alpha = 0
140+ } else if dist > radius - 1.0 {
141+ // Edge: apply anti-aliasing
142+ let alpha = ( radius - dist) . clamp ( 0.0 , 1.0 ) ;
143+ let pixel = cropped_image. get_pixel_mut ( x, y) ;
144+ pixel[ 3 ] = ( pixel[ 3 ] as f32 * alpha) as u8 ;
170145 }
171- } ) ;
146+ }
147+ }
148+
149+ // 4. Resize to target dimensions
150+ * image = resize_rgba_image_contain ( cropped_image, target_width, target_height, false ) ?;
151+ // *image = cropped_image;
172152
173- * image = result;
174153 Ok ( ( ) )
175154 }
176155}
0 commit comments