Affinity reader: convert raster tiles with tables instead of a per-pixel Vec<f64>

Each pixel of a decoded layer was widened to a freshly allocated Vec<f64>,
divided, clamped and rounded through libm. The channels are now interleaved
directly: 8-bit RGBA and grey are plain copies (x / 255 * 255 rounds back to x),
16-bit goes through a 64 Ki table and CMYK through a table over (c, k), both
built from the same f64 expressions, so the bytes are identical. Tile copies
into a plane use one slice copy or fill per row instead of a closure per byte.

The scratch charged to the pixel budget is unchanged. Tests keep the old
conversion and compare it exhaustively (every byte, every 16-bit value, every
CMYK colour/black pair) and the old byte copy against the row copy for many crops.

Part of #9412

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
vangel76andClaude Sonnet 5.5 committed 2026-10-25 11:45:21 +01:00
1 parent b0622d20ca
commit ace68fbb14
2 files changed
+190 -29

No files matched your search

+78 -29
View File
@@ -127,31 +127,74 @@ fn decode(r: &mut Reader, bitmap: ObjId, crop: (u32, u32, u32, u32), world: Affi
for c in 1..=channels {
planes.push(plane(r, bitmap, c, bps, (x0, y0, w, h), &mut cache)?);
}
let n = (w as usize) * (h as usize);
let mut rgba = vec![0u8; n * 4];
let sample = |p: &Vec<u8>, i: usize| -> f64 {
if bps == 2 {
f64::from(u16::from_le_bytes([p.get(2 * i).copied().unwrap_or(0), p.get(2 * i + 1).copied().unwrap_or(0)])) / 65535.0
} else {
f64::from(p.get(i).copied().unwrap_or(0)) / 255.0
}
};
for (i, px) in rgba.as_chunks_mut::<4>().0.iter_mut().enumerate() {
let v: Vec<f64> = planes.iter().map(|p| sample(p, i)).collect();
let (r2, g, b, a) = if channels == 1 {
(v[0], v[0], v[0], 1.0)
} else if channels == 5 {
let k = 1.0 - v[3];
((1.0 - v[0]) * k, (1.0 - v[1]) * k, (1.0 - v[2]) * k, v[4])
} else {
(v[0], v[1], v[2], v[3])
};
px.copy_from_slice(&[r2, g, b, a].map(|c| (c.clamp(0.0, 1.0) * 255.0).round() as u8));
}
let rgba = interleave(&planes, channels, bps, pixels);
let transform = Affine([1.0, 0.0, 0.0, 1.0, f64::from(x0), f64::from(y0)]).then(world);
Ok(Some(Image { width: w, height: h, pixels: Pixels::Rgba8(rgba), transform }))
}
/// Quantize a unit-range sample to a byte: the one conversion every format goes through.
fn quant(v: f64) -> u8 {
(v.clamp(0.0, 1.0) * 255.0).round() as u8
}
/// Interleave the decoded planes (`n` samples of `bps` bytes each) into RGBA8.
///
/// The samples used to be widened to `f64` one pixel at a time. The tables below hold the same
/// expressions evaluated once per input value, so the bytes are identical: an 8-bit sample
/// `x / 255 * 255` rounds back to `x`, a 16-bit one goes through a 64 Ki table, and CMYK's
/// `(1 - c)(1 - k)` through a table over the 256 x 256 `(c, k)` pairs.
pub(crate) fn interleave(planes: &[Vec<u8>], channels: usize, bps: usize, n: usize) -> Vec<u8> {
let mut rgba = vec![0u8; n * 4];
let out = rgba.as_chunks_mut::<4>().0.iter_mut();
let p = |c: usize| planes.get(c).map_or(&[][..], Vec::as_slice);
match (channels, bps) {
(1, 1) => {
for (o, &v) in out.zip(p(0)) {
*o = [v, v, v, 255];
}
}
(4, 1) => {
let (r, g, b, a) = (p(0), p(1), p(2), p(3));
for ((((o, &r), &g), &b), &a) in out.zip(r).zip(g).zip(b).zip(a) {
*o = [r, g, b, a];
}
}
(5, 1) => {
let lut = cmyk_table();
let (c, m, y, k, a) = (p(0), p(1), p(2), p(3), p(4));
for (((((o, &c), &m), &y), &k), &a) in out.zip(c).zip(m).zip(y).zip(k).zip(a) {
let k = usize::from(k) << 8;
*o = [lut[k | usize::from(c)], lut[k | usize::from(m)], lut[k | usize::from(y)], a];
}
}
(_, _) => {
let lut: Vec<u8> = (0..=u16::MAX).map(|v| quant(f64::from(v) / 65535.0)).collect();
let s16 = |c: usize| p(c).as_chunks::<2>().0.iter().map(|b| lut[usize::from(u16::from_le_bytes(*b))]);
if channels == 1 {
for (o, g) in out.zip(s16(0)) {
*o = [g, g, g, 255];
}
} else {
for ((((o, r), g), b), a) in out.zip(s16(0)).zip(s16(1)).zip(s16(2)).zip(s16(3)) {
*o = [r, g, b, a];
}
}
}
}
rgba
}
/// `(1 - c)(1 - k)` quantized, indexed by `k << 8 | c`.
fn cmyk_table() -> Vec<u8> {
let mut lut = Vec::with_capacity(256 * 256);
for k in 0..=255u8 {
for c in 0..=255u8 {
lut.push(quant((1.0 - f64::from(c) / 255.0) * (1.0 - f64::from(k) / 255.0)));
}
}
lut
}
/// The original image file: entry `c/N` is a stream whose root (`Blck`) holds it as `Data`.
fn original(r: &mut Reader, name: &str) -> Result<Vec<u8>, Error> {
let bytes = r.archive.read(name)?;
@@ -224,7 +267,7 @@ fn plane(
cache.insert(name.clone(), data);
}
let tile = cache.get(&name).ok_or(Error::Malformed("pixel tile"))?;
copy(&mut out, row, (bx0, py0, bx1, py1), (ox, oy), |x, y| tile.get(y * TILE + x).copied().unwrap_or(0));
copy(&mut out, row, (bx0, py0, bx1, py1), (ox, oy), Some(tile), 0);
continue;
}
3 => {
@@ -238,19 +281,25 @@ fn plane(
_ => return Err(Error::Malformed("unknown pixel tile state")),
};
if let Some(v) = fill {
copy(&mut out, row, (bx0, py0, bx1, py1), (tx * TILE, ty * TILE), |_, _| v);
copy(&mut out, row, (bx0, py0, bx1, py1), (tx * TILE, ty * TILE), None, v);
}
}
Ok(out)
}
/// Copy the overlap of a tile at byte column `ox`, row `oy` into the cropped plane.
fn copy(out: &mut [u8], row: usize, (bx0, py0, bx1, py1): (usize, usize, usize, usize), (ox, oy): (usize, usize), at: impl Fn(usize, usize) -> u8) {
/// Copy the overlap of a tile at byte column `ox`, row `oy` into the cropped plane. `tile` is
/// the tile's bytes, or `None` to fill the overlap with `fill`.
pub(crate) fn copy(out: &mut [u8], row: usize, (bx0, py0, bx1, py1): (usize, usize, usize, usize), (ox, oy): (usize, usize), tile: Option<&[u8]>, fill: u8) {
let (xa, xb) = (ox.max(bx0), (ox + TILE).min(bx1));
if xa >= xb {
return;
}
for y in oy.max(py0)..(oy + TILE).min(py1) {
for x in ox.max(bx0)..(ox + TILE).min(bx1) {
if let Some(b) = out.get_mut((y - py0) * row + (x - bx0)) {
*b = at(x - ox, y - oy);
}
let start = (y - py0) * row + (xa - bx0);
let Some(dst) = out.get_mut(start..start + (xb - xa)) else { continue };
match tile.and_then(|t| t.get((y - oy) * TILE + (xa - ox)..(y - oy) * TILE + (xb - ox))) {
Some(src) => dst.copy_from_slice(src),
None => dst.fill(if tile.is_some() { 0 } else { fill }),
}
}
}
+112
View File
@@ -734,3 +734,115 @@ mod spot_colours {
assert!(warned(&doc, "spot colours nested deeper than 4 levels"), "{:?}", doc.warnings);
}
}
/// The raster fast paths against the code they replaced.
mod raster_identity {
use crate::raster::{copy, interleave};
/// Deterministic filler (xorshift).
fn bytes(n: usize, mut x: u64) -> Vec<u8> {
(0..n)
.map(|_| {
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
(x >> 24) as u8
})
.collect()
}
/// The per-pixel `f64` conversion `decode` used before the tables.
fn reference(planes: &[Vec<u8>], channels: usize, bps: usize, n: usize) -> Vec<u8> {
let sample = |p: &Vec<u8>, i: usize| -> f64 {
if bps == 2 {
f64::from(u16::from_le_bytes([p.get(2 * i).copied().unwrap_or(0), p.get(2 * i + 1).copied().unwrap_or(0)])) / 65535.0
} else {
f64::from(p.get(i).copied().unwrap_or(0)) / 255.0
}
};
let mut rgba = vec![0u8; n * 4];
for (i, px) in rgba.as_chunks_mut::<4>().0.iter_mut().enumerate() {
let v: Vec<f64> = planes.iter().map(|p| sample(p, i)).collect();
let (r, g, b, a) = if channels == 1 {
(v[0], v[0], v[0], 1.0)
} else if channels == 5 {
let k = 1.0 - v[3];
((1.0 - v[0]) * k, (1.0 - v[1]) * k, (1.0 - v[2]) * k, v[4])
} else {
(v[0], v[1], v[2], v[3])
};
px.copy_from_slice(&[r, g, b, a].map(|c| (c.clamp(0.0, 1.0) * 255.0).round() as u8));
}
rgba
}
#[test]
fn eight_bit_samples_round_trip() {
// Every byte through every position of the 4-channel and the grey path.
let ramp: Vec<u8> = (0..=255).collect();
let planes = vec![ramp.clone(), ramp.iter().rev().copied().collect(), ramp.clone(), ramp.iter().rev().copied().collect()];
assert_eq!(interleave(&planes, 4, 1, 256), reference(&planes, 4, 1, 256));
assert_eq!(interleave(&planes[..1], 1, 1, 256), reference(&planes[..1], 1, 1, 256));
}
#[test]
fn sixteen_bit_samples_are_exhaustive() {
// All 65536 values in each channel position (rotated so the channels differ).
let all: Vec<u8> = (0..=u16::MAX).flat_map(u16::to_le_bytes).collect();
let rot = |k: usize| -> Vec<u8> { (0..=u16::MAX).flat_map(|v| v.wrapping_add((k * 4099) as u16).to_le_bytes()).collect() };
let planes = vec![all, rot(1), rot(2), rot(3)];
assert_eq!(interleave(&planes, 4, 2, 65536), reference(&planes, 4, 2, 65536));
assert_eq!(interleave(&planes[..1], 1, 2, 65536), reference(&planes[..1], 1, 2, 65536));
}
#[test]
fn cmyk_is_exhaustive_over_colour_and_black() {
// Every (c, k) pair in each of the c, m and y positions; alpha is arbitrary.
let n = 256 * 256;
let k: Vec<u8> = (0..n).map(|i| (i >> 8) as u8).collect();
let c: Vec<u8> = (0..n).map(|i| i as u8).collect();
let m: Vec<u8> = (0..n).map(|i| (i as u8).wrapping_mul(7).wrapping_add(3)).collect();
let y: Vec<u8> = (0..n).map(|i| (i as u8).wrapping_mul(13).wrapping_add(250)).collect();
let planes = vec![c, m, y, k, bytes(n, 9)];
assert_eq!(interleave(&planes, 5, 1, n), reference(&planes, 5, 1, n));
}
#[test]
fn random_planes_match_for_every_format() {
for (channels, bps) in [(1, 1), (1, 2), (4, 1), (4, 2), (5, 1)] {
let n = 5000;
let planes: Vec<Vec<u8>> = (0..channels).map(|c| bytes(n * bps, 1 + c as u64)).collect();
assert_eq!(interleave(&planes, channels, bps, n), reference(&planes, channels, bps, n), "{channels}x{bps}");
}
}
/// The per-byte closure copy `plane` used before the row copies.
fn copy_reference(out: &mut [u8], row: usize, (bx0, py0, bx1, py1): (usize, usize, usize, usize), (ox, oy): (usize, usize), at: impl Fn(usize, usize) -> u8) {
for y in oy.max(py0)..(oy + 256).min(py1) {
for x in ox.max(bx0)..(ox + 256).min(bx1) {
if let Some(b) = out.get_mut((y - py0) * row + (x - bx0)) {
*b = at(x - ox, y - oy);
}
}
}
}
#[test]
fn tile_copy_matches_the_byte_loop_for_every_crop() {
let tile = bytes(256 * 256, 77);
// Crops that cut the tile on each side, sit inside it, and miss it.
for &(bx0, bx1, py0, py1) in &[(0, 600, 0, 600), (10, 300, 20, 200), (100, 150, 100, 130), (256, 512, 0, 256), (0, 256, 300, 400), (255, 257, 255, 257), (300, 301, 0, 1)] {
for &(ox, oy) in &[(0, 0), (256, 0), (0, 256), (256, 256), (512, 256)] {
let row = bx1 - bx0;
let mut old = vec![0xAAu8; row * (py1 - py0)];
let mut new = old.clone();
copy_reference(&mut old, row, (bx0, py0, bx1, py1), (ox, oy), |x, y| tile[y * 256 + x]);
copy(&mut new, row, (bx0, py0, bx1, py1), (ox, oy), Some(&tile), 0);
assert_eq!(old, new, "tile {ox},{oy} crop {bx0}..{bx1} x {py0}..{py1}");
copy_reference(&mut old, row, (bx0, py0, bx1, py1), (ox, oy), |_, _| 0xFF);
copy(&mut new, row, (bx0, py0, bx1, py1), (ox, oy), None, 0xFF);
assert_eq!(old, new, "fill {ox},{oy} crop {bx0}..{bx1} x {py0}..{py1}");
}
}
}
}