mirror of
https://github.com/storytold/photocraft.git
synced 2026-11-12 05:02:51 +00:00
raw: unpack 10/12/14-bit rows by groups, keep a single strip's buffer
Bit-packed uncompressed raw data was read one sample at a time through a bit accumulator. Rows of 10, 12 and 14-bit samples are now read a whole group (4x10, 2x12, 4x14 bits = 5, 3, 7 bytes) at a time; the row tail still goes through the general reader. A file whose only segment is the whole image row for row no longer copies it into a second full-size buffer: the decoded Vec becomes the plane. The predictor 2 undo works in place instead of on a copy. The summed-segment charge (#4959) and the per-segment limits are unchanged. A test compares the grouped reader with the plain bit reader for 10/12/14 bits, 1 and 3 samples and several widths. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b0622d20ca
commit
e15d68bfbb
1 file changed
+105
-4
+105
-4
@@ -286,7 +286,7 @@ pub(crate) fn read_plane(t: &Tiff, ifd: &Ifd, limits: &Limits, layout: JpegLayou
|
||||
// The TIFF predictor runs after unpacking, per row, per sample plane.
|
||||
match t.tag_uint(ifd, tag::PREDICTOR).unwrap_or(1) {
|
||||
1 => Ok(v),
|
||||
2 => Ok(undelta(&v, samples, s.w * samples, rows, bits)),
|
||||
2 => Ok(undelta(v, samples, s.w * samples, rows, bits)),
|
||||
3 => Err(RawError::unsupported("floating-point predictor (compression 8, predictor 3)")),
|
||||
p => Err(RawError::malformed(format!("unknown predictor {p}"))),
|
||||
}
|
||||
@@ -303,7 +303,21 @@ pub(crate) fn read_plane(t: &Tiff, ifd: &Ifd, limits: &Limits, layout: JpegLayou
|
||||
_ => lossless_jpeg(src, s, rows, samples, layout, limits),
|
||||
}
|
||||
});
|
||||
let mut data = vec![0u16; width * height * samples];
|
||||
// One segment holding the whole image row for row (a single strip): its buffer is the plane, no
|
||||
// second full-size buffer and no copy. The scatter below then has no segment left to place.
|
||||
let whole = matches!(segments.as_slice(), [s] if s.x == 0 && s.y == 0 && s.w == width && s.h >= height);
|
||||
let mut decoded = decoded.into_iter();
|
||||
let taken = if whole {
|
||||
let mut d = decoded.next().ok_or_else(|| RawError::malformed("raw segment missing"))??;
|
||||
if d.len() < width * height * samples {
|
||||
return Err(RawError::malformed("raw segment is smaller than the image"));
|
||||
}
|
||||
d.truncate(width * height * samples);
|
||||
Some(d)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let mut data = taken.unwrap_or_else(|| vec![0u16; width * height * samples]);
|
||||
for (s, d) in segments.iter().zip(decoded) {
|
||||
let d = d?;
|
||||
let rows = s.h.min(height - s.y);
|
||||
@@ -464,9 +478,8 @@ fn lossy_jpeg(src: &[u8], s: &Segment, rows: usize, samples: usize, bits: u32, t
|
||||
|
||||
/// Reverses TIFF predictor 2 (horizontal differencing) on interleaved samples: each sample
|
||||
/// gains its channel's predecessor in the row, wrapping at the sample's container (8 or 16 bits).
|
||||
fn undelta(v: &[u16], plane: usize, row_samples: usize, rows: usize, bits: u32) -> Vec<u16> {
|
||||
fn undelta(mut out: Vec<u16>, plane: usize, row_samples: usize, rows: usize, bits: u32) -> Vec<u16> {
|
||||
let mask: u16 = if bits <= 8 { 0xFF } else { 0xFFFF };
|
||||
let mut out = v.to_vec();
|
||||
for row in out.chunks_mut(row_samples.max(1)).take(rows) {
|
||||
for i in plane..row.len() {
|
||||
if let (Some(&prev), Some(&cur)) = (row.get(i - plane), row.get(i)) {
|
||||
@@ -495,6 +508,18 @@ fn unpack(src: &[u8], w: usize, rows: usize, samples: usize, bits: u32, le: bool
|
||||
if src.len() < row_bytes * rows {
|
||||
return Err(truncated());
|
||||
}
|
||||
// The common packings, a whole group of samples per fixed run of bytes (MSB first).
|
||||
let per_row = w * samples;
|
||||
match bits {
|
||||
12 | 14 | 10 => {
|
||||
let mut out = vec![0u16; n];
|
||||
for (orow, row) in out.chunks_exact_mut(per_row.max(1)).zip(src.chunks_exact(row_bytes.max(1))) {
|
||||
unpack_row_msb(row, orow, bits);
|
||||
}
|
||||
return Ok(out);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
let mut out = Vec::with_capacity(n);
|
||||
for r in 0..rows {
|
||||
let row = &src[r * row_bytes..(r + 1) * row_bytes];
|
||||
@@ -513,10 +538,86 @@ fn unpack(src: &[u8], w: usize, rows: usize, samples: usize, bits: u32, le: bool
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
/// One MSB-first bit-packed row of 10, 12 or 14-bit samples (`row` holds at least
|
||||
/// `out.len() * bits / 8` bytes, rounded up; missing bytes read as 0, as [`unpack`] does).
|
||||
fn unpack_row_msb(row: &[u8], out: &mut [u16], bits: u32) {
|
||||
let mask = (1u64 << bits) - 1;
|
||||
// Samples per group and bytes per group: 4 × 10 = 40, 2 × 12 = 24, 4 × 14 = 56 bits.
|
||||
let (g, gb) = match bits {
|
||||
10 => (4, 5),
|
||||
12 => (2, 3),
|
||||
_ => (4, 7),
|
||||
};
|
||||
let groups = out.len() / g;
|
||||
for (o, b) in out[..groups * g].chunks_exact_mut(g).zip(row.chunks_exact(gb)) {
|
||||
let mut acc = 0u64;
|
||||
for &x in b {
|
||||
acc = (acc << 8) | u64::from(x);
|
||||
}
|
||||
for (k, v) in o.iter_mut().enumerate() {
|
||||
*v = ((acc >> ((g - 1 - k) as u32 * bits)) & mask) as u16;
|
||||
}
|
||||
}
|
||||
// The tail (fewer than a group): the general reader.
|
||||
let mut acc: u32 = 0;
|
||||
let mut have = 0u32;
|
||||
let mut it = row.iter().skip(groups * gb);
|
||||
for v in &mut out[groups * g..] {
|
||||
while have < bits {
|
||||
acc = (acc << 8) | u32::from(*it.next().unwrap_or(&0));
|
||||
have += 8;
|
||||
}
|
||||
have -= bits;
|
||||
*v = ((acc >> have) & ((1 << bits) - 1)) as u16;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// The grouped 10/12/14-bit readers equal the plain bit reader on every row width.
|
||||
#[test]
|
||||
fn grouped_unpack_matches_the_bit_reader() {
|
||||
let plain = |src: &[u8], w: usize, rows: usize, bits: u32| {
|
||||
let row_bytes = (w * bits as usize).div_ceil(8);
|
||||
let mut out = Vec::new();
|
||||
for r in 0..rows {
|
||||
let row = &src[r * row_bytes..(r + 1) * row_bytes];
|
||||
let (mut acc, mut have, mut it) = (0u32, 0u32, row.iter());
|
||||
for _ in 0..w {
|
||||
while have < bits {
|
||||
acc = (acc << 8) | u32::from(*it.next().unwrap_or(&0));
|
||||
have += 8;
|
||||
}
|
||||
have -= bits;
|
||||
out.push(((acc >> have) & ((1 << bits) - 1)) as u16);
|
||||
}
|
||||
}
|
||||
out
|
||||
};
|
||||
let mut s = 7u64;
|
||||
for bits in [10u32, 12, 14] {
|
||||
for samples in [1usize, 3] {
|
||||
// Widths whose packed rows are shorter than 16-bit containers (else `unpack` reads containers).
|
||||
for w in [4usize, 5, 7, 8, 13, 64, 67] {
|
||||
let rows = 3;
|
||||
let src: Vec<u8> = (0..(w * samples * bits as usize).div_ceil(8) * rows)
|
||||
.map(|_| {
|
||||
s = s.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
|
||||
(s >> 40) as u8
|
||||
})
|
||||
.collect();
|
||||
assert_eq!(
|
||||
unpack(&src, w, rows, samples, bits, true).unwrap(),
|
||||
plain(&src, w * samples, rows, bits),
|
||||
"{bits} bits, {samples} samples, {w} wide"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// #4959: a decoded segment is retained until the scatter, so the segments are charged
|
||||
/// together. Each of these is far inside the budget on its own; their sum is not.
|
||||
#[test]
|
||||
|
||||
Reference in new issue
Block a user