use crate::error::PlatformError;
use crate::traits::media_runtime::{CompressImageRequest, ImageInfo};
use std::fs::File;
use std::io::BufReader;
use std::path::PathBuf;
pub fn get_image_info_desktop(uri: &str) -> Result<ImageInfo, PlatformError> {
let path = normalize_uri_to_path(uri)?;
let file = File::open(&path).map_err(|e| {
PlatformError::Platform(format!("Failed to open image file {}: {}", path, e))
})?;
let reader = BufReader::new(file);
let size = imagesize::reader_size(reader)
.map_err(|e| PlatformError::Platform(format!("Failed to read image dimensions: {}", e)))?;
let mime_type = infer_mime_type(&path);
Ok(ImageInfo {
width: size.width as u32,
height: size.height as u32,
mime_type: Some(mime_type),
})
}
fn normalize_uri_to_path(uri: &str) -> Result<String, PlatformError> {
let trimmed = uri.trim();
if trimmed.is_empty() {
return Err(PlatformError::Platform("URI is empty".to_string()));
}
let path = if let Some(stripped) = trimmed.strip_prefix("file://") {
stripped
} else {
trimmed
};
if path.contains("://") {
return Err(PlatformError::Platform(format!(
"Unsupported URI scheme: {}",
path
)));
}
Ok(path.to_string())
}
fn infer_mime_type(path: &str) -> String {
let lower = path.to_ascii_lowercase();
if lower.ends_with(".jpg") || lower.ends_with(".jpeg") {
"image/jpeg"
} else if lower.ends_with(".png") {
"image/png"
} else if lower.ends_with(".gif") {
"image/gif"
} else if lower.ends_with(".webp") {
"image/webp"
} else if lower.ends_with(".bmp") {
"image/bmp"
} else if lower.ends_with(".svg") {
"image/svg+xml"
} else if lower.ends_with(".ico") {
"image/x-icon"
} else if lower.ends_with(".tiff") || lower.ends_with(".tif") {
"image/tiff"
} else if lower.ends_with(".heic") || lower.ends_with(".heif") {
"image/heic"
} else {
"image/jpeg" }
.to_string()
}
pub fn compress_image_desktop(request: &CompressImageRequest) -> Result<PathBuf, PlatformError> {
use image::ImageReader;
use image::codecs::jpeg::JpegEncoder;
let source_path = normalize_uri_to_path(&request.source_uri)?;
let img = ImageReader::open(&source_path)
.map_err(|e| PlatformError::Platform(format!("Failed to open image: {}", e)))?
.decode()
.map_err(|e| PlatformError::Platform(format!("Failed to decode image: {}", e)))?;
let processed_img = if request.max_width.is_some() || request.max_height.is_some() {
let (orig_width, orig_height) = (img.width(), img.height());
let (target_width, target_height) = calculate_resize_dimensions(
orig_width,
orig_height,
request.max_width,
request.max_height,
);
if target_width != orig_width || target_height != orig_height {
img.resize(
target_width,
target_height,
image::imageops::FilterType::Lanczos3,
)
} else {
img
}
} else {
img
};
if let Some(parent) = request.output_path.parent() {
std::fs::create_dir_all(parent).map_err(|e| {
PlatformError::Platform(format!("Failed to create output directory: {}", e))
})?;
}
let output_file = File::create(&request.output_path)
.map_err(|e| PlatformError::Platform(format!("Failed to create output file: {}", e)))?;
let quality = request.quality.clamp(1, 100) as u8;
let mut encoder = JpegEncoder::new_with_quality(output_file, quality);
encoder
.encode(
processed_img.as_bytes(),
processed_img.width(),
processed_img.height(),
processed_img.color().into(),
)
.map_err(|e| PlatformError::Platform(format!("Failed to encode JPEG: {}", e)))?;
Ok(request.output_path.clone())
}
fn calculate_resize_dimensions(
orig_width: u32,
orig_height: u32,
max_width: Option<u32>,
max_height: Option<u32>,
) -> (u32, u32) {
match (max_width, max_height) {
(Some(w), Some(h)) => {
let width_ratio = w as f64 / orig_width as f64;
let height_ratio = h as f64 / orig_height as f64;
let ratio = width_ratio.min(height_ratio);
if ratio < 1.0 {
(
(orig_width as f64 * ratio) as u32,
(orig_height as f64 * ratio) as u32,
)
} else {
(orig_width, orig_height)
}
}
(Some(w), None) => {
if w < orig_width {
let ratio = w as f64 / orig_width as f64;
(w, (orig_height as f64 * ratio) as u32)
} else {
(orig_width, orig_height)
}
}
(None, Some(h)) => {
if h < orig_height {
let ratio = h as f64 / orig_height as f64;
((orig_width as f64 * ratio) as u32, h)
} else {
(orig_width, orig_height)
}
}
(None, None) => {
(orig_width, orig_height)
}
}
}