1pub const TAG_NEW_SUBFILE_TYPE: u16 = 254;
3pub const TAG_SUBFILE_TYPE: u16 = 255;
4pub const TAG_IMAGE_WIDTH: u16 = 256;
5pub const TAG_IMAGE_LENGTH: u16 = 257;
6pub const TAG_BITS_PER_SAMPLE: u16 = 258;
7pub const TAG_COMPRESSION: u16 = 259;
8pub const TAG_PHOTOMETRIC_INTERPRETATION: u16 = 262;
9pub const TAG_STRIP_OFFSETS: u16 = 273;
10pub const TAG_SAMPLES_PER_PIXEL: u16 = 277;
11pub const TAG_ROWS_PER_STRIP: u16 = 278;
12pub const TAG_STRIP_BYTE_COUNTS: u16 = 279;
13pub const TAG_PLANAR_CONFIGURATION: u16 = 284;
14pub const TAG_PREDICTOR: u16 = 317;
15pub const TAG_COLOR_MAP: u16 = 320;
16pub const TAG_TILE_WIDTH: u16 = 322;
17pub const TAG_TILE_LENGTH: u16 = 323;
18pub const TAG_TILE_OFFSETS: u16 = 324;
19pub const TAG_TILE_BYTE_COUNTS: u16 = 325;
20pub const TAG_SUB_IFDS: u16 = 330;
21pub const TAG_INK_SET: u16 = 332;
22pub const TAG_EXTRA_SAMPLES: u16 = 338;
23pub const TAG_SAMPLE_FORMAT: u16 = 339;
24pub const TAG_JPEG_TABLES: u16 = 347;
25pub const TAG_YCBCR_SUBSAMPLING: u16 = 530;
26pub const TAG_YCBCR_POSITIONING: u16 = 531;
27pub const TAG_REFERENCE_BLACK_WHITE: u16 = 532;
28pub const TAG_LERC_PARAMETERS: u16 = 50674;
29
30pub const LERC_VERSION_2_4: u32 = 4;
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
37pub enum Compression {
38 None,
39 Lzw,
40 OldJpeg,
41 Jpeg,
42 Deflate,
43 PackBits,
44 DeflateOld,
45 Lerc,
46 Zstd,
47}
48
49impl Compression {
50 pub fn from_code(code: u16) -> Option<Self> {
51 match code {
52 1 => Some(Self::None),
53 5 => Some(Self::Lzw),
54 6 => Some(Self::OldJpeg),
55 7 => Some(Self::Jpeg),
56 8 => Some(Self::Deflate),
57 32773 => Some(Self::PackBits),
58 32946 => Some(Self::DeflateOld),
59 34887 => Some(Self::Lerc),
60 50000 => Some(Self::Zstd),
61 _ => None,
62 }
63 }
64
65 pub fn to_code(self) -> u16 {
66 match self {
67 Self::None => 1,
68 Self::Lzw => 5,
69 Self::OldJpeg => 6,
70 Self::Jpeg => 7,
71 Self::Deflate => 8,
72 Self::PackBits => 32773,
73 Self::DeflateOld => 32946,
74 Self::Lerc => 34887,
75 Self::Zstd => 50000,
76 }
77 }
78
79 pub fn name(self) -> &'static str {
80 match self {
81 Self::None => "None",
82 Self::Lzw => "LZW",
83 Self::OldJpeg => "OldJpeg",
84 Self::Jpeg => "JPEG",
85 Self::Deflate => "Deflate",
86 Self::PackBits => "PackBits",
87 Self::DeflateOld => "DeflateOld",
88 Self::Lerc => "LERC",
89 Self::Zstd => "ZSTD",
90 }
91 }
92}
93
94#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
96pub enum Predictor {
97 None,
98 Horizontal,
99 FloatingPoint,
100}
101
102impl Predictor {
103 pub fn from_code(code: u16) -> Option<Self> {
104 match code {
105 1 => Some(Self::None),
106 2 => Some(Self::Horizontal),
107 3 => Some(Self::FloatingPoint),
108 _ => None,
109 }
110 }
111
112 pub fn to_code(self) -> u16 {
113 match self {
114 Self::None => 1,
115 Self::Horizontal => 2,
116 Self::FloatingPoint => 3,
117 }
118 }
119}
120
121#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
123pub enum SampleFormat {
124 Uint,
125 Int,
126 Float,
127}
128
129impl SampleFormat {
130 pub fn from_code(code: u16) -> Option<Self> {
131 match code {
132 1 => Some(Self::Uint),
133 2 => Some(Self::Int),
134 3 => Some(Self::Float),
135 _ => None,
136 }
137 }
138
139 pub fn to_code(self) -> u16 {
140 match self {
141 Self::Uint => 1,
142 Self::Int => 2,
143 Self::Float => 3,
144 }
145 }
146}
147
148#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
150pub enum PhotometricInterpretation {
151 MinIsWhite,
152 MinIsBlack,
153 Rgb,
154 Palette,
155 Mask,
156 Separated,
157 YCbCr,
158 CieLab,
159}
160
161impl PhotometricInterpretation {
162 pub fn from_code(code: u16) -> Option<Self> {
163 match code {
164 0 => Some(Self::MinIsWhite),
165 1 => Some(Self::MinIsBlack),
166 2 => Some(Self::Rgb),
167 3 => Some(Self::Palette),
168 4 => Some(Self::Mask),
169 5 => Some(Self::Separated),
170 6 => Some(Self::YCbCr),
171 8 => Some(Self::CieLab),
172 _ => None,
173 }
174 }
175
176 pub fn to_code(self) -> u16 {
177 match self {
178 Self::MinIsWhite => 0,
179 Self::MinIsBlack => 1,
180 Self::Rgb => 2,
181 Self::Palette => 3,
182 Self::Mask => 4,
183 Self::Separated => 5,
184 Self::YCbCr => 6,
185 Self::CieLab => 8,
186 }
187 }
188}
189
190#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
192pub enum ExtraSample {
193 Unspecified,
194 AssociatedAlpha,
195 UnassociatedAlpha,
196 Unknown(u16),
197}
198
199impl ExtraSample {
200 pub fn from_code(code: u16) -> Self {
201 match code {
202 0 => Self::Unspecified,
203 1 => Self::AssociatedAlpha,
204 2 => Self::UnassociatedAlpha,
205 other => Self::Unknown(other),
206 }
207 }
208
209 pub fn to_code(self) -> u16 {
210 match self {
211 Self::Unspecified => 0,
212 Self::AssociatedAlpha => 1,
213 Self::UnassociatedAlpha => 2,
214 Self::Unknown(code) => code,
215 }
216 }
217}
218
219#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
221pub enum YCbCrPositioning {
222 Centered,
223 Cosited,
224 Unknown(u16),
225}
226
227impl YCbCrPositioning {
228 pub fn from_code(code: u16) -> Self {
229 match code {
230 1 => Self::Centered,
231 2 => Self::Cosited,
232 other => Self::Unknown(other),
233 }
234 }
235
236 pub fn to_code(self) -> u16 {
237 match self {
238 Self::Centered => 1,
239 Self::Cosited => 2,
240 Self::Unknown(code) => code,
241 }
242 }
243}
244
245#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
247pub enum InkSet {
248 Cmyk,
249 NotCmyk,
250 Unknown(u16),
251}
252
253impl InkSet {
254 pub fn from_code(code: u16) -> Self {
255 match code {
256 1 => Self::Cmyk,
257 2 => Self::NotCmyk,
258 other => Self::Unknown(other),
259 }
260 }
261
262 pub fn to_code(self) -> u16 {
263 match self {
264 Self::Cmyk => 1,
265 Self::NotCmyk => 2,
266 Self::Unknown(code) => code,
267 }
268 }
269}
270
271#[derive(Debug, Clone, PartialEq, Eq)]
273pub struct ColorMap {
274 red: Vec<u16>,
275 green: Vec<u16>,
276 blue: Vec<u16>,
277}
278
279impl ColorMap {
280 pub fn new(red: Vec<u16>, green: Vec<u16>, blue: Vec<u16>) -> Result<Self, String> {
281 let len = red.len();
282 if green.len() != len || blue.len() != len {
283 return Err(format!(
284 "ColorMap planes must have equal length, got red={}, green={}, blue={}",
285 red.len(),
286 green.len(),
287 blue.len()
288 ));
289 }
290 Ok(Self { red, green, blue })
291 }
292
293 pub fn from_tag_values(values: &[u16]) -> Result<Self, String> {
294 if values.len() % 3 != 0 {
295 return Err(format!(
296 "ColorMap tag length must be divisible by 3, got {} values",
297 values.len()
298 ));
299 }
300 let plane_len = values.len() / 3;
301 Self::new(
302 values[..plane_len].to_vec(),
303 values[plane_len..plane_len * 2].to_vec(),
304 values[plane_len * 2..].to_vec(),
305 )
306 }
307
308 pub fn len(&self) -> usize {
309 self.red.len()
310 }
311
312 pub fn is_empty(&self) -> bool {
313 self.red.is_empty()
314 }
315
316 pub fn red(&self) -> &[u16] {
317 &self.red
318 }
319
320 pub fn green(&self) -> &[u16] {
321 &self.green
322 }
323
324 pub fn blue(&self) -> &[u16] {
325 &self.blue
326 }
327
328 pub fn encode_tag_values(&self) -> Vec<u16> {
329 let mut values = Vec::with_capacity(self.len() * 3);
330 values.extend_from_slice(&self.red);
331 values.extend_from_slice(&self.green);
332 values.extend_from_slice(&self.blue);
333 values
334 }
335}
336
337#[derive(Debug, Clone, PartialEq, Eq)]
339pub enum ColorModel {
340 Grayscale {
341 white_is_zero: bool,
342 extra_samples: Vec<ExtraSample>,
343 },
344 Palette {
345 color_map: ColorMap,
346 extra_samples: Vec<ExtraSample>,
347 },
348 Rgb {
349 extra_samples: Vec<ExtraSample>,
350 },
351 TransparencyMask,
352 Cmyk {
353 extra_samples: Vec<ExtraSample>,
354 },
355 Separated {
356 ink_set: InkSet,
357 color_channels: u16,
358 extra_samples: Vec<ExtraSample>,
359 },
360 YCbCr {
361 subsampling: [u16; 2],
362 positioning: YCbCrPositioning,
363 extra_samples: Vec<ExtraSample>,
364 },
365 CieLab {
366 extra_samples: Vec<ExtraSample>,
367 },
368}
369
370#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
372pub enum PlanarConfiguration {
373 Chunky,
374 Planar,
375}
376
377impl PlanarConfiguration {
378 pub fn from_code(code: u16) -> Option<Self> {
379 match code {
380 1 => Some(Self::Chunky),
381 2 => Some(Self::Planar),
382 _ => None,
383 }
384 }
385
386 pub fn to_code(self) -> u16 {
387 match self {
388 Self::Chunky => 1,
389 Self::Planar => 2,
390 }
391 }
392}
393
394#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
400pub enum LercAdditionalCompression {
401 None,
402 Deflate,
403 Zstd,
404}
405
406impl LercAdditionalCompression {
407 pub fn from_code(code: u32) -> Option<Self> {
408 match code {
409 0 => Some(Self::None),
410 1 => Some(Self::Deflate),
411 2 => Some(Self::Zstd),
412 _ => None,
413 }
414 }
415
416 pub fn to_code(self) -> u32 {
417 match self {
418 Self::None => 0,
419 Self::Deflate => 1,
420 Self::Zstd => 2,
421 }
422 }
423}
424
425#[cfg(test)]
426mod tests {
427 use super::*;
428
429 #[test]
430 fn compression_roundtrips_lerc() {
431 assert_eq!(Compression::from_code(34887), Some(Compression::Lerc));
432 assert_eq!(Compression::Lerc.to_code(), 34887);
433 assert_eq!(Compression::Lerc.name(), "LERC");
434 }
435
436 #[test]
437 fn lerc_parameters_tag_matches_registered_value() {
438 assert_eq!(TAG_LERC_PARAMETERS, 50674);
439 }
440
441 #[test]
442 fn lerc_additional_compression_roundtrips() {
443 for (code, expected) in [
444 (0, LercAdditionalCompression::None),
445 (1, LercAdditionalCompression::Deflate),
446 (2, LercAdditionalCompression::Zstd),
447 ] {
448 assert_eq!(LercAdditionalCompression::from_code(code), Some(expected));
449 assert_eq!(expected.to_code(), code);
450 }
451 assert_eq!(LercAdditionalCompression::from_code(99), None);
452 }
453
454 #[test]
455 fn photometric_roundtrips_extended_color_models() {
456 for (code, expected) in [
457 (5, PhotometricInterpretation::Separated),
458 (6, PhotometricInterpretation::YCbCr),
459 (8, PhotometricInterpretation::CieLab),
460 ] {
461 assert_eq!(PhotometricInterpretation::from_code(code), Some(expected));
462 assert_eq!(expected.to_code(), code);
463 }
464 }
465
466 #[test]
467 fn color_map_splits_tag_values_into_rgb_planes() {
468 let values = vec![1u16, 2, 10, 20, 100, 200];
469 let color_map = ColorMap::from_tag_values(&values).unwrap();
470 assert_eq!(color_map.red(), &[1, 2]);
471 assert_eq!(color_map.green(), &[10, 20]);
472 assert_eq!(color_map.blue(), &[100, 200]);
473 assert_eq!(color_map.encode_tag_values(), values);
474 }
475
476 #[test]
477 fn extra_sample_and_ink_set_roundtrip() {
478 assert_eq!(ExtraSample::from_code(1), ExtraSample::AssociatedAlpha);
479 assert_eq!(ExtraSample::UnassociatedAlpha.to_code(), 2);
480 assert_eq!(InkSet::from_code(1), InkSet::Cmyk);
481 assert_eq!(InkSet::NotCmyk.to_code(), 2);
482 }
483}