@@ -25,54 +25,50 @@ pub struct EyeRegion {
2525pub fn preprocess_image_from_memory (
2626 image_data : & [ u8 ] ,
2727 target_size : u32 ,
28- extract_subject : bool ,
29- remove_shadows : bool ,
3028) -> Result < Array2 < f32 > > {
3129 // Load image from memory and convert to grayscale
3230 let img = image:: load_from_memory ( image_data) ?. to_luma8 ( ) ;
3331
34- preprocess_image_internal ( img, target_size, extract_subject , remove_shadows )
32+ preprocess_image_internal ( img, target_size)
3533}
3634
3735/// Loads and preprocesses an image for string art generation
3836pub fn load_and_preprocess_image (
3937 image_path : & str ,
4038 target_size : u32 ,
41- extract_subject : bool ,
42- remove_shadows : bool ,
4339) -> Result < Array2 < f32 > > {
4440 // Load image and convert to grayscale
4541 let img = image:: open ( image_path) ?. to_luma8 ( ) ;
4642
47- preprocess_image_internal ( img, target_size, extract_subject , remove_shadows )
43+ preprocess_image_internal ( img, target_size)
4844}
4945
5046/// Internal preprocessing logic shared by both file and memory-based functions
5147fn preprocess_image_internal (
5248 img : GrayImage ,
5349 target_size : u32 ,
54- extract_subject : bool ,
55- remove_shadows : bool ,
5650) -> Result < Array2 < f32 > > {
5751 let ( width, height) = img. dimensions ( ) ;
5852
5953 println ! ( "Loaded image: {}x{}" , width, height) ;
6054
61- // Find the most common color for background
62- let background_color = get_most_common_color ( & img) ?;
63- println ! ( "Most common color: {}" , background_color) ;
64-
65- // Create square canvas with background color
66- let mut canvas = ImageBuffer :: from_pixel ( target_size, target_size, Luma ( [ background_color] ) ) ;
67-
55+
6856 // Calculate scaling to preserve aspect ratio
6957 let scale = ( target_size as f32 / width as f32 ) . min ( target_size as f32 / height as f32 ) ;
7058 let new_width = ( width as f32 * scale) as u32 ;
7159 let new_height = ( height as f32 * scale) as u32 ;
7260
7361 // Resize image
74- let resized = image:: imageops:: resize ( & img, new_width, new_height, image:: imageops:: FilterType :: Lanczos3 ) ;
62+ let resized = image:: imageops:: resize ( & img, new_width, new_height, image:: imageops:: FilterType :: Nearest ) ;
63+
64+ // Find the most common color for background
65+ let background_color = get_most_common_color ( & resized) ?;
66+ println ! ( "Most common color: {}" , background_color) ;
67+
68+ // Create square canvas with background color
69+ let mut canvas = ImageBuffer :: from_pixel ( target_size, target_size, Luma ( [ background_color] ) ) ;
7570
71+
7672 // Center the image on canvas
7773 let start_x = ( target_size - new_width) / 2 ;
7874 let start_y = ( target_size - new_height) / 2 ;
@@ -81,27 +77,15 @@ fn preprocess_image_internal(
8177 canvas. put_pixel ( start_x + x, start_y + y, * pixel) ;
8278 }
8379
84- let mut processed = canvas;
85-
86- // Remove shadows if requested
87- if remove_shadows {
88- processed = remove_shadows_clahe ( & processed) ?;
89- }
90-
91- // Extract subject if requested
92- if extract_subject {
93- processed = extract_subject_canny ( & processed) ?;
94- }
95-
9680 // Save processed image for reference (skip in WASM)
9781 if std:: env:: var ( "CARGO_CFG_TARGET_ARCH" ) . unwrap_or_default ( ) != "wasm32" {
98- if let Err ( e) = processed . save ( "string_art_input.png" ) {
82+ if let Err ( e) = canvas . save ( "string_art_input.png" ) {
9983 eprintln ! ( "Warning: Could not save processed image: {}" , e) ;
10084 }
10185 }
10286
10387 // Convert to ndarray
104- let array = image_to_array ( & processed ) ;
88+ let array = image_to_array ( & canvas ) ;
10589 Ok ( array)
10690}
10791
@@ -122,183 +106,6 @@ fn get_most_common_color(img: &GrayImage) -> Result<u8> {
122106 } )
123107}
124108
125- /// Remove shadows using CLAHE (Contrast Limited Adaptive Histogram Equalization)
126- #[ cfg( feature = "opencv-support" ) ]
127- fn remove_shadows_clahe ( img : & GrayImage ) -> Result < GrayImage > {
128- let ( width, height) = img. dimensions ( ) ;
129-
130- // Convert to OpenCV Mat
131- let mut cv_img = Mat :: new_rows_cols_with_default (
132- height as i32 ,
133- width as i32 ,
134- CV_8UC1 ,
135- opencv:: core:: Scalar :: all ( 0.0 ) ,
136- ) ?;
137-
138- // Copy data
139- for ( x, y, pixel) in img. enumerate_pixels ( ) {
140- cv_img. at_2d_mut :: < u8 > ( y as i32 , x as i32 ) ? = & pixel[ 0 ] ;
141- }
142-
143- // Apply CLAHE
144- let mut clahe = CLAHE :: create_default ( ) ?;
145- clahe. set_clip_limit ( 2.0 ) ?;
146- clahe. set_tile_grid_size ( Size :: new ( 8 , 8 ) ) ?;
147-
148- let mut enhanced = Mat :: default ( ) ;
149- clahe. apply ( & cv_img, & mut enhanced) ?;
150-
151- // Apply gamma correction
152- let mut corrected = Mat :: default ( ) ;
153- let gamma = 1.2 ;
154- let inv_gamma = 1.0 / gamma;
155-
156- let mut lookup_table = Vec :: with_capacity ( 256 ) ;
157- for i in 0 ..256 {
158- let val = ( ( i as f64 / 255.0 ) . powf ( inv_gamma) * 255.0 ) as u8 ;
159- lookup_table. push ( val) ;
160- }
161-
162- // Apply lookup table
163- for row in 0 ..enhanced. rows ( ) {
164- for col in 0 ..enhanced. cols ( ) {
165- let val = enhanced. at_2d :: < u8 > ( row, col) ?;
166- corrected. at_2d_mut :: < u8 > ( row, col) ? = & lookup_table[ * val as usize ] ;
167- }
168- }
169-
170- // Convert back to image
171- let mut result = ImageBuffer :: new ( width, height) ;
172- for ( x, y, pixel) in result. enumerate_pixels_mut ( ) {
173- let val = corrected. at_2d :: < u8 > ( y as i32 , x as i32 ) ?;
174- * pixel = Luma ( [ * val] ) ;
175- }
176-
177- Ok ( result)
178- }
179-
180- /// Extract subject using Canny edge detection
181- #[ cfg( feature = "opencv-support" ) ]
182- fn extract_subject_canny ( img : & GrayImage ) -> Result < GrayImage > {
183- let ( width, height) = img. dimensions ( ) ;
184-
185- // Convert to OpenCV Mat
186- let mut cv_img = Mat :: new_rows_cols_with_default (
187- height as i32 ,
188- width as i32 ,
189- CV_8UC1 ,
190- opencv:: core:: Scalar :: all ( 0.0 ) ,
191- ) ?;
192-
193- for ( x, y, pixel) in img. enumerate_pixels ( ) {
194- cv_img. at_2d_mut :: < u8 > ( y as i32 , x as i32 ) ? = & pixel[ 0 ] ;
195- }
196-
197- // Apply bilateral filter
198- let mut filtered = Mat :: default ( ) ;
199- imgproc:: bilateral_filter ( & cv_img, & mut filtered, 9 , 75.0 , 75.0 , opencv:: core:: BORDER_DEFAULT ) ?;
200-
201- // Apply Canny edge detection
202- let mut edges = Mat :: default ( ) ;
203- imgproc:: canny ( & filtered, & mut edges, 50.0 , 150.0 , 3 , false ) ?;
204-
205- // Dilate edges
206- let kernel = imgproc:: get_structuring_element (
207- imgproc:: MORPH_RECT ,
208- Size :: new ( 3 , 3 ) ,
209- opencv:: core:: Point :: new ( -1 , -1 ) ,
210- ) ?;
211- let mut edges_dilated = Mat :: default ( ) ;
212- imgproc:: dilate ( & edges, & mut edges_dilated, & kernel, opencv:: core:: Point :: new ( -1 , -1 ) , 1 , opencv:: core:: BORDER_CONSTANT , opencv:: core:: Scalar :: all ( 0.0 ) ) ?;
213-
214- // Find contours
215- let mut contours = opencv:: core:: Vector :: < opencv:: core:: Vector < opencv:: core:: Point > > :: new ( ) ;
216- let mut hierarchy = opencv:: core:: Vector :: < opencv:: core:: Vec4i > :: new ( ) ;
217- imgproc:: find_contours (
218- & edges_dilated,
219- & mut contours,
220- & mut hierarchy,
221- imgproc:: RETR_EXTERNAL ,
222- imgproc:: CHAIN_APPROX_SIMPLE ,
223- opencv:: core:: Point :: new ( 0 , 0 ) ,
224- ) ?;
225-
226- // Find largest contour
227- let mut largest_area = 0.0 ;
228- let mut largest_contour_idx = None ;
229-
230- for i in 0 ..contours. len ( ) {
231- let contour = contours. get ( i) ?;
232- let area = imgproc:: contour_area ( & contour, false ) ?;
233- if area > largest_area {
234- largest_area = area;
235- largest_contour_idx = Some ( i) ;
236- }
237- }
238-
239- // Create result image
240- let mut result = ImageBuffer :: from_pixel ( width, height, Luma ( [ 255u8 ] ) ) ; // White background
241-
242- if let Some ( idx) = largest_contour_idx {
243- let min_area = ( width * height) as f64 * 0.05 ; // At least 5% of image
244-
245- if largest_area > min_area {
246- // Create mask
247- let mut mask = Mat :: zeros ( height as i32 , width as i32 , CV_8UC1 ) ?. to_mat ( ) ?;
248- let contour = contours. get ( idx) ?;
249-
250- // Fill contour
251- let mut contours_vec = opencv:: core:: Vector :: new ( ) ;
252- contours_vec. push ( contour) ;
253- imgproc:: fill_poly (
254- & mut mask,
255- & contours_vec,
256- opencv:: core:: Scalar :: all ( 255.0 ) ,
257- opencv:: imgproc:: LINE_8 ,
258- 0 ,
259- opencv:: core:: Point :: new ( 0 , 0 ) ,
260- ) ?;
261-
262- // Apply Gaussian blur to mask for smooth transitions
263- let mut blurred_mask = Mat :: default ( ) ;
264- imgproc:: gaussian_blur ( & mask, & mut blurred_mask, Size :: new ( 21 , 21 ) , 0.0 , 0.0 , opencv:: core:: BORDER_DEFAULT ) ?;
265-
266- // Apply threshold
267- let mut final_mask = Mat :: default ( ) ;
268- imgproc:: threshold ( & blurred_mask, & mut final_mask, 30.0 , 255.0 , imgproc:: THRESH_BINARY ) ?;
269-
270- // Blend original image with white background using mask
271- for ( x, y, pixel) in result. enumerate_pixels_mut ( ) {
272- let mask_val = * final_mask. at_2d :: < u8 > ( y as i32 , x as i32 ) ? as f32 / 255.0 ;
273- let original_val = img. get_pixel ( x, y) [ 0 ] as f32 ;
274- let blended = ( 255.0 * ( 1.0 - mask_val) + original_val * mask_val) as u8 ;
275- * pixel = Luma ( [ blended] ) ;
276- }
277- } else {
278- // Contour too small, return original
279- result = img. clone ( ) ;
280- }
281- } else {
282- // No contours found, return original
283- result = img. clone ( ) ;
284- }
285-
286- Ok ( result)
287- }
288-
289- /// Fallback implementations when OpenCV is not available
290- #[ cfg( not( feature = "opencv-support" ) ) ]
291- fn remove_shadows_clahe ( img : & GrayImage ) -> Result < GrayImage > {
292- println ! ( "Warning: Shadow removal requires OpenCV support. Returning original image." ) ;
293- Ok ( img. clone ( ) )
294- }
295-
296- #[ cfg( not( feature = "opencv-support" ) ) ]
297- fn extract_subject_canny ( img : & GrayImage ) -> Result < GrayImage > {
298- println ! ( "Warning: Subject extraction requires OpenCV support. Returning original image." ) ;
299- Ok ( img. clone ( ) )
300- }
301-
302109/// Detect eyes in the image using OpenCV's Haar cascade
303110#[ cfg( feature = "opencv-support" ) ]
304111pub fn detect_eyes ( img : & Array2 < f32 > ) -> Result < Vec < EyeRegion > > {
@@ -371,12 +178,6 @@ pub fn detect_eyes(img: &Array2<f32>) -> Result<Vec<EyeRegion>> {
371178 println ! ( "Using fallback eye detection (no OpenCV support)" ) ;
372179
373180 // Use simple computer vision techniques to detect potential eye regions
374- detect_eyes_simple ( img)
375- }
376-
377- /// Simple eye detection without OpenCV
378- /// Looks for dark circular regions in the upper half of the image
379- fn detect_eyes_simple ( img : & Array2 < f32 > ) -> Result < Vec < EyeRegion > > {
380181 let ( height, width) = img. dim ( ) ;
381182 let mut eye_regions = Vec :: new ( ) ;
382183
0 commit comments