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