chore: address review feedback on CIELab/TransparencyMask PR

This commit is contained in:
aarshivv committed 2026-07-23 00:39:19 +05:30
1 parent 9924ea2721
commit 8f83f0fa82
3 files changed
+83 -58

No files matched your search

+38 -39
View File
@@ -592,16 +592,15 @@ 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* uses unsigned with +128 bias. For 8-bit we
// re-bias a/b during expand_chunk so callers see the same encoding as ICCLab.
// (-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.
//
// libtiff takes the same approach — accepts only 8-bit CIELab and converts
// without checking SampleFormat:
// https://libtiff.gitlab.io/libtiff/functions/TIFFcolor.html
// https://gitlab.com/libtiff/libtiff/-/blob/master/libtiff/tif_getimage.c
//
// 16-bit CIELab would need L=uint16 + a/b=int16, which SampleFormat can't
// express (it applies uniformly). libtiff doesn't support it either.
// 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.
PhotometricInterpretation::CIELab => match self.photometric_samples {
3 if matches!(self.bits_per_sample, 8) => Ok(ColorType::Lab(self.bits_per_sample)),
_ => Err(TiffError::UnsupportedError(
@@ -1202,12 +1201,12 @@ impl Image {
scratch,
);
if photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
super::invert_colors(out_row, color_type, self.sample_format)?;
}
if photometric_interpretation == PhotometricInterpretation::CIELab {
super::cielab_to_icclab(out_row);
}
super::post_process_row(
out_row,
photometric_interpretation,
color_type,
self.sample_format,
)?;
}
} else if is_output_chunk_rows && is_all_bits {
// Here we can read directly into the output buffer itself.
@@ -1225,12 +1224,12 @@ impl Image {
);
}
if photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
super::invert_colors(tile, color_type, self.sample_format)?;
}
if photometric_interpretation == PhotometricInterpretation::CIELab {
super::cielab_to_icclab(tile);
}
super::post_process_row(
tile,
photometric_interpretation,
color_type,
self.sample_format,
)?;
} else if chunk_row_bytes > data_row_bytes && self.predictor == Predictor::FloatingPoint {
// The floating point predictor shuffles the padding bytes into the encoded output, so
// this case is handled specially when needed.
@@ -1254,12 +1253,12 @@ impl Image {
}
_ => unreachable!(),
}
if photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
super::invert_colors(row, color_type, self.sample_format)?;
}
if photometric_interpretation == PhotometricInterpretation::CIELab {
super::cielab_to_icclab(row);
}
super::post_process_row(
row,
photometric_interpretation,
color_type,
self.sample_format,
)?;
}
} else if is_all_bits {
// We read row-by-row but each row fits in its output buffer.
@@ -1286,12 +1285,12 @@ impl Image {
scratch,
);
if photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
super::invert_colors(row, color_type, self.sample_format)?;
}
if photometric_interpretation == PhotometricInterpretation::CIELab {
super::cielab_to_icclab(row);
}
super::post_process_row(
row,
photometric_interpretation,
color_type,
self.sample_format,
)?;
}
} else {
// The encoded data potentially takes up more space than the output data so we must be
@@ -1331,12 +1330,12 @@ impl Image {
scratch,
);
if photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
super::invert_colors(row, color_type, self.sample_format)?;
}
if photometric_interpretation == PhotometricInterpretation::CIELab {
super::cielab_to_icclab(row);
}
super::post_process_row(
row,
photometric_interpretation,
color_type,
self.sample_format,
)?;
}
}
+25 -5
View File
@@ -3,8 +3,8 @@ use std::io::{self, Read, Seek};
use std::num::NonZeroUsize;
use crate::tags::{
ByteOrder, IfdPointer, PlanarConfiguration, Predictor, SampleFormat, Tag, TiffVariant, Type,
ValueBuffer,
ByteOrder, IfdPointer, PhotometricInterpretation, PlanarConfiguration, Predictor, SampleFormat,
Tag, TiffVariant, Type, ValueBuffer,
};
use crate::{
ColorType, Directory, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError,
@@ -667,12 +667,32 @@ fn invert_colors(
/// See libtiff's TIFFCIELabToXYZ for reference:
/// https://gitlab.com/libtiff/libtiff/-/blob/master/libtiff/tif_color.c
fn cielab_to_icclab(buf: &mut [u8]) {
for pixel in buf.chunks_exact_mut(3) {
pixel[1] = pixel[1].wrapping_add(128);
pixel[2] = pixel[2].wrapping_add(128);
for [_, a, b] in buf.as_chunks_mut::<3>().0 {
*a = a.wrapping_add(128);
*b = b.wrapping_add(128);
}
}
/// Apply photometric-interpretation-specific post-processing to a decoded row.
///
/// Some interpretations need a fixup once the raw samples are in the output
/// buffer: `WhiteIsZero` inverts intensities, and `CIELab` re-biases the signed
/// a/b channels into the unsigned ICCLab encoding. These are mutually exclusive,
/// so at most one applies to any given row.
fn post_process_row(
buf: &mut [u8],
photometric_interpretation: PhotometricInterpretation,
color_type: ColorType,
sample_format: SampleFormat,
) -> TiffResult<()> {
match photometric_interpretation {
PhotometricInterpretation::WhiteIsZero => invert_colors(buf, color_type, sample_format)?,
PhotometricInterpretation::CIELab => cielab_to_icclab(buf),
_ => {}
}
Ok(())
}
/// Fix endianness. If `byte_order` matches the host, then conversion is a no-op.
fn fix_endianness(buf: &mut [u8], byte_order: ByteOrder, bit_depth: u8) {
let host = ByteOrder::native();