versatiles_image 1.0.0

A toolbox for converting, checking and serving map tiles in various formats.
Documentation
use super::convert::DynamicImageTraitConvert;
use anyhow::{Result, ensure};
use image::{DynamicImage, ExtendedColorType};

pub trait DynamicImageTraitInfo: DynamicImageTraitConvert {
	fn bits_per_value(&self) -> u8;
	fn channel_count(&self) -> u8;
	fn diff(&self, other: &DynamicImage) -> Result<Vec<f64>>;
	fn ensure_same_meta(&self, other: &DynamicImage) -> Result<()>;
	fn ensure_same_size(&self, other: &DynamicImage) -> Result<()>;
	fn extended_color_type(&self) -> ExtendedColorType;
	fn has_alpha(&self) -> bool;
	fn into_optional(self) -> Option<DynamicImage>;
	fn is_empty(&self) -> bool;
	fn is_opaque(&self) -> bool;
}

impl DynamicImageTraitInfo for DynamicImage
where
	DynamicImage: DynamicImageTraitConvert,
{
	fn bits_per_value(&self) -> u8 {
		(self.color().bits_per_pixel() / u16::from(self.color().channel_count())) as u8
	}

	fn channel_count(&self) -> u8 {
		self.color().channel_count()
	}

	fn diff(&self, other: &DynamicImage) -> Result<Vec<f64>> {
		self.ensure_same_meta(other)?;

		let channels = self.color().channel_count() as usize;
		let mut sqr_sum = vec![0u64; channels];

		for (p1, p2) in self.iter_pixels().zip(other.iter_pixels()) {
			for i in 0..channels {
				let d = i64::from(p1[i]) - i64::from(p2[i]);
				sqr_sum[i] += (d * d) as u64;
			}
		}

		let n = f64::from(self.width() * self.height());
		Ok(sqr_sum.iter().map(|v| (10.0 * (*v as f64) / n).ceil() / 10.0).collect())
	}

	fn ensure_same_meta(&self, other: &DynamicImage) -> Result<()> {
		self.ensure_same_size(other)?;
		ensure!(
			self.color() == other.color(),
			"Pixel value type mismatch: self has {:?}, but the other image has {:?}",
			self.color(),
			other.color()
		);
		Ok(())
	}

	fn ensure_same_size(&self, other: &DynamicImage) -> Result<()> {
		ensure!(
			self.width() == other.width(),
			"Image width mismatch: self has width {}, but the other image has width {}",
			self.width(),
			other.width()
		);
		ensure!(
			self.height() == other.height(),
			"Image height mismatch: self has height {}, but the other image has height {}",
			self.height(),
			other.height()
		);
		Ok(())
	}

	fn extended_color_type(&self) -> ExtendedColorType {
		self.color().into()
	}

	fn has_alpha(&self) -> bool {
		self.color().has_alpha()
	}

	fn into_optional(self) -> Option<DynamicImage> {
		if self.is_empty() { None } else { Some(self) }
	}

	fn is_empty(&self) -> bool {
		if !self.color().has_alpha() {
			return false;
		}
		let alpha_channel = (self.color().channel_count() - 1) as usize;
		return self.iter_pixels().all(|p| p[alpha_channel] == 0);
	}

	fn is_opaque(&self) -> bool {
		if !self.color().has_alpha() {
			return true;
		}
		let alpha_channel = (self.color().channel_count() - 1) as usize;
		return self.iter_pixels().all(|p| p[alpha_channel] == 255);
	}
}