init
This commit is contained in:
113
src/image_processor.rs
Normal file
113
src/image_processor.rs
Normal file
@@ -0,0 +1,113 @@
|
||||
use std::path::Path;
|
||||
use std::fs;
|
||||
use image::{GenericImageView, ImageFormat};
|
||||
use uuid::Uuid;
|
||||
use crate::config::StorageConfig;
|
||||
|
||||
pub struct ImageProcessor {
|
||||
pub config: StorageConfig,
|
||||
}
|
||||
|
||||
impl ImageProcessor {
|
||||
pub fn new(config: StorageConfig) -> Self {
|
||||
// 确保目录存在
|
||||
fs::create_dir_all(&config.images_dir).unwrap_or(());
|
||||
fs::create_dir_all(&config.thumbnails_dir).unwrap_or(());
|
||||
|
||||
Self { config }
|
||||
}
|
||||
|
||||
pub async fn save_image(&self, image_data: &[u8], filename: &str) -> Result<ImageInfo, Box<dyn std::error::Error>> {
|
||||
let image_id = Uuid::new_v4();
|
||||
let file_extension = Path::new(filename)
|
||||
.extension()
|
||||
.and_then(|ext| ext.to_str())
|
||||
.unwrap_or("jpg");
|
||||
|
||||
let new_filename = format!("{}.{}", image_id, file_extension);
|
||||
let image_path = self.config.images_dir.join(&new_filename);
|
||||
|
||||
// 保存原始图片
|
||||
fs::write(&image_path, image_data)?;
|
||||
|
||||
// 获取图片信息
|
||||
let img = image::load_from_memory(image_data)?;
|
||||
let (width, height) = img.dimensions();
|
||||
let file_size = image_data.len() as i64;
|
||||
|
||||
// 生成缩略图
|
||||
let thumbnail_path = self.config.thumbnails_dir.join(format!("thumb_{}", new_filename));
|
||||
self.generate_thumbnail(&img, &thumbnail_path, 300, 200)?;
|
||||
|
||||
Ok(ImageInfo {
|
||||
id: image_id,
|
||||
filename: new_filename,
|
||||
width: width as i32,
|
||||
height: height as i32,
|
||||
file_size,
|
||||
mime_type: self.detect_mime_type(file_extension),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn generate_thumbnail(
|
||||
&self,
|
||||
img: &image::DynamicImage,
|
||||
output_path: &Path,
|
||||
max_width: u32,
|
||||
max_height: u32,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let (width, height) = img.dimensions();
|
||||
|
||||
// 计算缩略图尺寸
|
||||
let ratio = width as f32 / height as f32;
|
||||
let (new_width, new_height) = if width > height {
|
||||
(max_width, (max_width as f32 / ratio) as u32)
|
||||
} else {
|
||||
((max_height as f32 * ratio) as u32, max_height)
|
||||
};
|
||||
|
||||
let thumbnail = img.thumbnail(new_width, new_height);
|
||||
thumbnail.save_with_format(output_path, ImageFormat::Jpeg)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn detect_mime_type(&self, extension: &str) -> String {
|
||||
match extension.to_lowercase().as_str() {
|
||||
"jpg" | "jpeg" => "image/jpeg".to_string(),
|
||||
"png" => "image/png".to_string(),
|
||||
"gif" => "image/gif".to_string(),
|
||||
"webp" => "image/webp".to_string(),
|
||||
_ => "application/octet-stream".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_image_url(&self, filename: &str) -> String {
|
||||
format!("/images/{}", filename)
|
||||
}
|
||||
|
||||
pub fn get_thumbnail_url(&self, filename: &str) -> String {
|
||||
format!("/thumbnails/thumb_{}", filename)
|
||||
}
|
||||
|
||||
pub async fn download_and_save_image(
|
||||
&self,
|
||||
url: &str,
|
||||
filename: &str,
|
||||
) -> Result<ImageInfo, Box<dyn std::error::Error>> {
|
||||
let response = reqwest::get(url).await?;
|
||||
let image_data = response.bytes().await?;
|
||||
|
||||
self.save_image(&image_data, filename).await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ImageInfo {
|
||||
pub id: Uuid,
|
||||
pub filename: String,
|
||||
pub width: i32,
|
||||
pub height: i32,
|
||||
pub file_size: i64,
|
||||
pub mime_type: String,
|
||||
}
|
||||
Reference in New Issue
Block a user