Skip to content

Commit a91957b

Browse files
rm wierd masking for image processing
1 parent 0ea545f commit a91957b

20 files changed

Lines changed: 22 additions & 296 deletions

StringArtRustImpl/processing.jpg

-1.28 MB
Loading

StringArtRustImpl/src/factories/generator_factory.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@ impl StringArtFactory {
2424
let target_image = load_and_preprocess_image(
2525
image_path,
2626
config.image_size as u32,
27-
config.extract_subject,
28-
config.remove_shadows,
2927
)?;
3028

3129
let residual_image = 255.0 - &target_image;
@@ -68,8 +66,6 @@ impl StringArtFactory {
6866
let target_image = preprocess_image_from_memory(
6967
image_data,
7068
config.image_size as u32,
71-
config.extract_subject,
72-
config.remove_shadows,
7369
)?;
7470

7571
let residual_image = 255.0 - &target_image;

StringArtRustImpl/src/image_processing.rs

Lines changed: 14 additions & 213 deletions
Original file line numberDiff line numberDiff line change
@@ -25,54 +25,50 @@ pub struct EyeRegion {
2525
pub 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
3836
pub 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
5147
fn 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")]
304111
pub 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

StringArtRustImpl/src/lib.rs

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,6 @@
3737
//! let config = StringArtConfig {
3838
//! num_nails: 720,
3939
//! image_size: 500,
40-
//! extract_subject: true,
41-
//! remove_shadows: true,
4240
//! preserve_eyes: true,
4341
//! ..Default::default()
4442
//! };
@@ -130,8 +128,6 @@ pub fn high_quality_generator(
130128
let config = StringArtConfig {
131129
num_nails: 1440,
132130
image_size: 800,
133-
extract_subject: true,
134-
remove_shadows: true,
135131
preserve_eyes: true,
136132
..Default::default()
137133
};
@@ -160,8 +156,6 @@ pub fn fast_generator(
160156
let config = StringArtConfig {
161157
num_nails: 360,
162158
image_size: 300,
163-
extract_subject: false,
164-
remove_shadows: false,
165159
preserve_eyes: false,
166160
..Default::default()
167161
};
@@ -215,8 +209,6 @@ mod tests {
215209
let config = default_config();
216210
assert_eq!(config.num_nails, 720);
217211
assert_eq!(config.image_size, 500);
218-
assert!(config.extract_subject);
219-
assert!(config.remove_shadows);
220212
assert!(config.preserve_eyes);
221213
}
222214
}

StringArtRustImpl/src/main.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,8 +194,6 @@ fn main() {
194194
let config = StringArtConfig {
195195
num_nails: nails,
196196
image_size: size,
197-
extract_subject: !matches.get_flag("no-subject-extraction"),
198-
remove_shadows: !matches.get_flag("no-shadow-removal"),
199197
preserve_eyes: !matches.get_flag("no-eye-protection"),
200198
preserve_negative_space: matches.get_flag("preserve-text"),
201199
negative_space_penalty,

StringArtRustImpl/src/masking/eye_enhancement.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ impl EyeEnhancementMask {
1616
impl MaskApplicator for EyeEnhancementMask {
1717
fn apply(&self, state: Arc<RwLock<StringArtState>>) -> Result<()> {
1818
let mut state = state.write().unwrap();
19-
2019
if state.config.preserve_eyes {
2120
let eyes = detect_eyes(&state.target_image).unwrap_or_else(|e| {
2221
eprintln!("Warning: Eye detection failed: {}. Eyes will not be enhanced.", e);
@@ -27,7 +26,6 @@ impl MaskApplicator for EyeEnhancementMask {
2726
state.eye_regions = eyes;
2827
state.eye_protection_mask = mask;
2928
}
30-
3129
Ok(())
3230
}
3331
}

StringArtRustImpl/src/state/config.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
pub struct StringArtConfig {
44
pub num_nails: usize,
55
pub image_size: usize,
6-
pub extract_subject: bool,
7-
pub remove_shadows: bool,
86
pub preserve_eyes: bool,
97
pub preserve_negative_space: bool,
108
pub negative_space_penalty: f32,
@@ -16,8 +14,6 @@ impl Default for StringArtConfig {
1614
Self {
1715
num_nails: 720, // 2 per degree
1816
image_size: 500,
19-
extract_subject: true,
20-
remove_shadows: true,
2117
preserve_eyes: true,
2218
preserve_negative_space: false,
2319
negative_space_penalty: 0.5,

0 commit comments

Comments
 (0)