feat: support 16-bit CIELab decoding
This commit is contained in:
1 parent
31919c7bfc
commit
3436a2222b
3 files changed
+218
-38
No files matched your search
+16
-11
@@ -597,18 +597,23 @@ impl Image {
|
||||
),
|
||||
)),
|
||||
},
|
||||
// CIE L*a*b* (TIFF6 Section 23, pp 110-115) encodes a/b as signed int8
|
||||
// (-128..=127) while ICC L*a*b* (photometric=9) uses unsigned samples with
|
||||
// a +128 bias. For 8-bit we re-bias a/b in post-processing (see
|
||||
// `cielab_to_icclab`) so callers see the same encoding as ICCLab.
|
||||
//
|
||||
// Only 8-bit is accepted here. 16-bit CIELab is a valid format that libtiff
|
||||
// does decode (`putcontig8bitCIELab16`, selected by `initCIELabConversion`
|
||||
// in tif_getimage.c line 2216 @ 04884181e8903915038be553bfab404302d98ea0),
|
||||
// but it uses L=uint16 with a/b=int16 — a mixed layout that `SampleFormat`,
|
||||
// which applies uniformly to every sample, cannot express. Left as follow-up.
|
||||
// CIE L*a*b* (TIFF6 Section 23, pp 110-115) encodes a/b as signed integers
|
||||
// (int8 at 8-bit, int16 at 16-bit) with 0 neutral, while ICC L*a*b*
|
||||
// (photometric=9) uses unsigned samples with the neutral at the middle of
|
||||
// the range. `SampleFormat` applies uniformly to all samples, so it can't
|
||||
// describe the L=unsigned + a/b=signed mix; instead we take a/b as signed
|
||||
// from the photometric interpretation (requiring `SampleFormat::Uint`, like
|
||||
// the ICCLab arm) and re-bias them to the unsigned ICCLab encoding in
|
||||
// post-processing (see `cielab_to_icclab`). libtiff does the same, decoding
|
||||
// both depths (`putcontig8bitCIELab8` / `putcontig8bitCIELab16`, selected by
|
||||
// `initCIELabConversion` in tif_getimage.c line 2216
|
||||
// @ 04884181e8903915038be553bfab404302d98ea0).
|
||||
PhotometricInterpretation::CIELab => match self.photometric_samples {
|
||||
3 if matches!(self.bits_per_sample, 8) => Ok(ColorType::Lab(self.bits_per_sample)),
|
||||
3 if matches!(self.bits_per_sample, 8 | 16)
|
||||
&& matches!(self.sample_format, SampleFormat::Uint) =>
|
||||
{
|
||||
Ok(ColorType::Lab(self.bits_per_sample))
|
||||
}
|
||||
_ => Err(TiffError::UnsupportedError(
|
||||
TiffUnsupportedError::InterpretationWithBits(
|
||||
PhotometricInterpretation::CIELab,
|
||||
|
||||
+24
-8
@@ -713,15 +713,31 @@ fn invert_colors(
|
||||
|
||||
/// Re-bias CIE L*a*b* a/b channels from signed to unsigned encoding.
|
||||
///
|
||||
/// CIELab (photometric=8) stores a/b as int8 where 0 is neutral.
|
||||
/// ICCLab (photometric=9) stores them as uint8 where 128 is neutral.
|
||||
/// Adding 128 (wrapping) converts between the two representations.
|
||||
/// CIELab (photometric=8) stores a/b as signed integers where 0 is neutral;
|
||||
/// ICCLab (photometric=9) stores them as unsigned with the neutral shifted to
|
||||
/// the middle of the range. The two differ by exactly half the sample range, so
|
||||
/// the conversion is a wrapping add of 2^(bits-1) — i.e. flipping the sign bit —
|
||||
/// applied per a/b sample. `bit_depth` is 8 or 16 (guaranteed by `colortype`);
|
||||
/// samples are in native byte order here (post `fix_endianness_and_predict`).
|
||||
/// See libtiff's `TIFFCIELabToXYZ` for reference (tif_color.c line 45
|
||||
/// @ 04884181e8903915038be553bfab404302d98ea0).
|
||||
fn cielab_to_icclab(buf: &mut [u8]) {
|
||||
for [_, a, b] in buf.as_chunks_mut::<3>().0 {
|
||||
*a = a.wrapping_add(128);
|
||||
*b = b.wrapping_add(128);
|
||||
fn cielab_to_icclab(buf: &mut [u8], bit_depth: u8) {
|
||||
match bit_depth {
|
||||
16 => {
|
||||
for pixel in buf.as_chunks_mut::<6>().0 {
|
||||
// pixel is [L, a, b] as three native-endian u16 samples.
|
||||
let (samples, _) = pixel.as_chunks_mut::<2>();
|
||||
for s in &mut samples[1..=2] {
|
||||
*s = u16::from_ne_bytes(*s).wrapping_add(0x8000).to_ne_bytes();
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
for [_, a, b] in buf.as_chunks_mut::<3>().0 {
|
||||
*a = a.wrapping_add(128);
|
||||
*b = b.wrapping_add(128);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -739,7 +755,7 @@ fn post_process_row(
|
||||
) -> TiffResult<()> {
|
||||
match photometric_interpretation {
|
||||
PhotometricInterpretation::WhiteIsZero => invert_colors(buf, color_type, sample_format)?,
|
||||
PhotometricInterpretation::CIELab => cielab_to_icclab(buf),
|
||||
PhotometricInterpretation::CIELab => cielab_to_icclab(buf, color_type.bit_depth()),
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
|
||||
Reference in new issue
Block a user