第一步:创建项目
bash
cargo new image-processor
cd image-processorCargo.toml
toml
[package]
name = "image-processor"
version = "0.1.0"
edition = "2021"
[dependencies]
clap = { version = "4", features = ["derive"] }
image = "0.24"
tokio = { version = "1", features = ["full"] }
rayon = "1"
anyhow = "1"
tracing = "0.1"
tracing-subscriber = "0.3"
walkdir = "2"
indicatif = "0.17" # 进度条第二步:图片格式
src/utils/format.rs
rust
▶ Runuse std::path::Path;
/// 支持的图片格式
#[derive(Debug, Clone, Copy)]
pub enum ImageFormat {
Jpeg,
Png,
Gif,
WebP,
Bmp,
Tiff,
}
impl ImageFormat {
/// 从扩展名解析
pub fn from_extension(ext: &str) -> Option<Self> {
match ext.to_lowercase().as_str() {
"jpg" | "jpeg" => Some(Self::Jpeg),
"png" => Some(Self::Png),
"gif" => Some(Self::Gif),
"webp" => Some(Self::WebP),
"bmp" => Some(Self::Bmp),
"tiff" | "tif" => Some(Self::Tiff),
_ => None,
}
}
/// 从路径解析
pub fn from_path(path: &Path) -> Option<Self> {
path.extension()
.and_then(|e| e.to_str())
.and_then(Self::from_extension)
}
/// 获取扩展名
pub fn extension(&self) -> &'static str {
match self {
Self::Jpeg => "jpg",
Self::Png => "png",
Self::Gif => "gif",
Self::WebP => "webp",
Self::Bmp => "bmp",
Self::Tiff => "tiff",
}
}
/// 转换为 image crate 格式
pub fn to_image_format(&self) -> image::ImageFormat {
match self {
Self::Jpeg => image::ImageFormat::Jpeg,
Self::Png => image::ImageFormat::Png,
Self::Gif => image::ImageFormat::Gif,
Self::WebP => image::ImageFormat::WebP,
Self::Bmp => image::ImageFormat::Bmp,
Self::Tiff => image::ImageFormat::Tiff,
}
}
}第三步:调整大小
src/processor/resize.rs
rust
▶ Runuse image::{DynamicImage, GenericImageView, imageops::FilterType};
/// 调整大小选项
pub struct ResizeOptions {
/// 宽度
pub width: Option<u32>,
/// 高度
pub height: Option<u32>,
/// 是否保持宽高比
pub maintain_aspect: bool,
/// 滤镜类型
pub filter: FilterType,
}
impl Default for ResizeOptions {
fn default() -> Self {
Self {
width: None,
height: None,
maintain_aspect: true,
filter: FilterType::Lanczos3,
}
}
}
impl ResizeOptions {
/// 创建新选项
pub fn new() -> Self {
Self::default()
}
/// 设置宽度
pub fn width(mut self, width: u32) -> Self {
self.width = Some(width);
self
}
/// 设置高度
pub fn height(mut self, height: u32) -> Self {
self.height = Some(height);
self
}
/// 设置是否保持宽高比
pub fn maintain_aspect(mut self, maintain: bool) -> Self {
self.maintain_aspect = maintain;
self
}
}
/// 调整图片大小
pub fn resize(image: &DynamicImage, options: &ResizeOptions) -> DynamicImage {
let (orig_width, orig_height) = image.dimensions();
let (new_width, new_height) = match (options.width, options.height) {
(Some(w), Some(h)) => {
if options.maintain_aspect {
// 保持宽高比
let aspect = orig_width as f32 / orig_height as f32;
let target_aspect = w as f32 / h as f32;
if aspect > target_aspect {
(w, (w as f32 / aspect) as u32)
} else {
((h as f32 * aspect) as u32, h)
}
} else {
(w, h)
}
}
(Some(w), None) => {
let aspect = orig_height as f32 / orig_width as f32;
(w, (w as f32 * aspect) as u32)
}
(None, Some(h)) => {
let aspect = orig_width as f32 / orig_height as f32;
((h as f32 * aspect) as u32, h)
}
(None, None) => return image.clone(),
};
image.resize(new_width, new_height, options.filter)
}
/// 裁剪图片
pub fn crop(image: &DynamicImage, x: u32, y: u32, width: u32, height: u32) -> DynamicImage {
image.crop_imm(x, y, width, height)
}
/// 缩放到适合的尺寸(不超过指定大小)
pub fn fit(image: &DynamicImage, max_width: u32, max_height: u32) -> DynamicImage {
let (orig_width, orig_height) = image.dimensions();
if orig_width <= max_width && orig_height <= max_height {
return image.clone();
}
let width_ratio = max_width as f32 / orig_width as f32;
let height_ratio = max_height as f32 / orig_height as f32;
let ratio = width_ratio.min(height_ratio);
let new_width = (orig_width as f32 * ratio) as u32;
let new_height = (orig_height as f32 * ratio) as u32;
image.resize(new_width, new_height, FilterType::Lanczos3)
}第四步:格式转换
src/processor/convert.rs
rust
▶ Runuse image::{DynamicImage, ImageFormat};
use std::path::Path;
use crate::utils::format::ImageFormat;
/// 格式转换选项
pub struct ConvertOptions {
/// 输出格式
pub format: ImageFormat,
/// JPEG 质量 (1-100)
pub quality: u8,
}
impl ConvertOptions {
/// 创建新选项
pub fn new(format: ImageFormat) -> Self {
Self {
format,
quality: 85,
}
}
/// 设置质量
pub fn quality(mut self, quality: u8) -> Self {
self.quality = quality.min(100).max(1);
self
}
}
/// 转换图片格式
pub fn convert(image: &DynamicImage, options: &ConvertOptions) -> Vec<u8> {
let mut buffer = std::io::Cursor::new(Vec::new());
match options.format {
ImageFormat::Jpeg => {
let mut encoder = image::codecs::jpeg::JpegEncoder::new_with_quality(
&mut buffer,
options.quality,
);
encoder.encode(
image.to_rgb8().as_raw(),
image.width(),
image.height(),
image::ExtendedColorType::Rgb8,
).ok();
}
_ => {
image.write_to(&mut buffer, options.format.to_image_format()).ok();
}
}
buffer.into_inner()
}
/// 保存图片
pub fn save(image: &DynamicImage, path: &Path, format: ImageFormat) -> anyhow::Result<()> {
image.save_with_format(path, format.to_image_format())?;
Ok(())
}第五步:压缩优化
src/processor/compress.rs
rust
▶ Runuse image::{DynamicImage, ImageBuffer, Rgb};
/// 压缩选项
pub struct CompressOptions {
/// 质量 (1-100)
pub quality: u8,
/// 是否移除元数据
pub strip_metadata: bool,
}
impl Default for CompressOptions {
fn default() -> Self {
Self {
quality: 80,
strip_metadata: true,
}
}
}
impl CompressOptions {
pub fn new() -> Self {
Self::default()
}
pub fn quality(mut self, quality: u8) -> Self {
self.quality = quality.min(100).max(1);
self
}
}
/// 压缩图片
pub fn compress(image: &DynamicImage, options: &CompressOptions) -> DynamicImage {
// 简单的颜色量化压缩
let quality_factor = options.quality as f32 / 100.0;
let rgb_image = image.to_rgb8();
let (width, height) = rgb_image.dimensions();
let compressed: ImageBuffer<Rgb<u8>, Vec<u8>> = ImageBuffer::from_fn(
width, height,
|x, y| {
let pixel = rgb_image.get_pixel(x, y);
// 减少颜色深度
let reduce = |v: u8| -> u8 {
let factor = if quality_factor < 0.5 {
32.0 // 更激进的量化
} else {
16.0
};
((v as f32 / factor).round() * factor) as u8
};
Rgb([reduce(pixel[0]), reduce(pixel[1]), reduce(pixel[2])])
}
);
DynamicImage::ImageRgb8(compressed)
}
/// 计算图片大小(字节)
pub fn calculate_size(image: &DynamicImage, format: ImageFormat) -> usize {
let mut buffer = std::io::Cursor::new(Vec::new());
image.write_to(&mut buffer, format.to_image_format()).ok();
buffer.into_inner().len()
}