use core::fmt;
use std::{cmp::Ordering, num::NonZero};
#[cfg(test)]
macro_rules! assert_cmp {
(($lhs:expr) == ($rhs:expr)) => {
assert_eq!(
$lhs.partial_cmp(&$rhs),
Some(Ordering::Equal),
"{:?} == {:?}",
$lhs,
$rhs
);
};
(($lhs:expr) < ($rhs:expr)) => {
assert_eq!(
$lhs.partial_cmp(&$rhs),
Some(Ordering::Less),
"{:?} < {:?}",
$lhs,
$rhs
);
};
(($lhs:expr) > ($rhs:expr)) => {
assert_eq!(
$lhs.partial_cmp(&$rhs),
Some(Ordering::Greater),
"{:?} > {:?}",
$lhs,
$rhs
);
};
(($lhs:expr) != ($rhs:expr)) => {
assert_eq!($lhs.partial_cmp(&$rhs), None, "{:?} != {:?}", $lhs, $rhs);
};
}
/// Bit depth.
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
#[repr(u8)]
pub(crate) enum Depth {
One = 1,
Eight = 8,
Sixteen = 16,
}
impl Depth {
/// Convert from maximum value.
pub(crate) const fn from_max_value(max_value: NonZero<u16>) -> Option<Depth> {
match max_value.get() {
1 => Some(Depth::One),
255 => Some(Depth::Eight),
65535 => Some(Depth::Sixteen),
_ => None,
}
}
/// Maximum value.
pub(crate) fn max_value(self) -> u16 {
match self {
Depth::One => 1,
Depth::Eight => 255,
Depth::Sixteen => 65535,
}
}
}
impl fmt::Display for Depth {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&(*self as u8), f)
}
}
/// Number of channels.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[repr(u8)]
pub(crate) enum Channels {
One = 1,
Two = 2,
Three = 3,
Four = 4,
}
impl Channels {
/// Create from number.
pub(crate) fn new(value: NonZero<u8>) -> Option<Channels> {
match value.get() {
1 => Some(Channels::One),
2 => Some(Channels::Two),
3 => Some(Channels::Three),
4 => Some(Channels::Four),
_ => None,
}
}
}
impl fmt::Display for Channels {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&(*self as u8), f)
}
}
impl PartialOrd for Channels {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
match (self, other) {
(Channels::One, Channels::One)
| (Channels::Two, Channels::Two)
| (Channels::Three, Channels::Three)
| (Channels::Four, Channels::Four) => Some(Ordering::Equal),
(Channels::One, _) | (_, Channels::Four) => Some(Ordering::Less),
(_, Channels::One) | (Channels::Four, _) => Some(Ordering::Greater),
(Channels::Two, Channels::Three) | (Channels::Three, Channels::Two) => None,
}
}
}
#[test]
fn channels_cmp() {
assert_cmp!((Channels::One) == (Channels::One));
assert_cmp!((Channels::One) < (Channels::Two));
assert_cmp!((Channels::One) < (Channels::Three));
assert_cmp!((Channels::One) < (Channels::Four));
assert_cmp!((Channels::Two) > (Channels::One));
assert_cmp!((Channels::Two) == (Channels::Two));
assert_cmp!((Channels::Two) != (Channels::Three));
assert_cmp!((Channels::Two) < (Channels::Four));
assert_cmp!((Channels::Three) > (Channels::One));
assert_cmp!((Channels::Three) != (Channels::Two));
assert_cmp!((Channels::Three) == (Channels::Three));
assert_cmp!((Channels::Three) < (Channels::Four));
assert_cmp!((Channels::Four) > (Channels::One));
assert_cmp!((Channels::Four) > (Channels::Two));
assert_cmp!((Channels::Four) > (Channels::Three));
assert_cmp!((Channels::Four) == (Channels::Four));
}
/// Combination of bit depth + channels.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[repr(u8)]
pub(crate) enum DepthChannels {
BlackAndWhite = 0,
BlackAndWhiteAlpha = 1,
Grayscale = 8,
GrayscaleAlpha = 9,
Rgb = 10,
RgbAlpha = 11,
Grayscale16 = 16,
GrayscaleAlpha16 = 17,
Rgb16 = 18,
RgbAlpha16 = 19,
}
impl DepthChannels {
/// Create from raw numbers.
pub(crate) fn new_raw(channels: NonZero<u8>, max_value: NonZero<u16>) -> Option<DepthChannels> {
DepthChannels::new(Depth::from_max_value(max_value)?, Channels::new(channels)?)
}
/// Create from parts.
pub(crate) fn new(depth: Depth, channels: Channels) -> Option<DepthChannels> {
Some(match ((depth as u8) & !0b111) + ((channels as u8) - 1) {
0 => DepthChannels::BlackAndWhite,
1 => DepthChannels::BlackAndWhiteAlpha,
8 => DepthChannels::Grayscale,
9 => DepthChannels::GrayscaleAlpha,
10 => DepthChannels::Rgb,
11 => DepthChannels::RgbAlpha,
16 => DepthChannels::Grayscale16,
17 => DepthChannels::GrayscaleAlpha16,
18 => DepthChannels::Rgb16,
19 => DepthChannels::RgbAlpha16,
_ => return None,
})
}
/// Get depth.
pub(crate) fn depth(self) -> Depth {
match (self as u8) & !0b111 {
0 => Depth::One,
8 => Depth::Eight,
16 => Depth::Sixteen,
_ => unreachable!(),
}
}
/// Get channels.
pub(crate) fn channels(self) -> Channels {
match (self as u8) & 0b111 {
0 => Channels::One,
1 => Channels::Two,
2 => Channels::Three,
3 => Channels::Four,
_ => unreachable!(),
}
}
}
impl fmt::Display for DepthChannels {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.pad(match self {
DepthChannels::BlackAndWhite => "black-and-white",
DepthChannels::BlackAndWhiteAlpha => "black-and-white+alpha",
DepthChannels::Grayscale => "grayscale",
DepthChannels::GrayscaleAlpha => "grayscale+alpha",
DepthChannels::Rgb => "RGB",
DepthChannels::RgbAlpha => "RGB+alpha",
DepthChannels::Grayscale16 => "16-bit grayscale",
DepthChannels::GrayscaleAlpha16 => "16-bit grayscale+alpha",
DepthChannels::Rgb16 => "16-bit RGB",
DepthChannels::RgbAlpha16 => "16-bit RGB+alpha",
})
}
}
impl PartialOrd for DepthChannels {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
match (self, other) {
(DepthChannels::BlackAndWhite, DepthChannels::BlackAndWhite)
| (DepthChannels::BlackAndWhiteAlpha, DepthChannels::BlackAndWhiteAlpha)
| (DepthChannels::Grayscale, DepthChannels::Grayscale)
| (DepthChannels::GrayscaleAlpha, DepthChannels::GrayscaleAlpha)
| (DepthChannels::Rgb, DepthChannels::Rgb)
| (DepthChannels::RgbAlpha, DepthChannels::RgbAlpha)
| (DepthChannels::Grayscale16, DepthChannels::Grayscale16)
| (DepthChannels::GrayscaleAlpha16, DepthChannels::GrayscaleAlpha16)
| (DepthChannels::Rgb16, DepthChannels::Rgb16)
| (DepthChannels::RgbAlpha16, DepthChannels::RgbAlpha16) => Some(Ordering::Equal),
(DepthChannels::BlackAndWhite, _)
| (_, DepthChannels::RgbAlpha16)
| (
DepthChannels::BlackAndWhiteAlpha,
DepthChannels::GrayscaleAlpha
| DepthChannels::RgbAlpha
| DepthChannels::GrayscaleAlpha16,
)
| (
DepthChannels::Grayscale,
DepthChannels::GrayscaleAlpha
| DepthChannels::Rgb
| DepthChannels::RgbAlpha
| DepthChannels::Grayscale16
| DepthChannels::GrayscaleAlpha16
| DepthChannels::Rgb16,
)
| (
DepthChannels::GrayscaleAlpha,
DepthChannels::RgbAlpha | DepthChannels::GrayscaleAlpha16,
)
| (DepthChannels::Rgb, DepthChannels::RgbAlpha | DepthChannels::Rgb16)
| (
DepthChannels::Grayscale16,
DepthChannels::GrayscaleAlpha16 | DepthChannels::Rgb16,
) => Some(Ordering::Less),
(_, DepthChannels::BlackAndWhite)
| (DepthChannels::RgbAlpha16, _)
| (
DepthChannels::GrayscaleAlpha,
DepthChannels::BlackAndWhiteAlpha | DepthChannels::Grayscale,
)
| (DepthChannels::Rgb, DepthChannels::Grayscale)
| (
DepthChannels::RgbAlpha,
DepthChannels::BlackAndWhiteAlpha
| DepthChannels::Grayscale
| DepthChannels::GrayscaleAlpha
| DepthChannels::Rgb,
)
| (DepthChannels::Grayscale16, DepthChannels::Grayscale)
| (
DepthChannels::GrayscaleAlpha16,
DepthChannels::BlackAndWhiteAlpha
| DepthChannels::Grayscale
| DepthChannels::GrayscaleAlpha
| DepthChannels::Grayscale16,
)
| (
DepthChannels::Rgb16,
DepthChannels::Grayscale | DepthChannels::Rgb | DepthChannels::Grayscale16,
) => Some(Ordering::Greater),
(
DepthChannels::BlackAndWhiteAlpha,
DepthChannels::Grayscale
| DepthChannels::Rgb
| DepthChannels::Grayscale16
| DepthChannels::Rgb16,
)
| (DepthChannels::Grayscale, DepthChannels::BlackAndWhiteAlpha)
| (
DepthChannels::GrayscaleAlpha,
DepthChannels::Rgb | DepthChannels::Grayscale16 | DepthChannels::Rgb16,
)
| (
DepthChannels::Rgb,
DepthChannels::BlackAndWhiteAlpha
| DepthChannels::GrayscaleAlpha
| DepthChannels::Grayscale16
| DepthChannels::GrayscaleAlpha16,
)
| (
DepthChannels::RgbAlpha,
DepthChannels::Grayscale16 | DepthChannels::GrayscaleAlpha16 | DepthChannels::Rgb16,
)
| (
DepthChannels::Grayscale16,
DepthChannels::BlackAndWhiteAlpha
| DepthChannels::GrayscaleAlpha
| DepthChannels::Rgb
| DepthChannels::RgbAlpha,
)
| (
DepthChannels::GrayscaleAlpha16,
DepthChannels::Rgb | DepthChannels::RgbAlpha | DepthChannels::Rgb16,
)
| (
DepthChannels::Rgb16,
DepthChannels::BlackAndWhiteAlpha
| DepthChannels::GrayscaleAlpha
| DepthChannels::RgbAlpha
| DepthChannels::GrayscaleAlpha16,
) => None,
}
}
}
#[test]
fn depth_channels_combine() {
let combos = [
(
Some(DepthChannels::BlackAndWhite),
Depth::One,
Channels::One,
),
(
Some(DepthChannels::BlackAndWhiteAlpha),
Depth::One,
Channels::Two,
),
(None, Depth::One, Channels::Three),
(None, Depth::One, Channels::Four),
(Some(DepthChannels::Grayscale), Depth::Eight, Channels::One),
(
Some(DepthChannels::GrayscaleAlpha),
Depth::Eight,
Channels::Two,
),
(Some(DepthChannels::Rgb), Depth::Eight, Channels::Three),
(Some(DepthChannels::RgbAlpha), Depth::Eight, Channels::Four),
(
Some(DepthChannels::Grayscale16),
Depth::Sixteen,
Channels::One,
),
(
Some(DepthChannels::GrayscaleAlpha16),
Depth::Sixteen,
Channels::Two,
),
(Some(DepthChannels::Rgb16), Depth::Sixteen, Channels::Three),
(
Some(DepthChannels::RgbAlpha16),
Depth::Sixteen,
Channels::Four,
),
];
for (depth_channels, depth, channels) in combos {
let combo = DepthChannels::new(depth, channels);
assert_eq!(
combo, depth_channels,
"DepthChannels::new({depth:?}, {channels:?})"
);
if let Some(depth_channels) = depth_channels {
assert_eq!(depth_channels.depth(), depth, "{depth_channels:?}.depth()");
assert_eq!(
depth_channels.channels(),
channels,
"{depth_channels:?}.channels()"
);
}
}
}
#[test]
fn depth_channels_cmp() {
assert_cmp!((DepthChannels::BlackAndWhite) == (DepthChannels::BlackAndWhite));
assert_cmp!((DepthChannels::BlackAndWhite) < (DepthChannels::BlackAndWhiteAlpha));
assert_cmp!((DepthChannels::BlackAndWhite) < (DepthChannels::Grayscale));
assert_cmp!((DepthChannels::BlackAndWhite) < (DepthChannels::GrayscaleAlpha));
assert_cmp!((DepthChannels::BlackAndWhite) < (DepthChannels::Rgb));
assert_cmp!((DepthChannels::BlackAndWhite) < (DepthChannels::RgbAlpha));
assert_cmp!((DepthChannels::BlackAndWhite) < (DepthChannels::Grayscale16));
assert_cmp!((DepthChannels::BlackAndWhite) < (DepthChannels::GrayscaleAlpha16));
assert_cmp!((DepthChannels::BlackAndWhite) < (DepthChannels::Rgb16));
assert_cmp!((DepthChannels::BlackAndWhite) < (DepthChannels::RgbAlpha16));
assert_cmp!((DepthChannels::BlackAndWhiteAlpha) > (DepthChannels::BlackAndWhite));
assert_cmp!((DepthChannels::BlackAndWhiteAlpha) == (DepthChannels::BlackAndWhiteAlpha));
assert_cmp!((DepthChannels::BlackAndWhiteAlpha) != (DepthChannels::Grayscale));
assert_cmp!((DepthChannels::BlackAndWhiteAlpha) < (DepthChannels::GrayscaleAlpha));
assert_cmp!((DepthChannels::BlackAndWhiteAlpha) != (DepthChannels::Rgb));
assert_cmp!((DepthChannels::BlackAndWhiteAlpha) < (DepthChannels::RgbAlpha));
assert_cmp!((DepthChannels::BlackAndWhiteAlpha) != (DepthChannels::Grayscale16));
assert_cmp!((DepthChannels::BlackAndWhiteAlpha) < (DepthChannels::GrayscaleAlpha16));
assert_cmp!((DepthChannels::BlackAndWhiteAlpha) != (DepthChannels::Rgb16));
assert_cmp!((DepthChannels::BlackAndWhiteAlpha) < (DepthChannels::RgbAlpha16));
assert_cmp!((DepthChannels::Grayscale) > (DepthChannels::BlackAndWhite));
assert_cmp!((DepthChannels::Grayscale) != (DepthChannels::BlackAndWhiteAlpha));
assert_cmp!((DepthChannels::Grayscale) == (DepthChannels::Grayscale));
assert_cmp!((DepthChannels::Grayscale) < (DepthChannels::GrayscaleAlpha));
assert_cmp!((DepthChannels::Grayscale) < (DepthChannels::Rgb));
assert_cmp!((DepthChannels::Grayscale) < (DepthChannels::RgbAlpha));
assert_cmp!((DepthChannels::Grayscale) < (DepthChannels::Grayscale16));
assert_cmp!((DepthChannels::Grayscale) < (DepthChannels::GrayscaleAlpha16));
assert_cmp!((DepthChannels::Grayscale) < (DepthChannels::Rgb16));
assert_cmp!((DepthChannels::Grayscale) < (DepthChannels::RgbAlpha16));
assert_cmp!((DepthChannels::GrayscaleAlpha) > (DepthChannels::BlackAndWhite));
assert_cmp!((DepthChannels::GrayscaleAlpha) > (DepthChannels::BlackAndWhiteAlpha));
assert_cmp!((DepthChannels::GrayscaleAlpha) > (DepthChannels::Grayscale));
assert_cmp!((DepthChannels::GrayscaleAlpha) == (DepthChannels::GrayscaleAlpha));
assert_cmp!((DepthChannels::GrayscaleAlpha) != (DepthChannels::Rgb));
assert_cmp!((DepthChannels::GrayscaleAlpha) < (DepthChannels::RgbAlpha));
assert_cmp!((DepthChannels::GrayscaleAlpha) != (DepthChannels::Grayscale16));
assert_cmp!((DepthChannels::GrayscaleAlpha) < (DepthChannels::GrayscaleAlpha16));
assert_cmp!((DepthChannels::GrayscaleAlpha) != (DepthChannels::Rgb16));
assert_cmp!((DepthChannels::GrayscaleAlpha) < (DepthChannels::RgbAlpha16));
assert_cmp!((DepthChannels::Rgb) > (DepthChannels::BlackAndWhite));
assert_cmp!((DepthChannels::Rgb) != (DepthChannels::BlackAndWhiteAlpha));
assert_cmp!((DepthChannels::Rgb) > (DepthChannels::Grayscale));
assert_cmp!((DepthChannels::Rgb) != (DepthChannels::GrayscaleAlpha));
assert_cmp!((DepthChannels::Rgb) == (DepthChannels::Rgb));
assert_cmp!((DepthChannels::Rgb) < (DepthChannels::RgbAlpha));
assert_cmp!((DepthChannels::Rgb) != (DepthChannels::Grayscale16));
assert_cmp!((DepthChannels::Rgb) != (DepthChannels::GrayscaleAlpha16));
assert_cmp!((DepthChannels::Rgb) < (DepthChannels::Rgb16));
assert_cmp!((DepthChannels::Rgb) < (DepthChannels::RgbAlpha16));
assert_cmp!((DepthChannels::RgbAlpha) > (DepthChannels::BlackAndWhite));
assert_cmp!((DepthChannels::RgbAlpha) > (DepthChannels::BlackAndWhiteAlpha));
assert_cmp!((DepthChannels::RgbAlpha) > (DepthChannels::Grayscale));
assert_cmp!((DepthChannels::RgbAlpha) > (DepthChannels::GrayscaleAlpha));
assert_cmp!((DepthChannels::RgbAlpha) > (DepthChannels::Rgb));
assert_cmp!((DepthChannels::RgbAlpha) == (DepthChannels::RgbAlpha));
assert_cmp!((DepthChannels::RgbAlpha) != (DepthChannels::Grayscale16));
assert_cmp!((DepthChannels::RgbAlpha) != (DepthChannels::GrayscaleAlpha16));
assert_cmp!((DepthChannels::RgbAlpha) != (DepthChannels::Rgb16));
assert_cmp!((DepthChannels::RgbAlpha) < (DepthChannels::RgbAlpha16));
assert_cmp!((DepthChannels::Grayscale16) > (DepthChannels::BlackAndWhite));
assert_cmp!((DepthChannels::Grayscale16) != (DepthChannels::BlackAndWhiteAlpha));
assert_cmp!((DepthChannels::Grayscale16) > (DepthChannels::Grayscale));
assert_cmp!((DepthChannels::Grayscale16) != (DepthChannels::GrayscaleAlpha));
assert_cmp!((DepthChannels::Grayscale16) != (DepthChannels::Rgb));
assert_cmp!((DepthChannels::Grayscale16) != (DepthChannels::RgbAlpha));
assert_cmp!((DepthChannels::Grayscale16) == (DepthChannels::Grayscale16));
assert_cmp!((DepthChannels::Grayscale16) < (DepthChannels::GrayscaleAlpha16));
assert_cmp!((DepthChannels::Grayscale16) < (DepthChannels::Rgb16));
assert_cmp!((DepthChannels::Grayscale16) < (DepthChannels::RgbAlpha16));
assert_cmp!((DepthChannels::GrayscaleAlpha16) > (DepthChannels::BlackAndWhite));
assert_cmp!((DepthChannels::GrayscaleAlpha16) > (DepthChannels::BlackAndWhiteAlpha));
assert_cmp!((DepthChannels::GrayscaleAlpha16) > (DepthChannels::Grayscale));
assert_cmp!((DepthChannels::GrayscaleAlpha16) > (DepthChannels::GrayscaleAlpha));
assert_cmp!((DepthChannels::GrayscaleAlpha16) != (DepthChannels::Rgb));
assert_cmp!((DepthChannels::GrayscaleAlpha16) != (DepthChannels::RgbAlpha));
assert_cmp!((DepthChannels::GrayscaleAlpha16) > (DepthChannels::Grayscale16));
assert_cmp!((DepthChannels::GrayscaleAlpha16) == (DepthChannels::GrayscaleAlpha16));
assert_cmp!((DepthChannels::GrayscaleAlpha16) != (DepthChannels::Rgb16));
assert_cmp!((DepthChannels::GrayscaleAlpha16) < (DepthChannels::RgbAlpha16));
assert_cmp!((DepthChannels::Rgb16) > (DepthChannels::BlackAndWhite));
assert_cmp!((DepthChannels::Rgb16) != (DepthChannels::BlackAndWhiteAlpha));
assert_cmp!((DepthChannels::Rgb16) > (DepthChannels::Grayscale));
assert_cmp!((DepthChannels::Rgb16) != (DepthChannels::GrayscaleAlpha));
assert_cmp!((DepthChannels::Rgb16) > (DepthChannels::Rgb));
assert_cmp!((DepthChannels::Rgb16) != (DepthChannels::RgbAlpha));
assert_cmp!((DepthChannels::Rgb16) > (DepthChannels::Grayscale16));
assert_cmp!((DepthChannels::Rgb16) != (DepthChannels::GrayscaleAlpha16));
assert_cmp!((DepthChannels::Rgb16) == (DepthChannels::Rgb16));
assert_cmp!((DepthChannels::Rgb16) < (DepthChannels::RgbAlpha16));
assert_cmp!((DepthChannels::RgbAlpha16) > (DepthChannels::BlackAndWhite));
assert_cmp!((DepthChannels::RgbAlpha16) > (DepthChannels::BlackAndWhiteAlpha));
assert_cmp!((DepthChannels::RgbAlpha16) > (DepthChannels::Grayscale));
assert_cmp!((DepthChannels::RgbAlpha16) > (DepthChannels::GrayscaleAlpha));
assert_cmp!((DepthChannels::RgbAlpha16) > (DepthChannels::Rgb));
assert_cmp!((DepthChannels::RgbAlpha16) > (DepthChannels::RgbAlpha));
assert_cmp!((DepthChannels::RgbAlpha16) > (DepthChannels::Grayscale16));
assert_cmp!((DepthChannels::RgbAlpha16) > (DepthChannels::GrayscaleAlpha16));
assert_cmp!((DepthChannels::RgbAlpha16) > (DepthChannels::Rgb16));
assert_cmp!((DepthChannels::RgbAlpha16) == (DepthChannels::RgbAlpha16));
}
/// Size of image.
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub(crate) struct Size {
/// Width of image.
width: NonZero<u16>,
/// Height of image.
height: NonZero<u16>,
}
impl Size {
/// Create from dimensions.
pub(crate) fn new(width: u16, height: u16) -> Option<Size> {
Some(Size {
width: NonZero::new(width)?,
height: NonZero::new(height)?,
})
}
/// Get width.
pub(crate) fn width(self) -> usize {
self.width.get() as usize
}
/// Get height.
pub(crate) fn height(self) -> usize {
self.height.get() as usize
}
/// Get total number of pixels.
pub(crate) fn pixels(self) -> usize {
self.width() * self.height()
}
/// Get total number of values.
pub(crate) fn values(self, channels: u8) -> usize {
self.pixels() * (channels as usize)
}
}