use std::{fmt::Display, str::FromStr};
use image::DynamicImage;
use serde::{Deserialize, Serialize};
use crate::IiifError;
#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Quality {
Default,
Color,
Gray,
Bitonal,
}
impl FromStr for Quality {
type Err = IiifError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s_trimmed = s.trim().to_lowercase();
if s_trimmed.is_empty() {
return Err(IiifError::BadRequest("Invalid quelity".to_string()));
}
match s_trimmed.as_str() {
"default" => Ok(Quality::Default),
"color" => Ok(Quality::Color),
"gray" => Ok(Quality::Gray),
"bitonal" => Ok(Quality::Bitonal),
_ => Err(IiifError::BadRequest("Invalid quelity".to_string())),
}
}
}
impl Quality {
pub fn process(&self, image: DynamicImage) -> Result<DynamicImage, IiifError> {
match self {
Quality::Default => Ok(image),
Quality::Color => Ok(image),
Quality::Gray => Ok(image.grayscale()),
Quality::Bitonal => {
let gray_image = image.to_luma8();
let threshold = 170u8;
let binary_image = imageproc::map::map_pixels(&gray_image, |_x, _y, pixel| {
if pixel[0] > threshold {
image::Luma([255u8]) } else {
image::Luma([0u8]) }
});
Ok(image::DynamicImage::ImageLuma8(binary_image))
}
}
}
}
impl Display for Quality {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Quality::Default => write!(f, "default"),
Quality::Color => write!(f, "color"),
Quality::Gray => write!(f, "gray"),
Quality::Bitonal => write!(f, "bitonal"),
}
}
}
#[cfg(test)]
mod tests {
use crate::storage::{LocalStorage, Storage};
use super::*;
#[test]
fn test_quality_from_str() {
assert_eq!(Quality::from_str("default").unwrap(), Quality::Default);
assert_eq!(Quality::from_str("color").unwrap(), Quality::Color);
assert_eq!(Quality::from_str("gray").unwrap(), Quality::Gray);
assert_eq!(Quality::from_str("bitonal").unwrap(), Quality::Bitonal);
assert_eq!(Quality::from_str("Default").unwrap(), Quality::Default);
assert!(Quality::from_str("").is_err());
assert!(Quality::from_str("invalid").is_err());
}
#[test]
fn test_quality_display() {
assert_eq!(format!("{}", Quality::Default), "default");
assert_eq!(format!("{}", Quality::Color), "color");
assert_eq!(format!("{}", Quality::Gray), "gray");
assert_eq!(format!("{}", Quality::Bitonal), "bitonal");
}
#[test]
fn test_quality_process() {
let storage = LocalStorage::new("./fixtures", "./fixtures/out");
let cases = vec![
("default", 300, 200),
("color", 300, 200),
("gray", 300, 200),
("bitonal", 300, 200),
];
for case in cases {
let quality = case.0.parse::<Quality>().unwrap();
let image = storage.get_origin_file("demo.jpg").unwrap();
let image = image::load_from_memory(&image).unwrap();
let processed_image = quality.process(image).unwrap();
assert_eq!(processed_image.width(), case.1);
assert_eq!(processed_image.height(), case.2);
}
}
}