Add error types and core image types: ImageFormat, ImageSource, Dimensions, QualityPreset
This commit is contained in:
@@ -1 +1,27 @@
|
||||
// Error types
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum PixstripError {
|
||||
#[error("Failed to load image '{path}': {reason}")]
|
||||
ImageLoad { path: PathBuf, reason: String },
|
||||
|
||||
#[error("Unsupported format: {format}")]
|
||||
UnsupportedFormat { format: String },
|
||||
|
||||
#[error("I/O error: {0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
|
||||
#[error("Output file already exists: {path}")]
|
||||
OutputExists { path: PathBuf },
|
||||
|
||||
#[error("Processing error in '{operation}': {reason}")]
|
||||
Processing { operation: String, reason: String },
|
||||
|
||||
#[error("Preset error: {0}")]
|
||||
Preset(String),
|
||||
|
||||
#[error("Configuration error: {0}")]
|
||||
Config(String),
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, PixstripError>;
|
||||
|
||||
@@ -1 +1,147 @@
|
||||
// Core domain types for image processing
|
||||
use std::fmt;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub enum ImageFormat {
|
||||
Jpeg,
|
||||
Png,
|
||||
WebP,
|
||||
Avif,
|
||||
Gif,
|
||||
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),
|
||||
"webp" => Some(Self::WebP),
|
||||
"avif" => Some(Self::Avif),
|
||||
"gif" => Some(Self::Gif),
|
||||
"tiff" | "tif" => Some(Self::Tiff),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extension(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Jpeg => "jpg",
|
||||
Self::Png => "png",
|
||||
Self::WebP => "webp",
|
||||
Self::Avif => "avif",
|
||||
Self::Gif => "gif",
|
||||
Self::Tiff => "tiff",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ImageSource {
|
||||
pub path: PathBuf,
|
||||
pub original_format: Option<ImageFormat>,
|
||||
}
|
||||
|
||||
impl ImageSource {
|
||||
pub fn from_path(path: impl AsRef<Path>) -> Self {
|
||||
let path = path.as_ref().to_path_buf();
|
||||
let original_format = path
|
||||
.extension()
|
||||
.and_then(|ext| ext.to_str())
|
||||
.and_then(ImageFormat::from_extension);
|
||||
Self {
|
||||
path,
|
||||
original_format,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Dimensions {
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
}
|
||||
|
||||
impl Dimensions {
|
||||
pub fn aspect_ratio(&self) -> f64 {
|
||||
self.width as f64 / self.height as f64
|
||||
}
|
||||
|
||||
pub fn fit_within(self, max: Dimensions, allow_upscale: bool) -> Dimensions {
|
||||
if !allow_upscale && self.width <= max.width && self.height <= max.height {
|
||||
return self;
|
||||
}
|
||||
|
||||
let scale_w = max.width as f64 / self.width as f64;
|
||||
let scale_h = max.height as f64 / self.height as f64;
|
||||
let scale = scale_w.min(scale_h);
|
||||
|
||||
if !allow_upscale && scale >= 1.0 {
|
||||
return self;
|
||||
}
|
||||
|
||||
Dimensions {
|
||||
width: (self.width as f64 * scale).round() as u32,
|
||||
height: (self.height as f64 * scale).round() as u32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Dimensions {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}x{}", self.width, self.height)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum QualityPreset {
|
||||
Maximum,
|
||||
High,
|
||||
Medium,
|
||||
Low,
|
||||
WebOptimized,
|
||||
}
|
||||
|
||||
impl QualityPreset {
|
||||
pub fn jpeg_quality(&self) -> u8 {
|
||||
match self {
|
||||
Self::Maximum => 95,
|
||||
Self::High => 85,
|
||||
Self::Medium => 72,
|
||||
Self::Low => 55,
|
||||
Self::WebOptimized => 65,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn png_level(&self) -> u8 {
|
||||
match self {
|
||||
Self::Maximum => 2,
|
||||
Self::High => 3,
|
||||
Self::Medium => 4,
|
||||
Self::Low => 6,
|
||||
Self::WebOptimized => 5,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn webp_quality(&self) -> f32 {
|
||||
match self {
|
||||
Self::Maximum => 95.0,
|
||||
Self::High => 85.0,
|
||||
Self::Medium => 72.0,
|
||||
Self::Low => 50.0,
|
||||
Self::WebOptimized => 75.0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn label(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Maximum => "Maximum",
|
||||
Self::High => "High",
|
||||
Self::Medium => "Medium",
|
||||
Self::Low => "Low",
|
||||
Self::WebOptimized => "Web-optimized",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user