versatiles_image 1.0.0

A toolbox for converting, checking and serving map tiles in various formats.
Documentation
use super::info::DynamicImageTraitInfo;
use anyhow::{Result, bail};
use fast_image_resize::{FilterType, ResizeAlg, ResizeOptions, Resizer};
use image::{DynamicImage, Rgb, imageops::overlay};
use imageproc::map::map_colors;

pub trait DynamicImageTraitOperation: DynamicImageTraitInfo {
	fn as_no_alpha(&self) -> Result<DynamicImage>;
	fn average_color(&self) -> Vec<u8>;
	fn get_extract(&self, x: f64, y: f64, w: f64, h: f64, width_dst: u32, height_dst: u32) -> Result<DynamicImage>;
	fn get_scaled_down(&self, factor: u32) -> Result<DynamicImage>;
	fn into_flattened(self, color: Rgb<u8>) -> Result<DynamicImage>;
	fn into_no_alpha_if_opaque(self) -> Result<DynamicImage>;
	fn into_no_alpha(self) -> Result<DynamicImage>;
	fn into_scaled_down(self, factor: u32) -> Result<DynamicImage>;
	fn make_opaque(&mut self) -> Result<()>;
	fn mut_color_values<F>(&mut self, f: F)
	where
		F: Fn(u8) -> u8;
	fn overlay(&mut self, top: &DynamicImage) -> Result<()>;
}

impl DynamicImageTraitOperation for DynamicImage
where
	DynamicImage: DynamicImageTraitInfo,
{
	fn as_no_alpha(&self) -> Result<DynamicImage> {
		Ok(match self {
			DynamicImage::ImageRgba8(_) => DynamicImage::from(self.to_rgb8()),
			DynamicImage::ImageLumaA8(_) => DynamicImage::from(self.to_luma8()),
			DynamicImage::ImageRgb8(_) | DynamicImage::ImageLuma8(_) => self.clone(),
			_ => bail!("Unsupported image type for removing alpha: {:?}", self.color()),
		})
	}

	fn average_color(&self) -> Vec<u8> {
		let img = self.resize_exact(1, 1, image::imageops::FilterType::Triangle);
		img.into_bytes()
	}

	fn get_extract(&self, x: f64, y: f64, w: f64, h: f64, width_dst: u32, height_dst: u32) -> Result<DynamicImage> {
		let mut dst_image = DynamicImage::new(width_dst, height_dst, self.color());
		Resizer::new().resize(self, &mut dst_image, &ResizeOptions::default().crop(x, y, w, h))?;
		Ok(dst_image)
	}

	fn get_scaled_down(&self, factor: u32) -> Result<DynamicImage> {
		assert!(factor > 0, "Scaling factor must be greater than zero");

		let mut dst_image = DynamicImage::new(self.width() / factor, self.height() / factor, self.color());
		Resizer::new().resize(
			self,
			&mut dst_image,
			&ResizeOptions::default().resize_alg(ResizeAlg::Convolution(FilterType::Box)),
		)?;

		Ok(dst_image)
	}

	fn into_flattened(self, color: Rgb<u8>) -> Result<DynamicImage> {
		if !self.has_alpha() {
			return Ok(self);
		}
		match self {
			DynamicImage::ImageRgba8(img) => {
				let c = [u16::from(color[0]), u16::from(color[1]), u16::from(color[2])];
				Ok(DynamicImage::from(map_colors(&img, |p| {
					if p[3] == 255 {
						Rgb([p[0], p[1], p[2]])
					} else {
						let a = u16::from(p[3]);
						let b = u16::from(255 - p[3]);
						Rgb([
							(((u16::from(p[0]) * a) + c[0] * b + 127) / 255) as u8,
							(((u16::from(p[1]) * a) + c[1] * b + 127) / 255) as u8,
							(((u16::from(p[2]) * a) + c[2] * b + 127) / 255) as u8,
						])
					}
				})))
			}
			_ => bail!("Unsupported image type {:?} for flattening", self.color()),
		}
	}

	fn into_no_alpha(self) -> Result<DynamicImage> {
		Ok(match self {
			DynamicImage::ImageRgba8(_) => DynamicImage::from(self.into_rgb8()),
			DynamicImage::ImageLumaA8(_) => DynamicImage::from(self.into_luma8()),
			DynamicImage::ImageRgb8(_) | DynamicImage::ImageLuma8(_) => self,
			_ => bail!("Unsupported image type for removing alpha: {:?}", self.color()),
		})
	}

	fn into_no_alpha_if_opaque(self) -> Result<DynamicImage> {
		if self.has_alpha() && self.is_opaque() {
			self.into_no_alpha()
		} else {
			Ok(self)
		}
	}

	fn into_scaled_down(self, factor: u32) -> Result<DynamicImage> {
		if factor == 1 {
			Ok(self)
		} else {
			self.get_scaled_down(factor)
		}
	}

	fn make_opaque(&mut self) -> Result<()> {
		match *self {
			DynamicImage::ImageRgba8(ref mut img) => {
				for p in img.pixels_mut() {
					p[3] = 255;
				}
			}
			DynamicImage::ImageLumaA8(ref mut img) => {
				for p in img.pixels_mut() {
					p[1] = 255;
				}
			}
			DynamicImage::ImageRgb8(_) | DynamicImage::ImageLuma8(_) => {}
			_ => bail!("Unsupported image type for removing alpha: {:?}", self.color()),
		}
		Ok(())
	}

	fn mut_color_values<F>(&mut self, f: F)
	where
		F: Fn(u8) -> u8,
	{
		match self {
			DynamicImage::ImageLuma8(img) => {
				for p in img.pixels_mut() {
					p[0] = f(p[0]);
				}
			}
			DynamicImage::ImageLumaA8(img) => {
				for p in img.pixels_mut() {
					p[0] = f(p[0]);
				}
			}
			DynamicImage::ImageRgb8(img) => {
				for p in img.pixels_mut() {
					p[0] = f(p[0]);
					p[1] = f(p[1]);
					p[2] = f(p[2]);
				}
			}
			DynamicImage::ImageRgba8(img) => {
				for p in img.pixels_mut() {
					p[0] = f(p[0]);
					p[1] = f(p[1]);
					p[2] = f(p[2]);
				}
			}
			_ => panic!("Unsupported image type for mutating color values: {:?}", self.color()),
		}
	}

	fn overlay(&mut self, top: &DynamicImage) -> Result<()> {
		self.ensure_same_size(top)?;
		overlay(self, top, 0, 0);
		Ok(())
	}
}