From 1732481b014dadc28b89cb7f6c6fea41e0c51ff5 Mon Sep 17 00:00:00 2001 From: "A. Molzer" <5550310+197g@users.noreply.github.com> Date: Tue, 30 Dec 2025 06:18:08 +0100 Subject: [PATCH] Add ICC Lab interpretation The CIE Lab interpretation uses heterogeneous samples, and the ITU Lab encoding requires the Tag Decode (433, 0x01B1) which has default values depending on bit depth and convering values into floating points, probably. See also Photometric Interpretation: See also Tag Decode: --- src/decoder/image.rs | 68 ++++++++++++++++++++++++++++++++++++++++---- src/lib.rs | 5 ++++ src/tags.rs | 2 ++ 3 files changed, 70 insertions(+), 5 deletions(-) diff --git a/src/decoder/image.rs b/src/decoder/image.rs index 4ff351b5..eef5d0a5 100644 --- a/src/decoder/image.rs +++ b/src/decoder/image.rs @@ -95,6 +95,19 @@ pub(crate) struct ReadoutLayout { pub planar_config: PlanarConfiguration, /// The sample interpretation (interpret with planar_config). + /// + /// FIXME: we should not require this here. The ability to turn out the raw bytes from the + /// sample arrays is very different from turning out interpretable color. Firstly we can always + /// readout `Multiband` but currently only use that ColorType in special circumstances (it must + /// not overlap cases where actually want to use a ColorType). + /// + /// And then we have CIE Lab, which uses a tuple of `(u8, i8, i8)`, that is still filterable + /// but still not represented by any of our `DecoderResult` variants. Other color variants + /// depend on extra tags (YCbCrCoefficients/0x0211) and we don't have a good side channel to + /// tag the output with all that TIFF specific information, so arguably we should process and + /// apply those to the data so it becomes a self-contained representation. + /// + /// This should be computed at a higher level, in `Decoder`, instead. pub color: ColorType, /// The number of bytes from one row to another. pub minimum_row_stride: usize, @@ -435,15 +448,60 @@ impl Image { }), } } - // TODO: this is bad we should not fail at this point - PhotometricInterpretation::RGBPalette - | PhotometricInterpretation::TransparencyMask - | PhotometricInterpretation::CIELab => Err(TiffError::UnsupportedError( + // ``` + // struct IccLab /* Interpretation 9* { + // pub L: u8, // SampleFormat::Uint + // pub a: u8, // SampleFormat::Uint, defined as TiffLab::a + 128 + // pub b: u8, // SampleFormat::Uint, defined as TiffLab::b + 128 + // } + // ``` + PhotometricInterpretation::IccLab => match self.photometric_samples { + 3 if matches!(self.sample_format, SampleFormat::Uint) => { + Ok(ColorType::Lab(self.bits_per_sample)) + } + _ => Err(TiffError::UnsupportedError( + TiffUnsupportedError::InterpretationWithBits( + self.photometric_interpretation, + vec![self.bits_per_sample; self.samples as usize], + ), + )), + }, + // Unsupported due to inherently heterogeneous sample types. This is represented as: + // ``` + // struct TiffLab /* Interpretation 8* { + // pub L: u8, // SampleFormat::Uint + // pub a: i8, // SampleFormat::Int + // pub b: i8, // SampleFormat::Int + // } + // ``` + PhotometricInterpretation::CIELab => Err(TiffError::UnsupportedError( TiffUnsupportedError::InterpretationWithBits( - self.photometric_interpretation, + PhotometricInterpretation::CIELab, vec![self.bits_per_sample; self.samples as usize], ), )), + // Unsupported due to extra unfiltering and conversion steps. We need to find the + // Decode tag (SRATIONAL; 2 * SamplesPerPixel) and apply the following conversion: + // + // L* = Decode[0] + Lsample x (Decode[1] - Decode[0]) / (2^n -1) + // … + // + // So we'll have a larger depth in the output and either worry about reducing fractions + // or turn everything into floats. That's a lot of decisions. + PhotometricInterpretation::ItuLab => Err(TiffError::UnsupportedError( + TiffUnsupportedError::InterpretationWithBits( + PhotometricInterpretation::CIELab, + vec![self.bits_per_sample; self.samples as usize], + ), + )), + PhotometricInterpretation::RGBPalette | PhotometricInterpretation::TransparencyMask => { + Err(TiffError::UnsupportedError( + TiffUnsupportedError::InterpretationWithBits( + self.photometric_interpretation, + vec![self.bits_per_sample; self.samples as usize], + ), + )) + } } } diff --git a/src/lib.rs b/src/lib.rs index 39b9ddbe..e7c2f1c5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -44,6 +44,9 @@ pub enum ColorType { /// Pixel is YCbCr YCbCr(u8), + /// Pixel is CIE L*a*b* (ICC LAb) + Lab(u8), + /// Pixel has multiple bands/channels Multiband { bit_depth: u8, num_samples: u16 }, } @@ -58,6 +61,7 @@ impl ColorType { | ColorType::CMYK(b) | ColorType::CMYKA(b) | ColorType::YCbCr(b) + | ColorType::Lab(b) | ColorType::Multiband { bit_depth: b, .. } => b, } } @@ -72,6 +76,7 @@ impl ColorType { ColorType::CMYK(_) => 4, ColorType::CMYKA(_) => 5, ColorType::YCbCr(_) => 3, + ColorType::Lab(_) => 3, ColorType::Multiband { num_samples, .. } => num_samples, } } diff --git a/src/tags.rs b/src/tags.rs index 7e4aa31b..f456f659 100644 --- a/src/tags.rs +++ b/src/tags.rs @@ -266,6 +266,8 @@ pub enum PhotometricInterpretation(u16) { CMYK = 5, YCbCr = 6, CIELab = 8, + IccLab = 9, + ItuLab = 10, } }