pub mod dither;
pub mod filter;
mod utils;
pub mod colour;
pub mod effect;
pub mod prelude {
pub use crate::dither;
pub use crate::filter::filters;
pub use crate::effect::Effect;
pub use crate::effect::Affectable;
pub use crate::colour::gradient::{
IntoGradient,
IntoGradientHsl,
IntoGradientLch,
IntoGradientOklch,
};
pub use crate::colour::colours::srgb as SrgbColour;
pub use crate::colour::palettes;
}
#[macro_export]
macro_rules! gradient_map {
[$($threshold:expr => $color:expr),*] => {
&[
$(
($color, $threshold)
),*
]
};
}
pub type GradientMap<'a, Color> = &'a [(Color, f32)];
#[cfg(test)]
mod test {
use std::error::Error;
use image::{DynamicImage, ImageResult, GenericImageView, imageops};
use palette::{Srgb, named};
use crate::{
colour::{utils::ONE_BIT},
prelude::{*, palettes::{WEB_SAFE, EIGHT_BIT}}, dither::{FLOYD_STEINBERG, JARVIS_JUDICE_NINKE, STUCKI, ATKINSON, BURKES, SIERRA, SIERRA_TWO_ROW, SIERRA_LITE, bayer::Bayer},
};
type UtilResult<T> = Result<T,Box<dyn Error>>;
const IMAGE_URL: &'static str = "https://clipart-library.com/image_gallery/n781743.png";
const MAX_DIM: Option<usize> = Some(500);
fn get_image() -> UtilResult<DynamicImage> {
let img_bytes = reqwest::blocking::get(IMAGE_URL)?.bytes()?;
let image = image::load_from_memory(&img_bytes)?;
let image = if let Some(max_dim) = MAX_DIM {
let (x, y) = image.dimensions();
if max_dim < x.max(y) as usize {
let image = ℑlet factor = max_dim as f32 / x.max(y) as f32;
let mul = |int: u32, float: f32| (int as f32 * float) as u32;
image.resize(mul(x, factor), mul(y, factor), imageops::Nearest)
} else { image }
} else { image };
Ok(image)
}
#[test]
fn dither_test() -> UtilResult<()> {
let image = get_image()?;
let palette = [
named::PURPLE.into_format().build_gradient_lch(10),
named::GOLD.into_format().build_gradient_lch(10),
].concat();
dither(&image, ONE_BIT.to_vec(), Some("-mono"))?;
dither(&image, WEB_SAFE.to_vec(), Some("-web-safe"))?;
dither(&image, EIGHT_BIT.to_vec(), Some("-8-bit"))?;
dither(&image, palette, Some("-custom-palette"))?;
Ok(())
}
#[test]
fn filter_effects_test() -> UtilResult<()> {
let image = get_image()?;
image
.clone()
.apply(&filters::HueRotate(180.0))
.save("data/colour/rotate-hue-180.png")?;
image
.clone()
.apply(&filters::Brighten( 0.2))
.save("data/colour/brighten+0.2.png")?;
image
.clone()
.apply(&filters::Brighten(-0.2))
.save("data/colour/brighten-0.2.png")?;
image
.clone()
.apply(&filters::Saturate( 0.2))
.save("data/colour/saturate+0.2.png")?;
image
.clone()
.apply(&filters::Saturate(-0.2))
.save("data/colour/saturate-0.2.png")?;
image
.clone()
.apply(&filters::Contrast(0.5))
.save("data/colour/contrast.0.5.png")?;
image
.clone()
.apply(&filters::Contrast(1.5))
.save("data/colour/contrast.1.5.png")?;
let _gradient_map = [
(Srgb::new(0.0, 0.0, 1.0), 0.00),
(Srgb::new(1.0, 0.0, 0.0), 0.50),
(Srgb::new(0.0, 1.0, 0.0), 1.00),
];
let mut gradient_map = filters::GradientMap::new();
gradient_map
.add_entry(Srgb::new(0.0, 0.0, 1.0), 0.00)
.add_entry(Srgb::new(1.0, 0.0, 0.0), 0.50)
.add_entry(Srgb::new(0.0, 1.0, 0.0), 1.00);
image
.clone()
.apply(&gradient_map)
.save("data/colour/gradient-mapped.png")?;
let hue_palette = vec![180.0, 300.0];
image
.clone()
.apply(&filters::QuantizeHue::with_hues(hue_palette))
.save("data/colour/quantize-hue.png")?;
image
.clone()
.apply(&filters::MultiplyHue(4.0))
.save("data/colour/multiply-hue.4.0.png")?;
image
.clone()
.apply(&filters::MultiplyHue(12.0))
.save("data/colour/multiply-hue.12.0.png")?;
Ok(())
}
fn dither(
image: &DynamicImage,
palette: Vec<Srgb>,
opt_postfix: Option<&str>,
) -> ImageResult<()> {
let postfix = opt_postfix.unwrap_or("");
let error_propagators = vec![
FLOYD_STEINBERG,
JARVIS_JUDICE_NINKE,
STUCKI,
ATKINSON,
BURKES,
SIERRA,
SIERRA_TWO_ROW,
SIERRA_LITE
];
for propagator in error_propagators.into_iter() {
image.clone()
.apply(&propagator.with_palette(palette.clone()))
.save(format!("data/dither/{}{}.png", propagator.name, postfix))?;
}
image.clone().apply(&Bayer::new(2, palette.clone()))
.save(format!("data/dither/bayer-2x2{}.png", postfix))?;
image.clone().apply(&Bayer::new(4, palette.clone()))
.save(format!("data/dither/bayer-4x4{}.png", postfix))?;
image.clone().apply(&Bayer::new(8, palette.clone()))
.save(format!("data/dither/bayer-8x8{}.png", postfix))?;
image.clone().apply(&Bayer::new(16, palette.clone()))
.save(format!("data/dither/bayer-16x16{}.png", postfix))?;
Ok(())
}
}