use crate::{ChannelLayout, ChannelType, ColorPrimaries, PixelDescriptor, TransferFunction};
#[derive(Clone, Copy, Debug)]
pub struct FormatEntry {
pub descriptor: PixelDescriptor,
pub effective_bits: u8,
pub can_overshoot: bool,
}
impl FormatEntry {
const fn standard(descriptor: PixelDescriptor) -> Self {
let effective_bits = match descriptor.channel_type() {
ChannelType::U8 => 8,
ChannelType::U16 => 16,
ChannelType::F16 => 11,
ChannelType::F32 => 32,
_ => 0,
};
Self {
descriptor,
effective_bits,
can_overshoot: false,
}
}
const fn with_bits(descriptor: PixelDescriptor, effective_bits: u8) -> Self {
Self {
descriptor,
effective_bits,
can_overshoot: false,
}
}
const fn overshoot(descriptor: PixelDescriptor, effective_bits: u8) -> Self {
Self {
descriptor,
effective_bits,
can_overshoot: true,
}
}
}
#[derive(Clone, Debug)]
pub struct CodecFormats {
pub name: &'static str,
pub decode_outputs: &'static [FormatEntry],
pub encode_inputs: &'static [FormatEntry],
pub icc_decode: bool,
pub icc_encode: bool,
pub cicp: bool,
}
static JPEG_DECODE: &[FormatEntry] = &[
FormatEntry::with_bits(PixelDescriptor::RGB8_SRGB, 8),
FormatEntry::with_bits(PixelDescriptor::RGBA8_SRGB, 8),
FormatEntry::with_bits(PixelDescriptor::GRAY8_SRGB, 8),
FormatEntry::with_bits(PixelDescriptor::BGRA8_SRGB, 8),
];
static JPEG_ENCODE: &[FormatEntry] = &[
FormatEntry::standard(PixelDescriptor::RGB8_SRGB),
FormatEntry::standard(PixelDescriptor::RGBA8_SRGB),
FormatEntry::standard(PixelDescriptor::GRAY8_SRGB),
FormatEntry::standard(PixelDescriptor::BGRA8_SRGB),
FormatEntry::standard(PixelDescriptor::RGB16_SRGB),
FormatEntry::standard(PixelDescriptor::RGBA16_SRGB),
FormatEntry::standard(PixelDescriptor::GRAY16_SRGB),
FormatEntry::standard(PixelDescriptor::RGBF32_LINEAR),
FormatEntry::standard(PixelDescriptor::RGBAF32_LINEAR),
FormatEntry::standard(PixelDescriptor::GRAYF32_LINEAR),
];
pub static JPEG_DECODE_EXTENDED: &[FormatEntry] = &[
FormatEntry::overshoot(
PixelDescriptor::new_full(
ChannelType::F32,
ChannelLayout::Rgb,
None,
TransferFunction::Srgb,
ColorPrimaries::Bt709,
),
8,
),
FormatEntry::overshoot(PixelDescriptor::RGBF32_LINEAR, 8),
FormatEntry::overshoot(
PixelDescriptor::new_full(
ChannelType::F32,
ChannelLayout::Rgb,
None,
TransferFunction::Srgb,
ColorPrimaries::Bt709,
),
10,
),
FormatEntry::overshoot(PixelDescriptor::RGBF32_LINEAR, 10),
FormatEntry::overshoot(PixelDescriptor::GRAYF32_LINEAR, 8),
];
pub static JPEG: CodecFormats = CodecFormats {
name: "jpeg",
decode_outputs: JPEG_DECODE,
encode_inputs: JPEG_ENCODE,
icc_decode: true,
icc_encode: true,
cicp: false,
};
static PNG_FORMATS: &[FormatEntry] = &[
FormatEntry::standard(PixelDescriptor::RGB8_SRGB),
FormatEntry::standard(PixelDescriptor::RGBA8_SRGB),
FormatEntry::standard(PixelDescriptor::GRAY8_SRGB),
FormatEntry::standard(PixelDescriptor::GRAYA8_SRGB),
FormatEntry::standard(PixelDescriptor::BGRA8_SRGB),
FormatEntry::standard(PixelDescriptor::RGB16_SRGB),
FormatEntry::standard(PixelDescriptor::RGBA16_SRGB),
FormatEntry::standard(PixelDescriptor::GRAY16_SRGB),
FormatEntry::standard(PixelDescriptor::GRAYA16_SRGB),
FormatEntry::standard(PixelDescriptor::RGBF32_LINEAR),
FormatEntry::standard(PixelDescriptor::RGBAF32_LINEAR),
FormatEntry::standard(PixelDescriptor::GRAYF32_LINEAR),
FormatEntry::standard(PixelDescriptor::GRAYAF32_LINEAR),
];
pub static PNG: CodecFormats = CodecFormats {
name: "png",
decode_outputs: PNG_FORMATS,
encode_inputs: PNG_FORMATS,
icc_decode: true,
icc_encode: true,
cicp: true,
};
static GIF_DECODE: &[FormatEntry] = &[
FormatEntry::with_bits(PixelDescriptor::RGBA8_SRGB, 8),
FormatEntry::with_bits(PixelDescriptor::RGB8_SRGB, 8),
FormatEntry::with_bits(PixelDescriptor::GRAY8_SRGB, 8),
FormatEntry::with_bits(PixelDescriptor::BGRA8_SRGB, 8),
FormatEntry::with_bits(PixelDescriptor::RGBF32_LINEAR, 8),
FormatEntry::with_bits(PixelDescriptor::RGBAF32_LINEAR, 8),
FormatEntry::with_bits(PixelDescriptor::GRAYF32_LINEAR, 8),
];
static GIF_ENCODE: &[FormatEntry] = &[
FormatEntry::standard(PixelDescriptor::RGBA8_SRGB),
FormatEntry::standard(PixelDescriptor::RGB8_SRGB),
FormatEntry::standard(PixelDescriptor::GRAY8_SRGB),
FormatEntry::standard(PixelDescriptor::BGRA8_SRGB),
];
pub static GIF: CodecFormats = CodecFormats {
name: "gif",
decode_outputs: GIF_DECODE,
encode_inputs: GIF_ENCODE,
icc_decode: false,
icc_encode: false,
cicp: false,
};
static WEBP_FORMATS: &[FormatEntry] = &[
FormatEntry::standard(PixelDescriptor::RGB8_SRGB),
FormatEntry::standard(PixelDescriptor::RGBA8_SRGB),
];
pub static WEBP: CodecFormats = CodecFormats {
name: "webp",
decode_outputs: WEBP_FORMATS,
encode_inputs: WEBP_FORMATS,
icc_decode: true,
icc_encode: true,
cicp: false,
};
static AVIF_FORMATS: &[FormatEntry] = &[
FormatEntry::with_bits(PixelDescriptor::RGB8_SRGB, 8),
FormatEntry::with_bits(PixelDescriptor::RGBA8_SRGB, 8),
];
pub static AVIF: CodecFormats = CodecFormats {
name: "avif",
decode_outputs: AVIF_FORMATS,
encode_inputs: AVIF_FORMATS,
icc_decode: true,
icc_encode: false,
cicp: true,
};
static JXL_FORMATS: &[FormatEntry] = &[
FormatEntry::standard(PixelDescriptor::RGB8_SRGB),
FormatEntry::standard(PixelDescriptor::RGBA8_SRGB),
FormatEntry::standard(PixelDescriptor::GRAY8_SRGB),
FormatEntry::standard(PixelDescriptor::GRAYA8_SRGB),
FormatEntry::standard(PixelDescriptor::BGRA8_SRGB),
FormatEntry::standard(PixelDescriptor::RGBF32_LINEAR),
FormatEntry::standard(PixelDescriptor::RGBAF32_LINEAR),
FormatEntry::standard(PixelDescriptor::GRAYF32_LINEAR),
FormatEntry::standard(PixelDescriptor::GRAYAF32_LINEAR),
];
pub static JXL: CodecFormats = CodecFormats {
name: "jxl",
decode_outputs: JXL_FORMATS,
encode_inputs: JXL_FORMATS,
icc_decode: true,
icc_encode: true,
cicp: true,
};
static BMP_FORMATS: &[FormatEntry] = &[
FormatEntry::standard(PixelDescriptor::RGB8_SRGB),
FormatEntry::standard(PixelDescriptor::RGBA8_SRGB),
FormatEntry::standard(PixelDescriptor::BGRA8_SRGB),
];
pub static BMP: CodecFormats = CodecFormats {
name: "bmp",
decode_outputs: BMP_FORMATS,
encode_inputs: BMP_FORMATS,
icc_decode: false,
icc_encode: false,
cicp: false,
};
static FARBFELD_FORMATS: &[FormatEntry] = &[
FormatEntry::standard(PixelDescriptor::RGBA16_SRGB),
FormatEntry::with_bits(PixelDescriptor::RGBA8_SRGB, 8),
FormatEntry::with_bits(PixelDescriptor::RGB8_SRGB, 8),
FormatEntry::with_bits(PixelDescriptor::GRAY8_SRGB, 8),
];
pub static FARBFELD: CodecFormats = CodecFormats {
name: "farbfeld",
decode_outputs: FARBFELD_FORMATS,
encode_inputs: FARBFELD_FORMATS,
icc_decode: false,
icc_encode: false,
cicp: false,
};
static PNM_FORMATS: &[FormatEntry] = &[
FormatEntry::standard(PixelDescriptor::RGB8_SRGB),
FormatEntry::standard(PixelDescriptor::RGBA8_SRGB),
FormatEntry::standard(PixelDescriptor::RGBA16_SRGB),
FormatEntry::standard(PixelDescriptor::GRAY8_SRGB),
FormatEntry::standard(PixelDescriptor::GRAYA8_SRGB),
FormatEntry::standard(PixelDescriptor::BGRA8_SRGB),
FormatEntry::standard(PixelDescriptor::RGBF32_LINEAR),
FormatEntry::standard(PixelDescriptor::RGBAF32_LINEAR),
FormatEntry::standard(PixelDescriptor::GRAYF32_LINEAR),
FormatEntry::standard(PixelDescriptor::GRAYAF32_LINEAR),
];
pub static PNM: CodecFormats = CodecFormats {
name: "pnm",
decode_outputs: PNM_FORMATS,
encode_inputs: PNM_FORMATS,
icc_decode: false,
icc_encode: false,
cicp: false,
};
pub static ALL_CODECS: &[&CodecFormats] =
&[&JPEG, &PNG, &GIF, &WEBP, &AVIF, &JXL, &BMP, &FARBFELD, &PNM];
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn all_codecs_have_decode_and_encode() {
for codec in ALL_CODECS {
assert!(
!codec.decode_outputs.is_empty(),
"{} has no decode outputs",
codec.name
);
assert!(
!codec.encode_inputs.is_empty(),
"{} has no encode inputs",
codec.name
);
}
}
#[test]
fn effective_bits_within_container() {
for codec in ALL_CODECS {
for entry in codec
.decode_outputs
.iter()
.chain(codec.encode_inputs.iter())
{
let container_bits = match entry.descriptor.channel_type() {
ChannelType::U8 => 8,
ChannelType::U16 => 16,
ChannelType::F16 => 16,
ChannelType::F32 => 32,
_ => 0,
};
assert!(
entry.effective_bits <= container_bits,
"{}: effective_bits {} > container {} for {:?}",
codec.name,
entry.effective_bits,
container_bits,
entry.descriptor
);
}
}
}
#[test]
fn jpeg_extended_has_overshoot() {
for entry in JPEG_DECODE_EXTENDED {
assert!(
entry.can_overshoot,
"JPEG extended f32 decode should have overshoot"
);
}
}
#[test]
fn codec_count() {
assert_eq!(ALL_CODECS.len(), 9);
}
}