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