174 lines
5.1 KiB
Rust
174 lines
5.1 KiB
Rust
use crate::config::StorageConfig;
|
|
use image::GenericImageView;
|
|
use std::fs;
|
|
use std::path::Path;
|
|
use thiserror::Error;
|
|
use uuid::Uuid;
|
|
|
|
#[derive(Error, Debug)]
|
|
pub enum ImageProcessorError {
|
|
#[error("IO error: {0}")]
|
|
Io(#[from] std::io::Error),
|
|
#[error("Image processing error: {0}")]
|
|
Image(#[from] image::ImageError),
|
|
#[error("Invalid image data")]
|
|
InvalidData,
|
|
}
|
|
|
|
// 手动实现 From<reqwest::Error> 以支持 ? 操作符
|
|
impl From<reqwest::Error> for ImageProcessorError {
|
|
fn from(err: reqwest::Error) -> Self {
|
|
ImageProcessorError::Io(std::io::Error::new(
|
|
std::io::ErrorKind::Other,
|
|
err.to_string(),
|
|
))
|
|
}
|
|
}
|
|
|
|
impl ImageProcessorError {
|
|
pub fn is_send(&self) -> bool {
|
|
true
|
|
}
|
|
}
|
|
|
|
pub struct ImageProcessor {
|
|
pub config: StorageConfig,
|
|
}
|
|
|
|
impl ImageProcessor {
|
|
pub fn new(config: StorageConfig) -> Self {
|
|
// 确保目录存在
|
|
fs::create_dir_all(&config.images_dir).unwrap_or(());
|
|
|
|
Self { config }
|
|
}
|
|
|
|
pub async fn save_image(
|
|
&self,
|
|
image_data: &[u8],
|
|
filename: &str,
|
|
) -> Result<ImageInfo, ImageProcessorError> {
|
|
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;
|
|
|
|
// 不再生成缩略图,直接返回原图信息
|
|
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<(), ImageProcessorError> {
|
|
// 缩略图功能已移除,此函数不再执行任何操作
|
|
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 async fn resize_image_to_max(&self, image_data: &[u8], max_width: u32) -> Vec<u8> {
|
|
match image::load_from_memory(image_data) {
|
|
Ok(img) => {
|
|
let (width, height) = img.dimensions();
|
|
if width <= max_width {
|
|
return image_data.to_vec(); // 无需调整
|
|
}
|
|
|
|
let new_height = (height as f32 * max_width as f32 / width as f32) as u32;
|
|
let resized = img.thumbnail(max_width, new_height);
|
|
|
|
let mut output = Vec::new();
|
|
if resized
|
|
.write_to(
|
|
&mut std::io::Cursor::new(&mut output),
|
|
image::ImageOutputFormat::Jpeg(75),
|
|
)
|
|
.is_ok()
|
|
{
|
|
output
|
|
} else {
|
|
image_data.to_vec() // 失败时返回原图
|
|
}
|
|
}
|
|
Err(_) => image_data.to_vec(), // 解码失败时返回原图
|
|
}
|
|
}
|
|
|
|
pub fn get_image_url(&self, filename: &str) -> String {
|
|
format!("/images/{}", filename)
|
|
}
|
|
|
|
pub async fn download_and_save_image(
|
|
&self,
|
|
url: &str,
|
|
filename: &str,
|
|
) -> Result<ImageInfo, ImageProcessorError> {
|
|
let response = reqwest::get(url).await?;
|
|
let image_data = response.bytes().await?;
|
|
|
|
self.save_image(&image_data, filename).await
|
|
}
|
|
|
|
/// 检测图片格式
|
|
fn detect_image_format(&self, image_data: &[u8]) -> Option<String> {
|
|
if image_data.len() < 12 {
|
|
return None;
|
|
}
|
|
|
|
match &image_data[0..12] {
|
|
b"\xff\xd8\xff" => Some("JPEG".to_string()),
|
|
b"\x89PNG\r\n\x1a\n" => Some("PNG".to_string()),
|
|
b"GIF87a" => Some("GIF87a".to_string()),
|
|
b"GIF89a" => Some("GIF89a".to_string()),
|
|
b"BM" => Some("BMP".to_string()),
|
|
b"RIFF" if image_data.len() > 12 && &image_data[8..12] == b"WEBP" => {
|
|
Some("WebP".to_string())
|
|
}
|
|
_ => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[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,
|
|
} |