1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
| // src/processor.rs
use anyhow::Context;
use image::{DynamicImage, ImageEncoder, ImageFormat};
use rayon::prelude::*;
use std::path::Path;
use tokio::fs;
pub struct ImageProcessor {
config: ProcessingConfig,
progress: indicatif::ProgressBar,
}
#[derive(Clone)]
pub struct ProcessingConfig {
pub operation: Operation,
pub quality: u8,
pub width: Option<u32>,
pub height: Option<u32>,
pub threads: usize,
}
impl ImageProcessor {
pub fn new(config: ProcessingConfig) -> Self {
let progress = indicatif::ProgressBar::new_spinner();
progress.set_style(indicatif::ProgressStyle::default_bar()
.template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len} ({eta})")
.unwrap()
.progress_chars("#>-"));
Self { config, progress }
}
pub async fn process_directory(&self, input_dir: &Path, output_dir: &Path) -> Result<()> {
let entries = fs::read_dir(input_dir).await?;
let images: Vec<_> = entries
.filter_map(|e| e.ok())
.filter(|e| {
e.path().extension()
.and_then(|s| s.to_str())
.map(|s| matches!(s.to_lowercase().as_str(), "jpg" | "jpeg" | "png" | "webp"))
.unwrap_or(false)
})
.collect();
self.progress.set_length(images.len() as u64);
// 使用rayon并行处理
let pool = rayon::ThreadPoolBuilder::new()
.num_threads(self.config.threads)
.build()
.context("Failed to create thread pool")?;
pool.install(|| {
images.par_iter().for_each(|entry| {
if let Err(e) = self.process_single_image(entry.path(), output_dir) {
eprintln!("Error processing {:?}: {}", entry.path(), e);
}
self.progress.inc(1);
});
});
self.progress.finish_with_message("Processing complete!");
Ok(())
}
fn process_single_image(&self, input_path: std::path::PathBuf, output_dir: &Path) -> Result<()> {
let img = image::open(&input_path)
.map_err(|e| ImgProcError::Image(format!("Failed to load image: {}", e)))?;
let processed = match self.config.operation {
Operation::Resize => self.resize_image(img),
Operation::Compress => self.compress_image(img),
Operation::Grayscale => self.grayscale_image(img),
Operation::Convert => self.convert_format(img, ImageFormat::Jpeg),
Operation::Thumbnail => self.create_thumbnail(img),
}?;
let filename = input_path
.file_name()
.ok_or_else(|| ImgProcError::Config("Invalid filename".to_string()))?;
let output_path = output_dir.join(filename);
processed.save(output_path)
.map_err(|e| ImgProcError::Image(format!("Failed to save: {}", e)))?;
Ok(())
}
fn resize_image(&self, img: DynamicImage) -> Result<DynamicImage> {
let (width, height) = (
self.config.width.unwrap_or(img.width()),
self.config.height.unwrap_or(img.height()),
);
Ok(img.resize(
width,
height,
image::imageops::FilterType::Lanczos3,
))
}
fn compress_image(&self, img: DynamicImage) -> Result<DynamicImage> {
// 对于JPEG,直接调整质量参数
Ok(img)
}
fn grayscale_image(&self, img: DynamicImage) -> Result<DynamicImage> {
Ok(img.grayscale())
}
fn convert_format(&self, img: DynamicImage, format: ImageFormat) -> Result<DynamicImage> {
Ok(img) // 格式转换在保存时处理
}
fn create_thumbnail(&self, img: DynamicImage) -> Result<DynamicImage> {
const THUMB_SIZE: u32 = 150;
Ok(img.thumbnail(THUMB_SIZE, THUMB_SIZE))
}
}
|