Add error types and core image types: ImageFormat, ImageSource, Dimensions, QualityPreset

This commit is contained in:
2026-03-06 01:37:39 +02:00
parent dab049b0d3
commit 3e176e3d65
4 changed files with 303 additions and 2 deletions

View File

@@ -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>;

View File

@@ -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",
}
}
}