use core::fmt;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
#[repr(u8)]
pub enum ChannelType {
U8 = 1,
U16 = 2,
F32 = 4,
F16 = 5,
}
impl ChannelType {
#[inline]
#[allow(unreachable_patterns)]
pub const fn byte_size(self) -> usize {
match self {
Self::U8 => 1,
Self::U16 | Self::F16 => 2,
Self::F32 => 4,
_ => 0,
}
}
#[inline]
pub const fn is_u8(self) -> bool {
matches!(self, Self::U8)
}
#[inline]
pub const fn is_u16(self) -> bool {
matches!(self, Self::U16)
}
#[inline]
pub const fn is_f32(self) -> bool {
matches!(self, Self::F32)
}
#[inline]
pub const fn is_f16(self) -> bool {
matches!(self, Self::F16)
}
#[inline]
#[allow(unreachable_patterns)]
pub const fn is_integer(self) -> bool {
matches!(self, Self::U8 | Self::U16)
}
#[inline]
#[allow(unreachable_patterns)]
pub const fn is_float(self) -> bool {
matches!(self, Self::F32 | Self::F16)
}
}
impl fmt::Display for ChannelType {
#[allow(unreachable_patterns)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::U8 => f.write_str("U8"),
Self::U16 => f.write_str("U16"),
Self::F32 => f.write_str("F32"),
Self::F16 => f.write_str("F16"),
_ => write!(f, "ChannelType({})", *self as u8),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
#[repr(u8)]
pub enum ChannelLayout {
Gray = 1,
GrayAlpha = 2,
Rgb = 3,
Rgba = 4,
Bgra = 5,
Oklab = 6,
OklabA = 7,
}
impl ChannelLayout {
#[inline]
#[allow(unreachable_patterns)]
pub const fn channels(self) -> usize {
match self {
Self::Gray => 1,
Self::GrayAlpha => 2,
Self::Rgb | Self::Oklab => 3,
Self::Rgba | Self::Bgra | Self::OklabA => 4,
_ => 0,
}
}
#[inline]
#[allow(unreachable_patterns)]
pub const fn has_alpha(self) -> bool {
matches!(
self,
Self::GrayAlpha | Self::Rgba | Self::Bgra | Self::OklabA
)
}
}
impl fmt::Display for ChannelLayout {
#[allow(unreachable_patterns)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Gray => f.write_str("Gray"),
Self::GrayAlpha => f.write_str("GrayAlpha"),
Self::Rgb => f.write_str("RGB"),
Self::Rgba => f.write_str("RGBA"),
Self::Bgra => f.write_str("BGRA"),
Self::Oklab => f.write_str("Oklab"),
Self::OklabA => f.write_str("OklabA"),
_ => write!(f, "ChannelLayout({})", *self as u8),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
#[repr(u8)]
pub enum AlphaMode {
Undefined = 1,
Straight = 2,
Premultiplied = 3,
Opaque = 4,
}
impl AlphaMode {
#[inline]
pub const fn has_alpha(self) -> bool {
matches!(self, Self::Straight | Self::Premultiplied | Self::Opaque)
}
}
impl fmt::Display for AlphaMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Undefined => f.write_str("undefined"),
Self::Straight => f.write_str("straight"),
Self::Premultiplied => f.write_str("premultiplied"),
Self::Opaque => f.write_str("opaque"),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
#[repr(u8)]
pub enum TransferFunction {
Linear = 0,
Srgb = 1,
Bt709 = 2,
Pq = 3,
Hlg = 4,
Unknown = 255,
}
impl TransferFunction {
#[inline]
pub const fn from_cicp(tc: u8) -> Option<Self> {
match tc {
1 => Some(Self::Bt709),
8 => Some(Self::Linear),
13 => Some(Self::Srgb),
16 => Some(Self::Pq),
18 => Some(Self::Hlg),
_ => None,
}
}
#[allow(unreachable_patterns)]
#[inline]
pub const fn to_cicp(self) -> Option<u8> {
match self {
Self::Bt709 => Some(1),
Self::Linear => Some(8),
Self::Srgb => Some(13),
Self::Pq => Some(16),
Self::Hlg => Some(18),
Self::Unknown => None,
_ => None,
}
}
#[allow(unreachable_patterns)]
pub fn reference_white_nits(&self) -> f32 {
match self {
Self::Pq => 203.0,
_ => 1.0,
}
}
}
impl fmt::Display for TransferFunction {
#[allow(unreachable_patterns)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Linear => f.write_str("linear"),
Self::Srgb => f.write_str("sRGB"),
Self::Bt709 => f.write_str("BT.709"),
Self::Pq => f.write_str("PQ"),
Self::Hlg => f.write_str("HLG"),
Self::Unknown => f.write_str("unknown"),
_ => write!(f, "TransferFunction({})", *self as u8),
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
#[repr(u8)]
pub enum ColorPrimaries {
#[default]
Bt709 = 1,
Bt2020 = 9,
DisplayP3 = 12,
Unknown = 255,
}
impl ColorPrimaries {
#[inline]
pub const fn from_cicp(code: u8) -> Option<Self> {
match code {
1 => Some(Self::Bt709),
9 => Some(Self::Bt2020),
12 => Some(Self::DisplayP3),
_ => None,
}
}
#[allow(unreachable_patterns)]
#[inline]
pub const fn to_cicp(self) -> Option<u8> {
match self {
Self::Bt709 => Some(1),
Self::Bt2020 => Some(9),
Self::DisplayP3 => Some(12),
Self::Unknown => None,
_ => None,
}
}
#[inline]
pub const fn contains(self, other: Self) -> bool {
self.gamut_width() >= other.gamut_width()
&& !matches!(self, Self::Unknown)
&& !matches!(other, Self::Unknown)
}
#[allow(unreachable_patterns)]
const fn gamut_width(self) -> u8 {
match self {
Self::Bt709 => 1,
Self::DisplayP3 => 2,
Self::Bt2020 => 3,
Self::Unknown => 0,
_ => 0,
}
}
}
impl fmt::Display for ColorPrimaries {
#[allow(unreachable_patterns)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Bt709 => f.write_str("BT.709"),
Self::Bt2020 => f.write_str("BT.2020"),
Self::DisplayP3 => f.write_str("Display P3"),
Self::Unknown => f.write_str("unknown"),
_ => write!(f, "ColorPrimaries({})", *self as u8),
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
#[repr(u8)]
pub enum SignalRange {
#[default]
Full = 0,
Narrow = 1,
}
impl fmt::Display for SignalRange {
#[allow(unreachable_patterns)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Full => f.write_str("full"),
Self::Narrow => f.write_str("narrow"),
_ => write!(f, "SignalRange({})", *self as u8),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub struct PixelDescriptor {
pub format: PixelFormat,
pub transfer: TransferFunction,
pub alpha: Option<AlphaMode>,
pub primaries: ColorPrimaries,
pub signal_range: SignalRange,
}
impl PixelDescriptor {
#[inline]
pub const fn pixel_format(&self) -> PixelFormat {
self.format
}
#[inline]
pub const fn channel_type(&self) -> ChannelType {
self.format.channel_type()
}
#[inline]
pub const fn alpha(&self) -> Option<AlphaMode> {
self.alpha
}
#[inline]
pub const fn transfer(&self) -> TransferFunction {
self.transfer
}
#[inline]
pub const fn byte_order(&self) -> ByteOrder {
self.format.byte_order()
}
#[inline]
pub const fn color_model(&self) -> ColorModel {
self.format.color_model()
}
#[inline]
pub const fn layout(&self) -> ChannelLayout {
self.format.layout()
}
#[inline]
pub const fn new(
channel_type: ChannelType,
layout: ChannelLayout,
alpha: Option<AlphaMode>,
transfer: TransferFunction,
) -> Self {
let format = match PixelFormat::from_parts(channel_type, layout, alpha) {
Some(f) => f,
None => panic!("unsupported PixelFormat combination"),
};
Self {
format,
transfer,
alpha,
primaries: ColorPrimaries::Bt709,
signal_range: SignalRange::Full,
}
}
#[inline]
pub const fn new_full(
channel_type: ChannelType,
layout: ChannelLayout,
alpha: Option<AlphaMode>,
transfer: TransferFunction,
primaries: ColorPrimaries,
) -> Self {
let format = match PixelFormat::from_parts(channel_type, layout, alpha) {
Some(f) => f,
None => panic!("unsupported PixelFormat combination"),
};
Self {
format,
transfer,
alpha,
primaries,
signal_range: SignalRange::Full,
}
}
#[inline]
pub const fn from_pixel_format(format: PixelFormat) -> Self {
Self {
format,
transfer: TransferFunction::Unknown,
alpha: format.default_alpha(),
primaries: ColorPrimaries::Bt709,
signal_range: SignalRange::Full,
}
}
pub const RGB8_SRGB: Self = Self::new(
ChannelType::U8,
ChannelLayout::Rgb,
None,
TransferFunction::Srgb,
);
pub const RGBA8_SRGB: Self = Self::new(
ChannelType::U8,
ChannelLayout::Rgba,
Some(AlphaMode::Straight),
TransferFunction::Srgb,
);
pub const RGB16_SRGB: Self = Self::new(
ChannelType::U16,
ChannelLayout::Rgb,
None,
TransferFunction::Srgb,
);
pub const RGBA16_SRGB: Self = Self::new(
ChannelType::U16,
ChannelLayout::Rgba,
Some(AlphaMode::Straight),
TransferFunction::Srgb,
);
pub const RGBF32_LINEAR: Self = Self::new(
ChannelType::F32,
ChannelLayout::Rgb,
None,
TransferFunction::Linear,
);
pub const RGBAF32_LINEAR: Self = Self::new(
ChannelType::F32,
ChannelLayout::Rgba,
Some(AlphaMode::Straight),
TransferFunction::Linear,
);
pub const GRAY8_SRGB: Self = Self::new(
ChannelType::U8,
ChannelLayout::Gray,
None,
TransferFunction::Srgb,
);
pub const GRAY16_SRGB: Self = Self::new(
ChannelType::U16,
ChannelLayout::Gray,
None,
TransferFunction::Srgb,
);
pub const GRAYF32_LINEAR: Self = Self::new(
ChannelType::F32,
ChannelLayout::Gray,
None,
TransferFunction::Linear,
);
pub const GRAYA8_SRGB: Self = Self::new(
ChannelType::U8,
ChannelLayout::GrayAlpha,
Some(AlphaMode::Straight),
TransferFunction::Srgb,
);
pub const GRAYA16_SRGB: Self = Self::new(
ChannelType::U16,
ChannelLayout::GrayAlpha,
Some(AlphaMode::Straight),
TransferFunction::Srgb,
);
pub const GRAYAF32_LINEAR: Self = Self::new(
ChannelType::F32,
ChannelLayout::GrayAlpha,
Some(AlphaMode::Straight),
TransferFunction::Linear,
);
pub const BGRA8_SRGB: Self = Self::new(
ChannelType::U8,
ChannelLayout::Bgra,
Some(AlphaMode::Straight),
TransferFunction::Srgb,
);
pub const RGBX8_SRGB: Self = Self::new(
ChannelType::U8,
ChannelLayout::Rgba,
Some(AlphaMode::Undefined),
TransferFunction::Srgb,
);
pub const BGRX8_SRGB: Self = Self::new(
ChannelType::U8,
ChannelLayout::Bgra,
Some(AlphaMode::Undefined),
TransferFunction::Srgb,
);
pub const RGB8: Self = Self::new(
ChannelType::U8,
ChannelLayout::Rgb,
None,
TransferFunction::Unknown,
);
pub const RGBA8: Self = Self::new(
ChannelType::U8,
ChannelLayout::Rgba,
Some(AlphaMode::Straight),
TransferFunction::Unknown,
);
pub const RGB16: Self = Self::new(
ChannelType::U16,
ChannelLayout::Rgb,
None,
TransferFunction::Unknown,
);
pub const RGBA16: Self = Self::new(
ChannelType::U16,
ChannelLayout::Rgba,
Some(AlphaMode::Straight),
TransferFunction::Unknown,
);
pub const RGBF32: Self = Self::new(
ChannelType::F32,
ChannelLayout::Rgb,
None,
TransferFunction::Unknown,
);
pub const RGBAF32: Self = Self::new(
ChannelType::F32,
ChannelLayout::Rgba,
Some(AlphaMode::Straight),
TransferFunction::Unknown,
);
pub const GRAY8: Self = Self::new(
ChannelType::U8,
ChannelLayout::Gray,
None,
TransferFunction::Unknown,
);
pub const GRAY16: Self = Self::new(
ChannelType::U16,
ChannelLayout::Gray,
None,
TransferFunction::Unknown,
);
pub const GRAYF32: Self = Self::new(
ChannelType::F32,
ChannelLayout::Gray,
None,
TransferFunction::Unknown,
);
pub const GRAYA8: Self = Self::new(
ChannelType::U8,
ChannelLayout::GrayAlpha,
Some(AlphaMode::Straight),
TransferFunction::Unknown,
);
pub const GRAYA16: Self = Self::new(
ChannelType::U16,
ChannelLayout::GrayAlpha,
Some(AlphaMode::Straight),
TransferFunction::Unknown,
);
pub const GRAYAF32: Self = Self::new(
ChannelType::F32,
ChannelLayout::GrayAlpha,
Some(AlphaMode::Straight),
TransferFunction::Unknown,
);
pub const BGRA8: Self = Self::new(
ChannelType::U8,
ChannelLayout::Bgra,
Some(AlphaMode::Straight),
TransferFunction::Unknown,
);
pub const RGBX8: Self = Self::new(
ChannelType::U8,
ChannelLayout::Rgba,
Some(AlphaMode::Undefined),
TransferFunction::Unknown,
);
pub const BGRX8: Self = Self::new(
ChannelType::U8,
ChannelLayout::Bgra,
Some(AlphaMode::Undefined),
TransferFunction::Unknown,
);
pub const OKLABF32: Self = Self {
format: PixelFormat::OklabF32,
transfer: TransferFunction::Unknown,
alpha: None,
primaries: ColorPrimaries::Bt709,
signal_range: SignalRange::Full,
};
pub const OKLABAF32: Self = Self {
format: PixelFormat::OklabaF32,
transfer: TransferFunction::Unknown,
alpha: Some(AlphaMode::Straight),
primaries: ColorPrimaries::Bt709,
signal_range: SignalRange::Full,
};
#[inline]
pub const fn channels(self) -> usize {
self.format.channels()
}
#[inline]
pub const fn bytes_per_pixel(self) -> usize {
self.format.bytes_per_pixel()
}
#[inline]
pub const fn has_alpha(self) -> bool {
matches!(
self.alpha,
Some(AlphaMode::Straight) | Some(AlphaMode::Premultiplied) | Some(AlphaMode::Opaque)
)
}
#[inline]
pub const fn is_grayscale(self) -> bool {
self.format.is_grayscale()
}
#[inline]
pub const fn is_bgr(self) -> bool {
matches!(self.format.byte_order(), ByteOrder::Bgr)
}
#[inline]
#[must_use]
pub const fn with_transfer(self, transfer: TransferFunction) -> Self {
Self { transfer, ..self }
}
#[inline]
#[must_use]
pub const fn with_primaries(self, primaries: ColorPrimaries) -> Self {
Self { primaries, ..self }
}
#[inline]
#[must_use]
pub const fn with_alpha(self, alpha: Option<AlphaMode>) -> Self {
Self { alpha, ..self }
}
#[inline]
#[must_use]
pub const fn with_alpha_mode(self, alpha: Option<AlphaMode>) -> Self {
self.with_alpha(alpha)
}
#[inline]
#[must_use]
pub const fn with_signal_range(self, signal_range: SignalRange) -> Self {
Self {
signal_range,
..self
}
}
#[inline]
pub const fn is_opaque(self) -> bool {
matches!(
self.alpha,
None | Some(AlphaMode::Undefined | AlphaMode::Opaque)
)
}
#[inline]
#[allow(unreachable_patterns)]
pub const fn may_have_transparency(self) -> bool {
matches!(
self.alpha,
Some(AlphaMode::Straight | AlphaMode::Premultiplied)
)
}
#[inline]
pub const fn is_linear(self) -> bool {
matches!(self.transfer, TransferFunction::Linear)
}
#[inline]
pub const fn is_unknown_transfer(self) -> bool {
matches!(self.transfer, TransferFunction::Unknown)
}
#[inline]
pub const fn min_alignment(self) -> usize {
self.format.channel_type().byte_size()
}
#[inline]
pub const fn aligned_stride(self, width: u32) -> usize {
width as usize * self.bytes_per_pixel()
}
#[inline]
pub const fn simd_aligned_stride(self, width: u32, simd_align: usize) -> usize {
let bpp = self.bytes_per_pixel();
let raw = width as usize * bpp;
let align = lcm(bpp, simd_align);
align_up_general(raw, align)
}
#[inline]
pub const fn layout_compatible(self, other: Self) -> bool {
self.format.channel_type() as u8 == other.format.channel_type() as u8
&& self.layout() as u8 == other.layout() as u8
}
}
const fn gcd(mut a: usize, mut b: usize) -> usize {
while b != 0 {
let t = b;
b = a % b;
a = t;
}
a
}
const fn lcm(a: usize, b: usize) -> usize {
if a == 0 || b == 0 {
0
} else {
a / gcd(a, b) * b
}
}
const fn align_up_general(value: usize, align: usize) -> usize {
if align == 0 {
return value;
}
let rem = value % align;
if rem == 0 { value } else { value + align - rem }
}
impl fmt::Display for PixelDescriptor {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{} {} {}",
self.format,
self.format.channel_type(),
self.transfer
)?;
if let Some(alpha) = self.alpha
&& alpha.has_alpha()
{
write!(f, " alpha={alpha}")?;
}
Ok(())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
#[repr(u8)]
pub enum ColorModel {
Gray = 0,
Rgb = 1,
YCbCr = 2,
Oklab = 3,
}
impl ColorModel {
#[inline]
#[allow(unreachable_patterns)]
pub const fn color_channels(self) -> u8 {
match self {
Self::Gray => 1,
_ => 3,
}
}
}
impl fmt::Display for ColorModel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Gray => f.write_str("Gray"),
Self::Rgb => f.write_str("RGB"),
Self::YCbCr => f.write_str("YCbCr"),
Self::Oklab => f.write_str("Oklab"),
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
#[non_exhaustive]
#[repr(u8)]
pub enum ByteOrder {
#[default]
Native = 0,
Bgr = 1,
}
impl fmt::Display for ByteOrder {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Native => f.write_str("native"),
Self::Bgr => f.write_str("BGR"),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
#[repr(u8)]
pub enum PixelFormat {
Rgb8 = 1,
Rgba8 = 2,
Rgb16 = 3,
Rgba16 = 4,
RgbF32 = 5,
RgbaF32 = 6,
Gray8 = 7,
Gray16 = 8,
GrayF32 = 9,
GrayA8 = 10,
GrayA16 = 11,
GrayAF32 = 12,
Bgra8 = 13,
Rgbx8 = 14,
Bgrx8 = 15,
OklabF32 = 16,
OklabaF32 = 17,
}
impl PixelFormat {
#[inline]
#[allow(unreachable_patterns)]
pub const fn channel_type(self) -> ChannelType {
match self {
Self::Rgb8
| Self::Rgba8
| Self::Gray8
| Self::GrayA8
| Self::Bgra8
| Self::Rgbx8
| Self::Bgrx8 => ChannelType::U8,
Self::Rgb16 | Self::Rgba16 | Self::Gray16 | Self::GrayA16 => ChannelType::U16,
Self::RgbF32
| Self::RgbaF32
| Self::GrayF32
| Self::GrayAF32
| Self::OklabF32
| Self::OklabaF32 => ChannelType::F32,
_ => ChannelType::U8,
}
}
#[inline]
#[allow(unreachable_patterns)]
pub const fn layout(self) -> ChannelLayout {
match self {
Self::Rgb8 | Self::Rgb16 | Self::RgbF32 => ChannelLayout::Rgb,
Self::Rgba8 | Self::Rgba16 | Self::RgbaF32 | Self::Rgbx8 => ChannelLayout::Rgba,
Self::Gray8 | Self::Gray16 | Self::GrayF32 => ChannelLayout::Gray,
Self::GrayA8 | Self::GrayA16 | Self::GrayAF32 => ChannelLayout::GrayAlpha,
Self::Bgra8 | Self::Bgrx8 => ChannelLayout::Bgra,
Self::OklabF32 => ChannelLayout::Oklab,
Self::OklabaF32 => ChannelLayout::OklabA,
_ => ChannelLayout::Rgb,
}
}
#[inline]
#[allow(unreachable_patterns)]
pub const fn color_model(self) -> ColorModel {
match self {
Self::Gray8
| Self::Gray16
| Self::GrayF32
| Self::GrayA8
| Self::GrayA16
| Self::GrayAF32 => ColorModel::Gray,
Self::OklabF32 | Self::OklabaF32 => ColorModel::Oklab,
_ => ColorModel::Rgb,
}
}
#[inline]
#[allow(unreachable_patterns)]
pub const fn byte_order(self) -> ByteOrder {
match self {
Self::Bgra8 | Self::Bgrx8 => ByteOrder::Bgr,
_ => ByteOrder::Native,
}
}
#[inline]
pub const fn channels(self) -> usize {
self.layout().channels()
}
#[inline]
pub const fn bytes_per_pixel(self) -> usize {
self.channels() * self.channel_type().byte_size()
}
#[inline]
pub const fn has_alpha_bytes(self) -> bool {
self.layout().has_alpha()
}
#[inline]
pub const fn is_grayscale(self) -> bool {
matches!(self.color_model(), ColorModel::Gray)
}
#[allow(unreachable_patterns)]
#[inline]
pub const fn default_alpha(self) -> Option<AlphaMode> {
match self {
Self::Rgb8
| Self::Rgb16
| Self::RgbF32
| Self::Gray8
| Self::Gray16
| Self::GrayF32
| Self::OklabF32 => None,
Self::Rgbx8 | Self::Bgrx8 => Some(AlphaMode::Undefined),
_ => Some(AlphaMode::Straight),
}
}
#[allow(unreachable_patterns)]
#[inline]
pub const fn name(self) -> &'static str {
match self {
Self::Rgb8 => "RGB8",
Self::Rgba8 => "RGBA8",
Self::Rgb16 => "RGB16",
Self::Rgba16 => "RGBA16",
Self::RgbF32 => "RgbF32",
Self::RgbaF32 => "RgbaF32",
Self::Gray8 => "Gray8",
Self::Gray16 => "Gray16",
Self::GrayF32 => "GrayF32",
Self::GrayA8 => "GrayA8",
Self::GrayA16 => "GrayA16",
Self::GrayAF32 => "GrayAF32",
Self::Bgra8 => "BGRA8",
Self::Rgbx8 => "RGBX8",
Self::Bgrx8 => "BGRX8",
Self::OklabF32 => "OklabF32",
Self::OklabaF32 => "OklabaF32",
_ => "Unknown",
}
}
#[inline]
pub const fn from_parts(
channel_type: ChannelType,
layout: ChannelLayout,
alpha: Option<AlphaMode>,
) -> Option<Self> {
let is_padding = matches!(alpha, Some(AlphaMode::Undefined));
match (channel_type, layout, is_padding) {
(ChannelType::U8, ChannelLayout::Rgb, _) => Some(Self::Rgb8),
(ChannelType::U16, ChannelLayout::Rgb, _) => Some(Self::Rgb16),
(ChannelType::F32, ChannelLayout::Rgb, _) => Some(Self::RgbF32),
(ChannelType::U8, ChannelLayout::Rgba, true) => Some(Self::Rgbx8),
(ChannelType::U8, ChannelLayout::Rgba, false) => Some(Self::Rgba8),
(ChannelType::U16, ChannelLayout::Rgba, _) => Some(Self::Rgba16),
(ChannelType::F32, ChannelLayout::Rgba, _) => Some(Self::RgbaF32),
(ChannelType::U8, ChannelLayout::Gray, _) => Some(Self::Gray8),
(ChannelType::U16, ChannelLayout::Gray, _) => Some(Self::Gray16),
(ChannelType::F32, ChannelLayout::Gray, _) => Some(Self::GrayF32),
(ChannelType::U8, ChannelLayout::GrayAlpha, _) => Some(Self::GrayA8),
(ChannelType::U16, ChannelLayout::GrayAlpha, _) => Some(Self::GrayA16),
(ChannelType::F32, ChannelLayout::GrayAlpha, _) => Some(Self::GrayAF32),
(ChannelType::U8, ChannelLayout::Bgra, true) => Some(Self::Bgrx8),
(ChannelType::U8, ChannelLayout::Bgra, false) => Some(Self::Bgra8),
(ChannelType::F32, ChannelLayout::Oklab, _) => Some(Self::OklabF32),
(ChannelType::F32, ChannelLayout::OklabA, _) => Some(Self::OklabaF32),
_ => None,
}
}
#[allow(unreachable_patterns)]
#[inline]
pub const fn descriptor(self) -> PixelDescriptor {
match self {
Self::Rgb8 => PixelDescriptor::RGB8,
Self::Rgba8 => PixelDescriptor::RGBA8,
Self::Rgb16 => PixelDescriptor::RGB16,
Self::Rgba16 => PixelDescriptor::RGBA16,
Self::RgbF32 => PixelDescriptor::RGBF32,
Self::RgbaF32 => PixelDescriptor::RGBAF32,
Self::Gray8 => PixelDescriptor::GRAY8,
Self::Gray16 => PixelDescriptor::GRAY16,
Self::GrayF32 => PixelDescriptor::GRAYF32,
Self::GrayA8 => PixelDescriptor::GRAYA8,
Self::GrayA16 => PixelDescriptor::GRAYA16,
Self::GrayAF32 => PixelDescriptor::GRAYAF32,
Self::Bgra8 => PixelDescriptor::BGRA8,
Self::Rgbx8 => PixelDescriptor::RGBX8,
Self::Bgrx8 => PixelDescriptor::BGRX8,
Self::OklabF32 => PixelDescriptor::OKLABF32,
Self::OklabaF32 => PixelDescriptor::OKLABAF32,
_ => PixelDescriptor::RGB8,
}
}
}
impl fmt::Display for PixelFormat {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.name())
}
}
#[cfg(test)]
mod tests {
use alloc::format;
use core::mem::size_of;
use super::*;
#[test]
fn channel_type_byte_size() {
assert_eq!(ChannelType::U8.byte_size(), 1);
assert_eq!(ChannelType::U16.byte_size(), 2);
assert_eq!(ChannelType::F16.byte_size(), 2);
assert_eq!(ChannelType::F32.byte_size(), 4);
}
#[test]
fn descriptor_bytes_per_pixel() {
assert_eq!(PixelDescriptor::RGB8.bytes_per_pixel(), 3);
assert_eq!(PixelDescriptor::RGBA8.bytes_per_pixel(), 4);
assert_eq!(PixelDescriptor::GRAY8.bytes_per_pixel(), 1);
assert_eq!(PixelDescriptor::RGBAF32.bytes_per_pixel(), 16);
assert_eq!(PixelDescriptor::GRAYA8.bytes_per_pixel(), 2);
}
#[test]
fn descriptor_has_alpha() {
assert!(!PixelDescriptor::RGB8.has_alpha());
assert!(PixelDescriptor::RGBA8.has_alpha());
assert!(!PixelDescriptor::RGBX8.has_alpha());
assert!(PixelDescriptor::GRAYA8.has_alpha());
}
#[test]
fn descriptor_is_grayscale() {
assert!(PixelDescriptor::GRAY8.is_grayscale());
assert!(PixelDescriptor::GRAYA8.is_grayscale());
assert!(!PixelDescriptor::RGB8.is_grayscale());
}
#[test]
fn layout_compatible() {
assert!(PixelDescriptor::RGB8_SRGB.layout_compatible(PixelDescriptor::RGB8));
assert!(!PixelDescriptor::RGB8.layout_compatible(PixelDescriptor::RGBA8));
}
#[test]
fn pixel_format_descriptor_roundtrip() {
let desc = PixelFormat::Rgba8.descriptor();
assert_eq!(desc.layout(), ChannelLayout::Rgba);
assert_eq!(desc.channel_type(), ChannelType::U8);
}
#[test]
fn pixel_format_enum_basics() {
assert_eq!(PixelFormat::Rgb8.channels(), 3);
assert_eq!(PixelFormat::Rgba8.channels(), 4);
assert!(PixelFormat::Rgba8.has_alpha_bytes());
assert!(!PixelFormat::Rgb8.has_alpha_bytes());
assert_eq!(PixelFormat::RgbF32.bytes_per_pixel(), 12);
assert_eq!(PixelFormat::RgbaF32.bytes_per_pixel(), 16);
assert_eq!(PixelFormat::Gray8.channels(), 1);
assert!(PixelFormat::Gray8.is_grayscale());
assert!(!PixelFormat::Rgb8.is_grayscale());
assert_eq!(PixelFormat::Bgra8.byte_order(), ByteOrder::Bgr);
assert_eq!(PixelFormat::Rgb8.byte_order(), ByteOrder::Native);
}
#[test]
fn pixel_format_enum_size() {
assert!(size_of::<PixelFormat>() <= 2);
}
#[test]
fn pixel_format_from_parts_roundtrip() {
let fmt = PixelFormat::Rgba8;
let rebuilt =
PixelFormat::from_parts(fmt.channel_type(), fmt.layout(), fmt.default_alpha());
assert_eq!(rebuilt, Some(fmt));
let fmt2 = PixelFormat::Bgra8;
let rebuilt2 =
PixelFormat::from_parts(fmt2.channel_type(), fmt2.layout(), fmt2.default_alpha());
assert_eq!(rebuilt2, Some(fmt2));
let fmt3 = PixelFormat::Gray8;
let rebuilt3 =
PixelFormat::from_parts(fmt3.channel_type(), fmt3.layout(), fmt3.default_alpha());
assert_eq!(rebuilt3, Some(fmt3));
}
#[test]
fn alpha_mode_semantics() {
assert!(!PixelDescriptor::RGB8.has_alpha());
assert!(!AlphaMode::Undefined.has_alpha());
assert!(AlphaMode::Straight.has_alpha());
assert!(AlphaMode::Premultiplied.has_alpha());
assert!(AlphaMode::Opaque.has_alpha());
}
#[test]
fn color_primaries_containment() {
assert!(ColorPrimaries::Bt2020.contains(ColorPrimaries::DisplayP3));
assert!(ColorPrimaries::Bt2020.contains(ColorPrimaries::Bt709));
assert!(ColorPrimaries::DisplayP3.contains(ColorPrimaries::Bt709));
assert!(!ColorPrimaries::Bt709.contains(ColorPrimaries::DisplayP3));
assert!(!ColorPrimaries::Unknown.contains(ColorPrimaries::Bt709));
}
#[test]
fn descriptor_size() {
assert!(size_of::<PixelDescriptor>() <= 8);
}
#[test]
fn color_model_channels() {
assert_eq!(ColorModel::Gray.color_channels(), 1);
assert_eq!(ColorModel::Rgb.color_channels(), 3);
assert_eq!(ColorModel::YCbCr.color_channels(), 3);
assert_eq!(ColorModel::Oklab.color_channels(), 3);
}
#[test]
fn reference_white_nits_values() {
assert_eq!(TransferFunction::Pq.reference_white_nits(), 203.0);
assert_eq!(TransferFunction::Srgb.reference_white_nits(), 1.0);
assert_eq!(TransferFunction::Hlg.reference_white_nits(), 1.0);
assert_eq!(TransferFunction::Linear.reference_white_nits(), 1.0);
assert_eq!(TransferFunction::Unknown.reference_white_nits(), 1.0);
}
#[test]
fn channel_type_display() {
assert_eq!(format!("{}", ChannelType::U8), "U8");
assert_eq!(format!("{}", ChannelType::U16), "U16");
assert_eq!(format!("{}", ChannelType::F32), "F32");
assert_eq!(format!("{}", ChannelType::F16), "F16");
}
#[test]
fn channel_layout_display() {
assert_eq!(format!("{}", ChannelLayout::Gray), "Gray");
assert_eq!(format!("{}", ChannelLayout::GrayAlpha), "GrayAlpha");
assert_eq!(format!("{}", ChannelLayout::Rgb), "RGB");
assert_eq!(format!("{}", ChannelLayout::Rgba), "RGBA");
assert_eq!(format!("{}", ChannelLayout::Bgra), "BGRA");
assert_eq!(format!("{}", ChannelLayout::Oklab), "Oklab");
assert_eq!(format!("{}", ChannelLayout::OklabA), "OklabA");
}
#[test]
fn alpha_mode_display() {
assert_eq!(format!("{}", AlphaMode::Undefined), "undefined");
assert_eq!(format!("{}", AlphaMode::Straight), "straight");
assert_eq!(format!("{}", AlphaMode::Premultiplied), "premultiplied");
assert_eq!(format!("{}", AlphaMode::Opaque), "opaque");
}
#[test]
fn transfer_function_display() {
assert_eq!(format!("{}", TransferFunction::Linear), "linear");
assert_eq!(format!("{}", TransferFunction::Srgb), "sRGB");
assert_eq!(format!("{}", TransferFunction::Bt709), "BT.709");
assert_eq!(format!("{}", TransferFunction::Pq), "PQ");
assert_eq!(format!("{}", TransferFunction::Hlg), "HLG");
assert_eq!(format!("{}", TransferFunction::Unknown), "unknown");
}
#[test]
fn color_primaries_display() {
assert_eq!(format!("{}", ColorPrimaries::Bt709), "BT.709");
assert_eq!(format!("{}", ColorPrimaries::Bt2020), "BT.2020");
assert_eq!(format!("{}", ColorPrimaries::DisplayP3), "Display P3");
assert_eq!(format!("{}", ColorPrimaries::Unknown), "unknown");
}
#[test]
fn signal_range_display() {
assert_eq!(format!("{}", SignalRange::Full), "full");
assert_eq!(format!("{}", SignalRange::Narrow), "narrow");
}
#[test]
fn pixel_descriptor_display() {
let s = format!("{}", PixelDescriptor::RGB8_SRGB);
assert!(s.contains("U8"), "expected U8 in: {s}");
assert!(s.contains("sRGB"), "expected sRGB in: {s}");
let s = format!("{}", PixelDescriptor::RGBA8_SRGB);
assert!(s.contains("alpha=straight"), "expected alpha in: {s}");
}
#[test]
fn pixel_format_display() {
let s = format!("{}", PixelFormat::Rgb8);
assert!(s.contains("RGB8"));
let s = format!("{}", PixelFormat::Bgra8);
assert!(s.contains("BGRA8"));
}
#[test]
fn transfer_function_from_cicp() {
assert_eq!(
TransferFunction::from_cicp(1),
Some(TransferFunction::Bt709)
);
assert_eq!(
TransferFunction::from_cicp(8),
Some(TransferFunction::Linear)
);
assert_eq!(
TransferFunction::from_cicp(13),
Some(TransferFunction::Srgb)
);
assert_eq!(TransferFunction::from_cicp(16), Some(TransferFunction::Pq));
assert_eq!(TransferFunction::from_cicp(18), Some(TransferFunction::Hlg));
assert_eq!(TransferFunction::from_cicp(99), None);
}
#[test]
fn transfer_function_to_cicp() {
assert_eq!(TransferFunction::Bt709.to_cicp(), Some(1));
assert_eq!(TransferFunction::Linear.to_cicp(), Some(8));
assert_eq!(TransferFunction::Srgb.to_cicp(), Some(13));
assert_eq!(TransferFunction::Pq.to_cicp(), Some(16));
assert_eq!(TransferFunction::Hlg.to_cicp(), Some(18));
assert_eq!(TransferFunction::Unknown.to_cicp(), None);
}
#[test]
fn transfer_function_cicp_roundtrip() {
for tf in [
TransferFunction::Bt709,
TransferFunction::Linear,
TransferFunction::Srgb,
TransferFunction::Pq,
TransferFunction::Hlg,
] {
let code = tf.to_cicp().unwrap();
assert_eq!(TransferFunction::from_cicp(code), Some(tf));
}
}
#[test]
fn color_primaries_from_cicp() {
assert_eq!(ColorPrimaries::from_cicp(1), Some(ColorPrimaries::Bt709));
assert_eq!(ColorPrimaries::from_cicp(9), Some(ColorPrimaries::Bt2020));
assert_eq!(
ColorPrimaries::from_cicp(12),
Some(ColorPrimaries::DisplayP3)
);
assert_eq!(ColorPrimaries::from_cicp(99), None);
}
#[test]
fn color_primaries_to_cicp() {
assert_eq!(ColorPrimaries::Bt709.to_cicp(), Some(1));
assert_eq!(ColorPrimaries::Bt2020.to_cicp(), Some(9));
assert_eq!(ColorPrimaries::DisplayP3.to_cicp(), Some(12));
assert_eq!(ColorPrimaries::Unknown.to_cicp(), None);
}
#[test]
fn channel_type_helpers() {
assert!(ChannelType::U8.is_u8());
assert!(!ChannelType::U8.is_u16());
assert!(ChannelType::U16.is_u16());
assert!(ChannelType::F32.is_f32());
assert!(ChannelType::F16.is_f16());
assert!(ChannelType::U8.is_integer());
assert!(ChannelType::U16.is_integer());
assert!(!ChannelType::F32.is_integer());
assert!(ChannelType::F32.is_float());
assert!(ChannelType::F16.is_float());
assert!(!ChannelType::U8.is_float());
}
#[test]
fn channel_layout_channels() {
assert_eq!(ChannelLayout::Gray.channels(), 1);
assert_eq!(ChannelLayout::GrayAlpha.channels(), 2);
assert_eq!(ChannelLayout::Rgb.channels(), 3);
assert_eq!(ChannelLayout::Rgba.channels(), 4);
assert_eq!(ChannelLayout::Bgra.channels(), 4);
assert_eq!(ChannelLayout::Oklab.channels(), 3);
assert_eq!(ChannelLayout::OklabA.channels(), 4);
}
#[test]
fn channel_layout_has_alpha() {
assert!(!ChannelLayout::Gray.has_alpha());
assert!(ChannelLayout::GrayAlpha.has_alpha());
assert!(!ChannelLayout::Rgb.has_alpha());
assert!(ChannelLayout::Rgba.has_alpha());
assert!(ChannelLayout::Bgra.has_alpha());
assert!(!ChannelLayout::Oklab.has_alpha());
assert!(ChannelLayout::OklabA.has_alpha());
}
#[test]
fn with_transfer() {
let desc = PixelDescriptor::RGB8_SRGB.with_transfer(TransferFunction::Linear);
assert_eq!(desc.transfer(), TransferFunction::Linear);
assert_eq!(desc.layout(), ChannelLayout::Rgb);
}
#[test]
fn with_primaries() {
let desc = PixelDescriptor::RGB8_SRGB.with_primaries(ColorPrimaries::DisplayP3);
assert_eq!(desc.primaries, ColorPrimaries::DisplayP3);
}
#[test]
fn with_signal_range() {
let desc = PixelDescriptor::RGB8_SRGB.with_signal_range(SignalRange::Narrow);
assert_eq!(desc.signal_range, SignalRange::Narrow);
}
#[test]
fn with_alpha_mode() {
let desc = PixelDescriptor::RGBA8_SRGB.with_alpha(Some(AlphaMode::Premultiplied));
assert_eq!(desc.alpha(), Some(AlphaMode::Premultiplied));
}
#[test]
fn is_opaque_and_may_have_transparency() {
assert!(PixelDescriptor::RGB8_SRGB.is_opaque());
assert!(!PixelDescriptor::RGB8_SRGB.may_have_transparency());
assert!(!PixelDescriptor::RGBA8_SRGB.is_opaque());
assert!(PixelDescriptor::RGBA8_SRGB.may_have_transparency());
let rgbx = PixelDescriptor::new(
ChannelType::U8,
ChannelLayout::Rgba,
Some(AlphaMode::Undefined),
TransferFunction::Srgb,
);
assert!(rgbx.is_opaque());
assert!(!rgbx.may_have_transparency());
}
#[test]
fn is_linear_and_is_unknown_transfer() {
assert!(!PixelDescriptor::RGB8_SRGB.is_linear());
assert!(PixelDescriptor::RGBF32_LINEAR.is_linear());
assert!(!PixelDescriptor::RGB8_SRGB.is_unknown_transfer());
let desc = PixelDescriptor::RGB8_SRGB.with_transfer(TransferFunction::Unknown);
assert!(desc.is_unknown_transfer());
}
#[test]
fn min_alignment() {
assert_eq!(PixelDescriptor::RGB8_SRGB.min_alignment(), 1);
assert_eq!(PixelDescriptor::RGBF32_LINEAR.min_alignment(), 4);
}
#[test]
fn aligned_stride() {
assert_eq!(PixelDescriptor::RGB8_SRGB.aligned_stride(100), 300);
assert_eq!(PixelDescriptor::RGBA8_SRGB.aligned_stride(100), 400);
assert_eq!(PixelDescriptor::RGBF32_LINEAR.aligned_stride(10), 120);
}
#[test]
fn simd_aligned_stride() {
let stride = PixelDescriptor::RGB8_SRGB.simd_aligned_stride(100, 16);
assert!(stride >= 300);
assert_eq!(stride % 16, 0);
assert_eq!(stride % 3, 0); }
#[test]
fn new_full_constructor() {
let desc = PixelDescriptor::new_full(
ChannelType::U8,
ChannelLayout::Rgb,
None,
TransferFunction::Srgb,
ColorPrimaries::DisplayP3,
);
assert_eq!(desc.primaries, ColorPrimaries::DisplayP3);
assert_eq!(desc.transfer(), TransferFunction::Srgb);
}
#[test]
fn from_pixel_format_constructor() {
let desc = PixelDescriptor::from_pixel_format(PixelFormat::Rgba8);
assert_eq!(desc.layout(), ChannelLayout::Rgba);
assert_eq!(desc.transfer(), TransferFunction::Unknown);
assert_eq!(desc.primaries, ColorPrimaries::Bt709);
assert_eq!(desc.signal_range, SignalRange::Full);
}
#[test]
fn pixel_format_name() {
assert_eq!(PixelFormat::Rgb8.name(), "RGB8");
assert_eq!(PixelFormat::Bgra8.name(), "BGRA8");
assert_eq!(PixelFormat::Gray8.name(), "Gray8");
}
#[test]
fn color_model_display() {
assert_eq!(format!("{}", ColorModel::Gray), "Gray");
assert_eq!(format!("{}", ColorModel::Rgb), "RGB");
assert_eq!(format!("{}", ColorModel::YCbCr), "YCbCr");
assert_eq!(format!("{}", ColorModel::Oklab), "Oklab");
}
#[test]
fn signal_range_default() {
assert_eq!(SignalRange::default(), SignalRange::Full);
}
#[test]
fn color_primaries_default() {
assert_eq!(ColorPrimaries::default(), ColorPrimaries::Bt709);
}
}