pub const TAG_NEW_SUBFILE_TYPE: u16 = 254;
pub const TAG_SUBFILE_TYPE: u16 = 255;
pub const TAG_IMAGE_WIDTH: u16 = 256;
pub const TAG_IMAGE_LENGTH: u16 = 257;
pub const TAG_BITS_PER_SAMPLE: u16 = 258;
pub const TAG_COMPRESSION: u16 = 259;
pub const TAG_PHOTOMETRIC_INTERPRETATION: u16 = 262;
pub const TAG_STRIP_OFFSETS: u16 = 273;
pub const TAG_SAMPLES_PER_PIXEL: u16 = 277;
pub const TAG_ROWS_PER_STRIP: u16 = 278;
pub const TAG_STRIP_BYTE_COUNTS: u16 = 279;
pub const TAG_PLANAR_CONFIGURATION: u16 = 284;
pub const TAG_PREDICTOR: u16 = 317;
pub const TAG_COLOR_MAP: u16 = 320;
pub const TAG_TILE_WIDTH: u16 = 322;
pub const TAG_TILE_LENGTH: u16 = 323;
pub const TAG_TILE_OFFSETS: u16 = 324;
pub const TAG_TILE_BYTE_COUNTS: u16 = 325;
pub const TAG_SUB_IFDS: u16 = 330;
pub const TAG_INK_SET: u16 = 332;
pub const TAG_EXTRA_SAMPLES: u16 = 338;
pub const TAG_SAMPLE_FORMAT: u16 = 339;
pub const TAG_JPEG_TABLES: u16 = 347;
pub const TAG_YCBCR_SUBSAMPLING: u16 = 530;
pub const TAG_YCBCR_POSITIONING: u16 = 531;
pub const TAG_REFERENCE_BLACK_WHITE: u16 = 532;
pub const TAG_LERC_PARAMETERS: u16 = 50674;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Compression {
None,
Lzw,
OldJpeg,
Jpeg,
Deflate,
PackBits,
DeflateOld,
Lerc,
Zstd,
}
impl Compression {
pub fn from_code(code: u16) -> Option<Self> {
match code {
1 => Some(Self::None),
5 => Some(Self::Lzw),
6 => Some(Self::OldJpeg),
7 => Some(Self::Jpeg),
8 => Some(Self::Deflate),
32773 => Some(Self::PackBits),
32946 => Some(Self::DeflateOld),
34887 => Some(Self::Lerc),
50000 => Some(Self::Zstd),
_ => None,
}
}
pub fn to_code(self) -> u16 {
match self {
Self::None => 1,
Self::Lzw => 5,
Self::OldJpeg => 6,
Self::Jpeg => 7,
Self::Deflate => 8,
Self::PackBits => 32773,
Self::DeflateOld => 32946,
Self::Lerc => 34887,
Self::Zstd => 50000,
}
}
pub fn name(self) -> &'static str {
match self {
Self::None => "None",
Self::Lzw => "LZW",
Self::OldJpeg => "OldJpeg",
Self::Jpeg => "JPEG",
Self::Deflate => "Deflate",
Self::PackBits => "PackBits",
Self::DeflateOld => "DeflateOld",
Self::Lerc => "LERC",
Self::Zstd => "ZSTD",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Predictor {
None,
Horizontal,
FloatingPoint,
}
impl Predictor {
pub fn from_code(code: u16) -> Option<Self> {
match code {
1 => Some(Self::None),
2 => Some(Self::Horizontal),
3 => Some(Self::FloatingPoint),
_ => None,
}
}
pub fn to_code(self) -> u16 {
match self {
Self::None => 1,
Self::Horizontal => 2,
Self::FloatingPoint => 3,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SampleFormat {
Uint,
Int,
Float,
}
impl SampleFormat {
pub fn from_code(code: u16) -> Option<Self> {
match code {
1 => Some(Self::Uint),
2 => Some(Self::Int),
3 => Some(Self::Float),
_ => None,
}
}
pub fn to_code(self) -> u16 {
match self {
Self::Uint => 1,
Self::Int => 2,
Self::Float => 3,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum PhotometricInterpretation {
MinIsWhite,
MinIsBlack,
Rgb,
Palette,
Mask,
Separated,
YCbCr,
CieLab,
}
impl PhotometricInterpretation {
pub fn from_code(code: u16) -> Option<Self> {
match code {
0 => Some(Self::MinIsWhite),
1 => Some(Self::MinIsBlack),
2 => Some(Self::Rgb),
3 => Some(Self::Palette),
4 => Some(Self::Mask),
5 => Some(Self::Separated),
6 => Some(Self::YCbCr),
8 => Some(Self::CieLab),
_ => None,
}
}
pub fn to_code(self) -> u16 {
match self {
Self::MinIsWhite => 0,
Self::MinIsBlack => 1,
Self::Rgb => 2,
Self::Palette => 3,
Self::Mask => 4,
Self::Separated => 5,
Self::YCbCr => 6,
Self::CieLab => 8,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ExtraSample {
Unspecified,
AssociatedAlpha,
UnassociatedAlpha,
Unknown(u16),
}
impl ExtraSample {
pub fn from_code(code: u16) -> Self {
match code {
0 => Self::Unspecified,
1 => Self::AssociatedAlpha,
2 => Self::UnassociatedAlpha,
other => Self::Unknown(other),
}
}
pub fn to_code(self) -> u16 {
match self {
Self::Unspecified => 0,
Self::AssociatedAlpha => 1,
Self::UnassociatedAlpha => 2,
Self::Unknown(code) => code,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum YCbCrPositioning {
Centered,
Cosited,
Unknown(u16),
}
impl YCbCrPositioning {
pub fn from_code(code: u16) -> Self {
match code {
1 => Self::Centered,
2 => Self::Cosited,
other => Self::Unknown(other),
}
}
pub fn to_code(self) -> u16 {
match self {
Self::Centered => 1,
Self::Cosited => 2,
Self::Unknown(code) => code,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum InkSet {
Cmyk,
NotCmyk,
Unknown(u16),
}
impl InkSet {
pub fn from_code(code: u16) -> Self {
match code {
1 => Self::Cmyk,
2 => Self::NotCmyk,
other => Self::Unknown(other),
}
}
pub fn to_code(self) -> u16 {
match self {
Self::Cmyk => 1,
Self::NotCmyk => 2,
Self::Unknown(code) => code,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ColorMap {
red: Vec<u16>,
green: Vec<u16>,
blue: Vec<u16>,
}
impl ColorMap {
pub fn new(red: Vec<u16>, green: Vec<u16>, blue: Vec<u16>) -> Result<Self, String> {
let len = red.len();
if green.len() != len || blue.len() != len {
return Err(format!(
"ColorMap planes must have equal length, got red={}, green={}, blue={}",
red.len(),
green.len(),
blue.len()
));
}
Ok(Self { red, green, blue })
}
pub fn from_tag_values(values: &[u16]) -> Result<Self, String> {
if values.len() % 3 != 0 {
return Err(format!(
"ColorMap tag length must be divisible by 3, got {} values",
values.len()
));
}
let plane_len = values.len() / 3;
Self::new(
values[..plane_len].to_vec(),
values[plane_len..plane_len * 2].to_vec(),
values[plane_len * 2..].to_vec(),
)
}
pub fn len(&self) -> usize {
self.red.len()
}
pub fn is_empty(&self) -> bool {
self.red.is_empty()
}
pub fn red(&self) -> &[u16] {
&self.red
}
pub fn green(&self) -> &[u16] {
&self.green
}
pub fn blue(&self) -> &[u16] {
&self.blue
}
pub fn encode_tag_values(&self) -> Vec<u16> {
let mut values = Vec::with_capacity(self.len() * 3);
values.extend_from_slice(&self.red);
values.extend_from_slice(&self.green);
values.extend_from_slice(&self.blue);
values
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ColorModel {
Grayscale {
white_is_zero: bool,
extra_samples: Vec<ExtraSample>,
},
Palette {
color_map: ColorMap,
extra_samples: Vec<ExtraSample>,
},
Rgb {
extra_samples: Vec<ExtraSample>,
},
TransparencyMask,
Cmyk {
extra_samples: Vec<ExtraSample>,
},
Separated {
ink_set: InkSet,
color_channels: u16,
extra_samples: Vec<ExtraSample>,
},
YCbCr {
subsampling: [u16; 2],
positioning: YCbCrPositioning,
extra_samples: Vec<ExtraSample>,
},
CieLab {
extra_samples: Vec<ExtraSample>,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum PlanarConfiguration {
Chunky,
Planar,
}
impl PlanarConfiguration {
pub fn from_code(code: u16) -> Option<Self> {
match code {
1 => Some(Self::Chunky),
2 => Some(Self::Planar),
_ => None,
}
}
pub fn to_code(self) -> u16 {
match self {
Self::Chunky => 1,
Self::Planar => 2,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum LercAdditionalCompression {
None,
Deflate,
Zstd,
}
impl LercAdditionalCompression {
pub fn from_code(code: u32) -> Option<Self> {
match code {
0 => Some(Self::None),
1 => Some(Self::Deflate),
2 => Some(Self::Zstd),
_ => None,
}
}
pub fn to_code(self) -> u32 {
match self {
Self::None => 0,
Self::Deflate => 1,
Self::Zstd => 2,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn compression_roundtrips_lerc() {
assert_eq!(Compression::from_code(34887), Some(Compression::Lerc));
assert_eq!(Compression::Lerc.to_code(), 34887);
assert_eq!(Compression::Lerc.name(), "LERC");
}
#[test]
fn lerc_parameters_tag_matches_registered_value() {
assert_eq!(TAG_LERC_PARAMETERS, 50674);
}
#[test]
fn lerc_additional_compression_roundtrips() {
for (code, expected) in [
(0, LercAdditionalCompression::None),
(1, LercAdditionalCompression::Deflate),
(2, LercAdditionalCompression::Zstd),
] {
assert_eq!(LercAdditionalCompression::from_code(code), Some(expected));
assert_eq!(expected.to_code(), code);
}
assert_eq!(LercAdditionalCompression::from_code(99), None);
}
#[test]
fn photometric_roundtrips_extended_color_models() {
for (code, expected) in [
(5, PhotometricInterpretation::Separated),
(6, PhotometricInterpretation::YCbCr),
(8, PhotometricInterpretation::CieLab),
] {
assert_eq!(PhotometricInterpretation::from_code(code), Some(expected));
assert_eq!(expected.to_code(), code);
}
}
#[test]
fn color_map_splits_tag_values_into_rgb_planes() {
let values = vec![1u16, 2, 10, 20, 100, 200];
let color_map = ColorMap::from_tag_values(&values).unwrap();
assert_eq!(color_map.red(), &[1, 2]);
assert_eq!(color_map.green(), &[10, 20]);
assert_eq!(color_map.blue(), &[100, 200]);
assert_eq!(color_map.encode_tag_values(), values);
}
#[test]
fn extra_sample_and_ink_set_roundtrip() {
assert_eq!(ExtraSample::from_code(1), ExtraSample::AssociatedAlpha);
assert_eq!(ExtraSample::UnassociatedAlpha.to_code(), 2);
assert_eq!(InkSet::from_code(1), InkSet::Cmyk);
assert_eq!(InkSet::NotCmyk.to_code(), 2);
}
}