fix: handle WhiteIsZero for non-standard sub-byte depths (3/5/6/7-bit)
For sub-byte depths where samples cross byte boundaries (3, 5, 6, 7-bit), byte-wise NOT corrupts samples. Added per-sample inversion that extracts each N-bit sample from the packed stream, computes (max - value), and writes it back. Tests cover: - 3/5/7-bit BlackIsZero (verify basic decoding) - 3-bit and 6-bit WhiteIsZero (verify per-sample inversion)
This commit is contained in:
1 parent
29e43d2314
commit
fb1afc81e9
7 files changed
+74
-1
No files matched your search
@@ -740,6 +740,41 @@ fn invert_colors(
|
|||||||
*x = !*x;
|
*x = !*x;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
(ColorType::Gray(n @ (3 | 5 | 6 | 7)), SampleFormat::Uint) => {
|
||||||
|
// Non-standard sub-byte depths where samples may cross byte boundaries.
|
||||||
|
// Byte-wise NOT would corrupt samples spanning two bytes, so we invert
|
||||||
|
// each N-bit sample individually within the packed stream.
|
||||||
|
let max = (1u8 << n) - 1;
|
||||||
|
let total_bits = buf.len() * 8;
|
||||||
|
let mut bit_offset = 0;
|
||||||
|
while bit_offset + n as usize <= total_bits {
|
||||||
|
let byte_idx = bit_offset / 8;
|
||||||
|
let bit_in_byte = bit_offset % 8;
|
||||||
|
|
||||||
|
if bit_in_byte + n as usize <= 8 {
|
||||||
|
// Sample fits within one byte
|
||||||
|
let shift = 8 - bit_in_byte - n as usize;
|
||||||
|
let val = (buf[byte_idx] >> shift) & max;
|
||||||
|
let mask = max << shift;
|
||||||
|
buf[byte_idx] = (buf[byte_idx] & !mask) | ((max - val) << shift);
|
||||||
|
} else {
|
||||||
|
// Sample spans two bytes
|
||||||
|
let combined = ((buf[byte_idx] as u16) << 8)
|
||||||
|
| buf.get(byte_idx + 1).copied().unwrap_or(0) as u16;
|
||||||
|
let shift = 16 - bit_in_byte - n as usize;
|
||||||
|
let max16 = max as u16;
|
||||||
|
let val = ((combined >> shift) & max16) as u8;
|
||||||
|
let new_combined =
|
||||||
|
(combined & !(max16 << shift)) | ((max - val) as u16) << shift;
|
||||||
|
buf[byte_idx] = (new_combined >> 8) as u8;
|
||||||
|
if byte_idx + 1 < buf.len() {
|
||||||
|
buf[byte_idx + 1] = new_combined as u8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bit_offset += n as usize;
|
||||||
|
}
|
||||||
|
}
|
||||||
(ColorType::Gray(n @ 9..=15), SampleFormat::Uint) => {
|
(ColorType::Gray(n @ 9..=15), SampleFormat::Uint) => {
|
||||||
// Extended depths promoted to u16: invert within the valid bit range.
|
// Extended depths promoted to u16: invert within the valid bit range.
|
||||||
// Byte-wise NOT would flip the upper padding bits, so we use (max - v).
|
// Byte-wise NOT would flip the upper padding bits, so we use (max - v).
|
||||||
|
|||||||
+39
-1
@@ -946,6 +946,8 @@ fn test_rgb_planar_u24() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- WhiteIsZero tests across bit depths ----
|
||||||
|
|
||||||
// WhiteIsZero + extended bit depth (12-bit gray, synthetic 4x2 image)
|
// WhiteIsZero + extended bit depth (12-bit gray, synthetic 4x2 image)
|
||||||
#[test]
|
#[test]
|
||||||
fn test_gray_u12_white_is_zero() {
|
fn test_gray_u12_white_is_zero() {
|
||||||
@@ -954,7 +956,43 @@ fn test_gray_u12_white_is_zero() {
|
|||||||
test_image_sum_u16("miniswhite-1c-12b.tiff", ColorType::Gray(12), 18565);
|
test_image_sum_u16("miniswhite-1c-12b.tiff", ColorType::Gray(12), 18565);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Horizontal predictor + extended bit depth (12-bit gray, synthetic 4x2 image)
|
// WhiteIsZero + non-standard sub-byte depths (3-bit and 6-bit, synthetic 8x2 images)
|
||||||
|
// These verify per-sample inversion for packed depths where samples cross byte boundaries.
|
||||||
|
#[test]
|
||||||
|
fn test_gray_u3_white_is_zero() {
|
||||||
|
test_image_sum_u8("miniswhite-1c-3b.tiff", ColorType::Gray(3), 765);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_gray_u6_white_is_zero() {
|
||||||
|
// Values: [0, 10, 20, 30, 40, 50, 60, 63] × 2 rows
|
||||||
|
// After inversion: [63, 53, 43, 33, 23, 13, 3, 0] × 2 rows
|
||||||
|
test_image_sum_u8("miniswhite-1c-6b.tiff", ColorType::Gray(6), 2124);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Non-standard sub-byte depths (BlackIsZero) ----
|
||||||
|
// Verify that odd bit depths (3, 5, 7) decode without error.
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_gray_u3() {
|
||||||
|
// 8x2 image, values [0..7, 7..0], packed to 6 bytes
|
||||||
|
test_image_sum_u8("minisblack-1c-3b.tiff", ColorType::Gray(3), 765);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_gray_u5() {
|
||||||
|
// 8x2 image, packed to 10 bytes
|
||||||
|
test_image_sum_u8("minisblack-1c-5b.tiff", ColorType::Gray(5), 1275);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_gray_u7() {
|
||||||
|
// 8x2 image, packed to 14 bytes
|
||||||
|
test_image_sum_u8("minisblack-1c-7b.tiff", ColorType::Gray(7), 1711);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Horizontal predictor + extended bit depth ----
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_gray_u12_hpredict() {
|
fn test_gray_u12_hpredict() {
|
||||||
// Delta-encoded 12-bit values: [100, 200, 300, 400, 1000, 2000, 3000, 4000]
|
// Delta-encoded 12-bit values: [100, 200, 300, 400, 1000, 2000, 3000, 4000]
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in new issue
Block a user