Merge pull request #381 from lilith/refactor/typed-predictor-slices

refactor: use typed slices for float predictor output
This commit is contained in:
Aurelia Molzer authored and GitHub committed 2026-04-07 13:03:16 +00:00
commit 7380611a7b
2 files changed
+55 -39

No files matched your search

+12 -3
View File
@@ -1227,9 +1227,18 @@ impl Image {
let row = &mut row[..data_row_bytes];
match color_type.bit_depth() {
16 => predict_f16(&mut encoded, row, samples),
32 => predict_f32(&mut encoded, row, samples),
64 => predict_f64(&mut encoded, row, samples),
16 => {
let (out, _) = row.as_chunks_mut::<2>();
predict_f16(&mut encoded, out, samples);
}
32 => {
let (out, _) = row.as_chunks_mut::<4>();
predict_f32(&mut encoded, out, samples);
}
64 => {
let (out, _) = row.as_chunks_mut::<8>();
predict_f64(&mut encoded, out, samples);
}
_ => unreachable!(),
}
if photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
+43 -36
View File
@@ -397,17 +397,17 @@ fn rev_hpredict_nsamp(buf: &mut [u8], bit_depth: u8, samples: u16) {
/// The TIFF float predictor splits each N-byte sample into N separate byte planes within the row,
/// then delta-encodes each plane. Decoding reverses this: prefix-sum to undo the deltas, then
/// transpose the N planes back into packed samples.
fn predict_f32(input: &mut [u8], output: &mut [u8], samples: u16) {
fn predict_f32(input: &mut [u8], output: &mut [[u8; 4]], samples: u16) {
one_byte_predict_generic(input, usize::from(samples));
deinterleave_f32_planes(input, output);
}
fn predict_f16(input: &mut [u8], output: &mut [u8], samples: u16) {
fn predict_f16(input: &mut [u8], output: &mut [[u8; 2]], samples: u16) {
one_byte_predict_generic(input, usize::from(samples));
deinterleave_f16_planes(input, output);
}
fn predict_f64(input: &mut [u8], output: &mut [u8], samples: u16) {
fn predict_f64(input: &mut [u8], output: &mut [[u8; 8]], samples: u16) {
one_byte_predict_generic(input, usize::from(samples));
deinterleave_f64_planes(input, output);
}
@@ -439,8 +439,8 @@ fn one_byte_predict_generic(buf: &mut [u8], stride: usize) {
/// Each function splits the input into equal-sized plane slices, then gathers one byte per plane
/// per sample. Using `from_be_bytes` for endian conversion lets LLVM recognise the byte-swap idiom
/// and emit efficient code (bswap or movbe).
fn deinterleave_f32_planes(input: &[u8], output: &mut [u8]) {
let n = output.len() / 4;
fn deinterleave_f32_planes(input: &[u8], output: &mut [[u8; 4]]) {
let n = output.len();
if n == 0 || input.len() < n * 4 {
return;
}
@@ -448,13 +448,12 @@ fn deinterleave_f32_planes(input: &[u8], output: &mut [u8]) {
let (p1, rest) = rest.split_at(n);
let (p2, p3) = rest.split_at(n);
// Batch 4 samples (16 output bytes) per iteration. Fixed-size array slicing eliminates
// bounds checks in the inner loop.
let (out_chunks, out_rest) = output.as_chunks_mut::<16>();
let (p0_chunks, p0_rest) = p0.as_chunks::<4>();
let (p1_chunks, p1_rest) = p1.as_chunks::<4>();
let (p2_chunks, p2_rest) = p2.as_chunks::<4>();
let (p3_chunks, p3_rest) = p3.as_chunks::<4>();
// Batch 4 samples per iteration. Fixed-size array slicing eliminates bounds checks.
let (out_chunks, out_rest) = output.as_chunks_mut::<4>();
let (p0_chunks, _) = p0.as_chunks::<4>();
let (p1_chunks, _) = p1.as_chunks::<4>();
let (p2_chunks, _) = p2.as_chunks::<4>();
let (p3_chunks, _) = p3.as_chunks::<4>();
for ((((out, b0), b1), b2), b3) in out_chunks
.iter_mut()
@@ -467,34 +466,34 @@ fn deinterleave_f32_planes(input: &[u8], output: &mut [u8]) {
let s1 = u32::from_be_bytes([b0[1], b1[1], b2[1], b3[1]]).to_ne_bytes();
let s2 = u32::from_be_bytes([b0[2], b1[2], b2[2], b3[2]]).to_ne_bytes();
let s3 = u32::from_be_bytes([b0[3], b1[3], b2[3], b3[3]]).to_ne_bytes();
*out = [
s0[0], s0[1], s0[2], s0[3], s1[0], s1[1], s1[2], s1[3], s2[0], s2[1], s2[2], s2[3],
s3[0], s3[1], s3[2], s3[3],
];
out[0] = s0;
out[1] = s1;
out[2] = s2;
out[3] = s3;
}
// Remaining 0–3 samples.
for (j, out) in out_rest.chunks_exact_mut(4).enumerate() {
out.copy_from_slice(
&u32::from_be_bytes([p0_rest[j], p1_rest[j], p2_rest[j], p3_rest[j]]).to_ne_bytes(),
);
let base = out_chunks.len() * 4;
for (j, out) in out_rest.iter_mut().enumerate() {
*out = u32::from_be_bytes([p0[base + j], p1[base + j], p2[base + j], p3[base + j]])
.to_ne_bytes();
}
}
fn deinterleave_f16_planes(input: &[u8], output: &mut [u8]) {
let n = output.len() / 2;
fn deinterleave_f16_planes(input: &[u8], output: &mut [[u8; 2]]) {
let n = output.len();
if n == 0 || input.len() < n * 2 {
return;
}
let (p0, p1) = input.split_at(n);
for (out, (b0, b1)) in output.chunks_exact_mut(2).zip(p0.iter().zip(p1)) {
out.copy_from_slice(&u16::from_be_bytes([*b0, *b1]).to_ne_bytes());
for (out, (b0, b1)) in output.iter_mut().zip(p0.iter().zip(p1)) {
*out = u16::from_be_bytes([*b0, *b1]).to_ne_bytes();
}
}
fn deinterleave_f64_planes(input: &[u8], output: &mut [u8]) {
let n = output.len() / 8;
fn deinterleave_f64_planes(input: &[u8], output: &mut [[u8; 8]]) {
let n = output.len();
if n == 0 || input.len() < n * 8 {
return;
}
@@ -506,8 +505,7 @@ fn deinterleave_f64_planes(input: &[u8], output: &mut [u8]) {
let (p5, rest) = rest.split_at(n);
let (p6, p7) = rest.split_at(n);
// Zip all 8 planes with the output to avoid indexed access and bounds checks.
let iter = output.chunks_exact_mut(8).zip(
for (out, (((((((b0, b1), b2), b3), b4), b5), b6), b7)) in output.iter_mut().zip(
p0.iter()
.zip(p1)
.zip(p2)
@@ -516,11 +514,8 @@ fn deinterleave_f64_planes(input: &[u8], output: &mut [u8]) {
.zip(p5)
.zip(p6)
.zip(p7),
);
for (out, (((((((b0, b1), b2), b3), b4), b5), b6), b7)) in iter {
out.copy_from_slice(
&u64::from_be_bytes([*b0, *b1, *b2, *b3, *b4, *b5, *b6, *b7]).to_ne_bytes(),
);
) {
*out = u64::from_be_bytes([*b0, *b1, *b2, *b3, *b4, *b5, *b6, *b7]).to_ne_bytes();
}
}
@@ -550,9 +545,21 @@ fn fix_endianness_and_predict(
scratch.resize(buf.len(), 0);
scratch.copy_from_slice(buf);
match bit_depth {
16 => predict_f16(scratch, buf, samples),
32 => predict_f32(scratch, buf, samples),
64 => predict_f64(scratch, buf, samples),
16 => {
let (output, rest) = buf.as_chunks_mut::<2>();
debug_assert!(rest.is_empty(), "f16 row buffer not a multiple of 2 bytes");
predict_f16(scratch, output, samples);
}
32 => {
let (output, rest) = buf.as_chunks_mut::<4>();
debug_assert!(rest.is_empty(), "f32 row buffer not a multiple of 4 bytes");
predict_f32(scratch, output, samples);
}
64 => {
let (output, rest) = buf.as_chunks_mut::<8>();
debug_assert!(rest.is_empty(), "f64 row buffer not a multiple of 8 bytes");
predict_f64(scratch, output, samples);
}
_ => unreachable!("Caller should have validated arguments. Please file a bug."),
}
}