Clippy 1.99: chunks_exact with a constant size -> as_chunks; drop a no-op Option filter

Rust 1.99's clippy adds chunks_exact_to_as_chunks and some_filter, which
broke the Clippy step on every CI runner (it uses the latest stable). All
sites are mechanical: as_chunks drops the remainder exactly like
chunks_exact.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Brandon Thomas 2026-10-04 10:39:06 -04:00
parent 574d4fe7da
commit 1c09936154
44 changed files with 92 additions and 86 deletions

View File

@ -65,7 +65,9 @@ pub(crate) fn oil_paint(src: &Image, out: Rect, ctx: &Ctx, spec: &OilSpec) -> Ve
gauss_blur_n(&mut efg, ww, wh, 3, tensor_sigma(spec.scale)); gauss_blur_n(&mut efg, ww, wh, 3, tensor_sigma(spec.scale));
// 4. Stroke direction = minor eigenvector (along edges). // 4. Stroke direction = minor eigenvector (along edges).
let tangent: Vec<(f32, f32)> = efg let tangent: Vec<(f32, f32)> = efg
.chunks_exact(3) .as_chunks::<3>()
.0
.iter()
.map(|t| { .map(|t| {
let (e, f, g) = (t[0], t[1], t[2]); let (e, f, g) = (t[0], t[1], t[2]);
let l1 = 0.5 * (e + g + ((e - g) * (e - g) + 4.0 * f * f).sqrt()); let l1 = 0.5 * (e + g + ((e - g) * (e - g) + 4.0 * f * f).sqrt());

View File

@ -298,7 +298,7 @@ mod tests {
let (w, h, ch) = (8, 1, 4); let (w, h, ch) = (8, 1, 4);
let data: Vec<f32> = (0..w).flat_map(|x| if x < 4 { [1.0, 0.0, 0.0, 1.0] } else { [0.0, 1.0, 0.0, 0.0] }).collect(); let data: Vec<f32> = (0..w).flat_map(|x| if x < 4 { [1.0, 0.0, 0.0, 1.0] } else { [0.0, 1.0, 0.0, 0.0] }).collect();
let b = local_blur(&data, w, h, ch, Some(3), (2, 0, 6, 1), 1.0); let b = local_blur(&data, w, h, ch, Some(3), (2, 0, 6, 1), 1.0);
for p in b.chunks_exact(4) { for p in b.as_chunks::<4>().0 {
if p[3] > 0.01 { if p[3] > 0.01 {
assert!(p[0] > 0.99 && p[1] < 0.01, "{p:?}"); assert!(p[0] > 0.99 && p[1] < 0.01, "{p:?}");
} }

View File

@ -547,7 +547,7 @@ pub fn polygon(points: &[(f32, f32)], area: Rect, anti_alias: bool) -> Vec<f32>
} }
} }
xs.sort_by(|a, b| a.total_cmp(b)); xs.sort_by(|a, b| a.total_cmp(b));
for span in xs.chunks_exact(2) { for span in xs.as_chunks::<2>().0 {
let (x0, x1) = ((span[0] - area.x0 as f32).max(0.0), (span[1] - area.x0 as f32).min(w as f32)); let (x0, x1) = ((span[0] - area.x0 as f32).max(0.0), (span[1] - area.x0 as f32).min(w as f32));
if x1 <= x0 { if x1 <= x0 {
continue; continue;

View File

@ -127,7 +127,7 @@ fn blur_preserves_flat_colour_and_mass() {
for p in [FilterParams::GaussianBlur { radius: 2.0 }, FilterParams::BoxBlur { radius: 3.0 }] { for p in [FilterParams::GaussianBlur { radius: 2.0 }, FilterParams::BoxBlur { radius: 3.0 }] {
let out = run(&small, &p); let out = run(&small, &p);
let big = Rect::new(-10, -10, 40, 40); let big = Rect::new(-10, -10, 40, 40);
let sum: f32 = out.read_region(big).chunks_exact(4).map(|px| px[3]).sum(); let sum: f32 = out.read_region(big).as_chunks::<4>().0.iter().map(|px| px[3]).sum();
assert!((sum - 100.0).abs() < 0.01, "{}: {sum}", p.label()); assert!((sum - 100.0).abs() < 0.01, "{}: {sum}", p.label());
// No colour bleeding from transparent pixels. // No colour bleeding from transparent pixels.
let edge = out.pixel(9, 15); let edge = out.pixel(9, 15);
@ -176,7 +176,7 @@ fn unsharp_threshold_and_contrast() {
let sharp = run(&s, &FilterParams::UnsharpMask { amount: 200.0, radius: 2.0, threshold: 0.0 }); let sharp = run(&s, &FilterParams::UnsharpMask { amount: 200.0, radius: 2.0, threshold: 0.0 });
let var = |surf: &Surface| { let var = |surf: &Surface| {
let v = surf.read_region(Rect::new(5, 5, 35, 25)); let v = surf.read_region(Rect::new(5, 5, 35, 25));
let reds: Vec<f32> = v.chunks_exact(4).map(|p| p[0]).collect(); let reds: Vec<f32> = v.as_chunks::<4>().0.iter().map(|p| p[0]).collect();
let m = reds.iter().sum::<f32>() / reds.len() as f32; let m = reds.iter().sum::<f32>() / reds.len() as f32;
reds.iter().map(|r| (r - m).powi(2)).sum::<f32>() reds.iter().map(|r| (r - m).powi(2)).sum::<f32>()
}; };
@ -198,7 +198,7 @@ fn noise_is_deterministic_monochrome_and_centred() {
assert_eq!(a, run(&s, &p)); assert_eq!(a, run(&s, &p));
let v = a.read_region(R); let v = a.read_region(R);
let mut mean = 0.0; let mut mean = 0.0;
for px in v.chunks_exact(4) { for px in v.as_chunks::<4>().0 {
assert_eq!(px[0], px[1]); assert_eq!(px[0], px[1]);
assert_eq!(px[1], px[2]); assert_eq!(px[1], px[2]);
assert!((px[0] - 0.5).abs() <= 0.25 + 1e-6); assert!((px[0] - 0.5).abs() <= 0.25 + 1e-6);

View File

@ -303,7 +303,7 @@ fn mezzotint_is_binary_and_tracks_brightness() {
let out = run(&s, &FilterParams::Mezzotint { kind: MezzotintType::FineDots, seed: 1 }); let out = run(&s, &FilterParams::Mezzotint { kind: MezzotintType::FineDots, seed: 1 });
let v = out.read_region(R); let v = out.read_region(R);
let mut mean = [0.0f32; 3]; let mut mean = [0.0f32; 3];
for px in v.chunks_exact(4) { for px in v.as_chunks::<4>().0 {
for k in 0..3 { for k in 0..3 {
assert!(px[k] == 0.0 || px[k] == 1.0); assert!(px[k] == 0.0 || px[k] == 1.0);
mean[k] += px[k] / 1200.0; mean[k] += px[k] / 1200.0;
@ -318,10 +318,10 @@ fn halftone_covers_by_ink() {
let w = run(&flat(SampleType::F32, R, WHITE), &FilterParams::ColorHalftone { max_radius: 4.0, angles }); let w = run(&flat(SampleType::F32, R, WHITE), &FilterParams::ColorHalftone { max_radius: 4.0, angles });
assert!(w.read_region(R).iter().all(|&v| (v - 1.0).abs() < 1e-6), "white paper stays white"); assert!(w.read_region(R).iter().all(|&v| (v - 1.0).abs() < 1e-6), "white paper stays white");
let b = run(&flat(SampleType::F32, R, BLACK), &FilterParams::ColorHalftone { max_radius: 4.0, angles }); let b = run(&flat(SampleType::F32, R, BLACK), &FilterParams::ColorHalftone { max_radius: 4.0, angles });
let mean_b: f32 = b.read_region(R).chunks_exact(4).map(|p| p[0]).sum::<f32>() / 1200.0; let mean_b: f32 = b.read_region(R).as_chunks::<4>().0.iter().map(|p| p[0]).sum::<f32>() / 1200.0;
assert!(mean_b < 0.05, "black is fully inked: {mean_b}"); assert!(mean_b < 0.05, "black is fully inked: {mean_b}");
let g = run(&flat(SampleType::F32, R, [0.5, 0.5, 0.5, 1.0]), &FilterParams::ColorHalftone { max_radius: 4.0, angles }); let g = run(&flat(SampleType::F32, R, [0.5, 0.5, 0.5, 1.0]), &FilterParams::ColorHalftone { max_radius: 4.0, angles });
let mean_g: f32 = g.read_region(R).chunks_exact(4).map(|p| p[1]).sum::<f32>() / 1200.0; let mean_g: f32 = g.read_region(R).as_chunks::<4>().0.iter().map(|p| p[1]).sum::<f32>() / 1200.0;
assert!((mean_g - 0.5).abs() < 0.12, "mid gray ≈ half coverage: {mean_g}"); assert!((mean_g - 0.5).abs() < 0.12, "mid gray ≈ half coverage: {mean_g}");
} }
@ -330,8 +330,8 @@ fn pointillize_shows_canvas() {
let s = flat(SampleType::F32, R, [0.0, 0.0, 1.0, 1.0]); let s = flat(SampleType::F32, R, [0.0, 0.0, 1.0, 1.0]);
let out = run(&s, &FilterParams::Pointillize { cell_size: 10.0, seed: 3, background: [1.0, 0.0, 0.0, 1.0] }); let out = run(&s, &FilterParams::Pointillize { cell_size: 10.0, seed: 3, background: [1.0, 0.0, 0.0, 1.0] });
let v = out.read_region(R); let v = out.read_region(R);
assert!(v.chunks_exact(4).any(|p| p[0] > 0.99 && p[2] < 0.01), "canvas visible between dots"); assert!(v.as_chunks::<4>().0.iter().any(|p| p[0] > 0.99 && p[2] < 0.01), "canvas visible between dots");
assert!(v.chunks_exact(4).any(|p| p[2] > 0.9), "dots take the image colour"); assert!(v.as_chunks::<4>().0.iter().any(|p| p[2] > 0.9), "dots take the image colour");
} }
#[test] #[test]
@ -372,8 +372,8 @@ fn tiles_leave_gaps_with_fill() {
let out = let out =
run(&s, &FilterParams::Tiles { count: 3, max_offset: 40.0, fill: TileFill::Foreground, foreground: [0.0, 1.0, 0.0, 1.0], background: WHITE, seed: 2 }); run(&s, &FilterParams::Tiles { count: 3, max_offset: 40.0, fill: TileFill::Foreground, foreground: [0.0, 1.0, 0.0, 1.0], background: WHITE, seed: 2 });
let v = out.read_region(R); let v = out.read_region(R);
assert!(v.chunks_exact(4).any(|p| p[1] > 0.99), "foreground gaps"); assert!(v.as_chunks::<4>().0.iter().any(|p| p[1] > 0.99), "foreground gaps");
assert!(v.chunks_exact(4).filter(|p| p[2] > 0.99).count() > 600, "mostly tiles"); assert!(v.as_chunks::<4>().0.iter().filter(|p| p[2] > 0.99).count() > 600, "mostly tiles");
} }
#[test] #[test]
@ -409,7 +409,7 @@ fn fibers_lie_between_colours_and_are_seeded() {
let p = FilterParams::Fibers { variance: 16.0, strength: 4.0, seed: 1, foreground: [1.0, 0.0, 0.0, 1.0], background: [0.0, 0.0, 1.0, 1.0] }; let p = FilterParams::Fibers { variance: 16.0, strength: 4.0, seed: 1, foreground: [1.0, 0.0, 0.0, 1.0], background: [0.0, 0.0, 1.0, 1.0] };
let a = run(&s, &p); let a = run(&s, &p);
assert_eq!(a, run(&s, &p)); assert_eq!(a, run(&s, &p));
for px in a.read_region(R).chunks_exact(4) { for px in a.read_region(R).as_chunks::<4>().0 {
assert!((px[0] + px[2] - 1.0).abs() < 1e-5 && px[1].abs() < 1e-6); assert!((px[0] + px[2] - 1.0).abs() < 1e-5 && px[1].abs() < 1e-6);
} }
let b = run(&s, &FilterParams::Fibers { variance: 16.0, strength: 4.0, seed: 2, foreground: [1.0, 0.0, 0.0, 1.0], background: [0.0, 0.0, 1.0, 1.0] }); let b = run(&s, &FilterParams::Fibers { variance: 16.0, strength: 4.0, seed: 2, foreground: [1.0, 0.0, 0.0, 1.0], background: [0.0, 0.0, 1.0, 1.0] });
@ -462,7 +462,7 @@ fn lighting_point_light_falls_off() {
} }
fn variance(s: &Surface, r: Rect, c: usize) -> f32 { fn variance(s: &Surface, r: Rect, c: usize) -> f32 {
let v: Vec<f32> = s.read_region(r).chunks_exact(4).map(|p| p[c]).collect(); let v: Vec<f32> = s.read_region(r).as_chunks::<4>().0.iter().map(|p| p[c]).collect();
let m = v.iter().sum::<f32>() / v.len() as f32; let m = v.iter().sum::<f32>() / v.len() as f32;
v.iter().map(|x| (x - m).powi(2)).sum::<f32>() / v.len() as f32 v.iter().map(|x| (x - m).powi(2)).sum::<f32>() / v.len() as f32
} }
@ -525,7 +525,7 @@ fn shaped_blurs_spread_a_point_into_the_shape() {
}, },
); );
// A triangle iris pointing up: the bokeh reaches farther below the point than above it... and specular boost brightens. // A triangle iris pointing up: the bokeh reaches farther below the point than above it... and specular boost brightens.
let sum: f32 = lb.read_region(R).chunks_exact(4).map(|p| p[0]).sum(); let sum: f32 = lb.read_region(R).as_chunks::<4>().0.iter().map(|p| p[0]).sum();
assert!(sum > 1.5, "highlight bloomed: {sum}"); assert!(sum > 1.5, "highlight bloomed: {sum}");
assert!(lb.pixel(20, 15 + 5)[0] != lb.pixel(20, 15 - 5)[0], "triangular, not circular"); assert!(lb.pixel(20, 15 + 5)[0] != lb.pixel(20, 15 - 5)[0], "triangular, not circular");
} }

View File

@ -283,7 +283,7 @@ pub fn warp_mesh_gray(s: &Surface, f: &(dyn Fn(f64, f64) -> (f64, f64) + Sync),
let b = w.content_bounds(); let b = w.content_bounds();
if !b.is_empty() { if !b.is_empty() {
let px = w.read_region(b); let px = w.read_region(b);
let flat: Vec<f32> = px.chunks_exact(2).map(|p| p[0] * p[1] + default * (1.0 - p[1])).collect(); let flat: Vec<f32> = px.as_chunks::<2>().0.iter().map(|p| p[0] * p[1] + default * (1.0 - p[1])).collect();
out.write_region(b, &flat); out.write_region(b, &flat);
} }
out.prune(); out.prune();

View File

@ -186,10 +186,10 @@ pub fn parse_look(text: &str) -> Result<LutFile, LutError> {
let size: usize = tag("size").and_then(|s| s.parse().ok()).ok_or_else(|| LutError("no <size> in .look".into()))?; let size: usize = tag("size").and_then(|s| s.parse().ok()).ok_or_else(|| LutError("no <size> in .look".into()))?;
let hex: Vec<u8> = tag("data").ok_or_else(|| LutError("no <data> in .look".into()))?.bytes().filter(u8::is_ascii_hexdigit).collect(); let hex: Vec<u8> = tag("data").ok_or_else(|| LutError("no <data> in .look".into()))?.bytes().filter(u8::is_ascii_hexdigit).collect();
let nib = |c: u8| (c as char).to_digit(16).unwrap_or(0) as u8; let nib = |c: u8| (c as char).to_digit(16).unwrap_or(0) as u8;
let bytes: Vec<u8> = hex.chunks_exact(2).map(|p| nib(p[0]) << 4 | nib(p[1])).collect(); let bytes: Vec<u8> = hex.as_chunks::<2>().0.iter().map(|p| nib(p[0]) << 4 | nib(p[1])).collect();
let floats: Vec<f32> = bytes.chunks_exact(4).map(|b| f32::from_le_bytes([b[0], b[1], b[2], b[3]])).collect(); let floats: Vec<f32> = bytes.as_chunks::<4>().0.iter().map(|b| f32::from_le_bytes([b[0], b[1], b[2], b[3]])).collect();
let n3 = size.pow(3); let n3 = size.pow(3);
let data = if floats.len() == n3 * 4 { floats.chunks_exact(4).flat_map(|c| [c[0], c[1], c[2]]).collect() } else { floats }; let data = if floats.len() == n3 * 4 { floats.as_chunks::<4>().0.iter().flat_map(|c| [c[0], c[1], c[2]]).collect() } else { floats };
LutFile { title: tag("title").unwrap_or_default(), size, data }.check() LutFile { title: tag("title").unwrap_or_default(), size, data }.check()
} }
@ -200,7 +200,7 @@ pub fn write_cube(l: &LutFile) -> String {
s.push_str(&format!("TITLE \"{}\"\n", l.title.replace('"', "'"))); s.push_str(&format!("TITLE \"{}\"\n", l.title.replace('"', "'")));
} }
s.push_str(&format!("LUT_3D_SIZE {}\n", l.size)); s.push_str(&format!("LUT_3D_SIZE {}\n", l.size));
for c in l.data.chunks_exact(3) { for c in l.data.as_chunks::<3>().0 {
s.push_str(&format!("{:.6} {:.6} {:.6}\n", c[0], c[1], c[2])); s.push_str(&format!("{:.6} {:.6} {:.6}\n", c[0], c[1], c[2]));
} }
s s

View File

@ -525,7 +525,7 @@ fn parse_curve(d: &[u8]) -> Result<(Curve, usize), CmsError> {
return Err(CmsError::Invalid("curve too long".into())); return Err(CmsError::Invalid("curve too long".into()));
} }
let s = r.slice(12, n * 2)?; let s = r.slice(12, n * 2)?;
let t = s.chunks_exact(2).map(|c| u16::from_be_bytes([c[0], c[1]]) as f32 / 65535.0).collect(); let t = s.as_chunks::<2>().0.iter().map(|c| u16::from_be_bytes([c[0], c[1]]) as f32 / 65535.0).collect();
Ok((Curve::Table(t), len)) Ok((Curve::Table(t), len))
} }
} }
@ -586,7 +586,7 @@ fn parse_lut(d: &[u8], a2b: bool) -> Result<Lut, CmsError> {
for _ in 0..count { for _ in 0..count {
let s = r.slice(*off, len * bps)?; let s = r.slice(*off, len * bps)?;
let t: Vec<f32> = if wide { let t: Vec<f32> = if wide {
s.chunks_exact(2).map(|c| u16::from_be_bytes([c[0], c[1]]) as f32 / 65535.0).collect() s.as_chunks::<2>().0.iter().map(|c| u16::from_be_bytes([c[0], c[1]]) as f32 / 65535.0).collect()
} else { } else {
s.iter().map(|b| *b as f32 / 255.0).collect() s.iter().map(|b| *b as f32 / 255.0).collect()
}; };
@ -603,7 +603,7 @@ fn parse_lut(d: &[u8], a2b: bool) -> Result<Lut, CmsError> {
let total = nodes as usize * o; let total = nodes as usize * o;
let s = r.slice(off, total * bps)?; let s = r.slice(off, total * bps)?;
let data: Vec<f32> = if wide { let data: Vec<f32> = if wide {
s.chunks_exact(2).map(|c| u16::from_be_bytes([c[0], c[1]]) as f32 / 65535.0).collect() s.as_chunks::<2>().0.iter().map(|c| u16::from_be_bytes([c[0], c[1]]) as f32 / 65535.0).collect()
} else { } else {
s.iter().map(|b| *b as f32 / 255.0).collect() s.iter().map(|b| *b as f32 / 255.0).collect()
}; };
@ -653,7 +653,7 @@ fn parse_lut(d: &[u8], a2b: bool) -> Result<Lut, CmsError> {
let total = nodes as usize * cout; let total = nodes as usize * cout;
let data: Vec<f32> = match prec { let data: Vec<f32> = match prec {
1 => r.slice(off + 20, total)?.iter().map(|b| *b as f32 / 255.0).collect(), 1 => r.slice(off + 20, total)?.iter().map(|b| *b as f32 / 255.0).collect(),
2 => r.slice(off + 20, total * 2)?.chunks_exact(2).map(|c| u16::from_be_bytes([c[0], c[1]]) as f32 / 65535.0).collect(), 2 => r.slice(off + 20, total * 2)?.as_chunks::<2>().0.iter().map(|c| u16::from_be_bytes([c[0], c[1]]) as f32 / 65535.0).collect(),
_ => return Err(CmsError::Invalid(format!("CLUT precision {prec}"))), _ => return Err(CmsError::Invalid(format!("CLUT precision {prec}"))),
}; };
Ok(Stage::Clut(Clut::new(grid, cout, data))) Ok(Stage::Clut(Clut::new(grid, cout, data)))
@ -730,7 +730,7 @@ fn parse_text(d: &[u8]) -> String {
} }
let Some((off, len)) = best else { return String::new() }; let Some((off, len)) = best else { return String::new() };
let Ok(s) = r.slice(off, len) else { return String::new() }; let Ok(s) = r.slice(off, len) else { return String::new() };
let units: Vec<u16> = s.chunks_exact(2).map(|c| u16::from_be_bytes([c[0], c[1]])).collect(); let units: Vec<u16> = s.as_chunks::<2>().0.iter().map(|c| u16::from_be_bytes([c[0], c[1]])).collect();
String::from_utf16_lossy(&units).trim_end_matches('\0').trim().to_string() String::from_utf16_lossy(&units).trim_end_matches('\0').trim().to_string()
} }
_ => String::new(), _ => String::new(),

View File

@ -429,18 +429,18 @@ impl Transform {
let run = |(s, d): (&[u8], &mut [u8])| match sample { let run = |(s, d): (&[u8], &mut [u8])| match sample {
SampleKind::U8 => self.run_u8(s, src_stride, d, dst_stride, extra), SampleKind::U8 => self.run_u8(s, src_stride, d, dst_stride, extra),
SampleKind::U16 => { SampleKind::U16 => {
let a: Vec<u16> = s.chunks_exact(2).map(|b| u16::from_ne_bytes([b[0], b[1]])).collect(); let a: Vec<u16> = s.as_chunks::<2>().0.iter().map(|b| u16::from_ne_bytes([b[0], b[1]])).collect();
let mut o: Vec<u16> = d.chunks_exact(2).map(|b| u16::from_ne_bytes([b[0], b[1]])).collect(); let mut o: Vec<u16> = d.as_chunks::<2>().0.iter().map(|b| u16::from_ne_bytes([b[0], b[1]])).collect();
self.run_u16(&a, src_stride, &mut o, dst_stride, extra); self.run_u16(&a, src_stride, &mut o, dst_stride, extra);
for (b, v) in d.chunks_exact_mut(2).zip(o) { for (b, v) in d.as_chunks_mut::<2>().0.iter_mut().zip(o) {
b.copy_from_slice(&v.to_ne_bytes()); b.copy_from_slice(&v.to_ne_bytes());
} }
} }
SampleKind::F32 => { SampleKind::F32 => {
let a: Vec<f32> = s.chunks_exact(4).map(|b| f32::from_ne_bytes([b[0], b[1], b[2], b[3]])).collect(); let a: Vec<f32> = s.as_chunks::<4>().0.iter().map(|b| f32::from_ne_bytes([b[0], b[1], b[2], b[3]])).collect();
let mut o: Vec<f32> = d.chunks_exact(4).map(|b| f32::from_ne_bytes([b[0], b[1], b[2], b[3]])).collect(); let mut o: Vec<f32> = d.as_chunks::<4>().0.iter().map(|b| f32::from_ne_bytes([b[0], b[1], b[2], b[3]])).collect();
self.run_f32(&a, src_stride, &mut o, dst_stride, extra); self.run_f32(&a, src_stride, &mut o, dst_stride, extra);
for (b, v) in d.chunks_exact_mut(4).zip(o) { for (b, v) in d.as_chunks_mut::<4>().0.iter_mut().zip(o) {
b.copy_from_slice(&v.to_ne_bytes()); b.copy_from_slice(&v.to_ne_bytes());
} }
} }

View File

@ -133,7 +133,7 @@ pub(crate) fn decode(bytes: &[u8], limits: &Limits) -> Result<Image, CodecError>
// Adobe-style CMYK is stored inverted (255 = no ink). // Adobe-style CMYK is stored inverted (255 = no ink).
ColorSpace::CMYK => px.iter_mut().for_each(|v| *v = 255 - *v), ColorSpace::CMYK => px.iter_mut().for_each(|v| *v = 255 - *v),
ColorSpace::YCCK => { ColorSpace::YCCK => {
for p in px.chunks_exact_mut(4) { for p in px.as_chunks_mut::<4>().0 {
let (y, cb, cr) = (p[0] as f32, p[1] as f32 - 128.0, p[2] as f32 - 128.0); let (y, cb, cr) = (p[0] as f32, p[1] as f32 - 128.0, p[2] as f32 - 128.0);
let c = y + 1.402 * cr; let c = y + 1.402 * cr;
let m = y - 0.344_136 * cb - 0.714_136 * cr; let m = y - 0.344_136 * cb - 0.714_136 * cr;

View File

@ -48,7 +48,7 @@ pub(crate) fn decode(bytes: &[u8], limits: &Limits) -> Result<Image, CodecError>
// tolerated: pixel data is already complete. // tolerated: pixel data is already complete.
let _ = reader.finish(); let _ = reader.finish();
if sample == SampleType::U16 { if sample == SampleType::U16 {
for c in buf.chunks_exact_mut(2) { for c in buf.as_chunks_mut::<2>().0 {
let v = u16::from_be_bytes([c[0], c[1]]); let v = u16::from_be_bytes([c[0], c[1]]);
c.copy_from_slice(&v.to_ne_bytes()); c.copy_from_slice(&v.to_ne_bytes());
} }
@ -114,7 +114,7 @@ pub(crate) fn encode(src: &Image, plan: Plan, opts: &EncodeOptions) -> Result<Ve
// Big-endian sample bytes. // Big-endian sample bytes.
let mut data = img.data().to_vec(); let mut data = img.data().to_vec();
if depth == png::BitDepth::Sixteen { if depth == png::BitDepth::Sixteen {
for c in data.chunks_exact_mut(2) { for c in data.as_chunks_mut::<2>().0 {
let v = u16::from_ne_bytes([c[0], c[1]]); let v = u16::from_ne_bytes([c[0], c[1]]);
c.copy_from_slice(&v.to_be_bytes()); c.copy_from_slice(&v.to_be_bytes());
} }

View File

@ -103,7 +103,7 @@ fn read_binary(data: &[u8], n: usize, maxval: u32) -> Result<Vec<u32>, CodecErro
if data.len() < n * 2 { if data.len() < n * 2 {
return Err(err("truncated raster")); return Err(err("truncated raster"));
} }
Ok(data[..n * 2].chunks_exact(2).map(|c| u16::from_be_bytes([c[0], c[1]]) as u32).collect()) Ok(data[..n * 2].as_chunks::<2>().0.iter().map(|c| u16::from_be_bytes([c[0], c[1]]) as u32).collect())
} }
} }

View File

@ -243,7 +243,7 @@ fn is_avif_ftyp(b: &[u8]) -> bool {
let size = u32::from_be_bytes([b[0], b[1], b[2], b[3]]) as usize; let size = u32::from_be_bytes([b[0], b[1], b[2], b[3]]) as usize;
let end = size.clamp(12, b.len().min(64)); let end = size.clamp(12, b.len().min(64));
let brands = &b[8..end]; let brands = &b[8..end];
brands.chunks_exact(4).enumerate().any(|(i, c)| i != 1 && (c == b"avif" || c == b"avis")) brands.as_chunks::<4>().0.iter().enumerate().any(|(i, c)| i != 1 && (c == b"avif" || c == b"avis"))
} }
fn looks_like_tga(b: &[u8]) -> bool { fn looks_like_tga(b: &[u8]) -> bool {

View File

@ -238,17 +238,17 @@ impl Image {
/// Copy samples out as `u16` (only when the sample type is `U16`). /// Copy samples out as `u16` (only when the sample type is `U16`).
pub fn to_u16_samples(&self) -> Option<Vec<u16>> { pub fn to_u16_samples(&self) -> Option<Vec<u16>> {
(self.sample == SampleType::U16).then(|| self.data.chunks_exact(2).map(|c| u16::from_ne_bytes([c[0], c[1]])).collect()) (self.sample == SampleType::U16).then(|| self.data.as_chunks::<2>().0.iter().map(|c| u16::from_ne_bytes([c[0], c[1]])).collect())
} }
/// Copy samples out as `f16` (only when the sample type is `F16`). /// Copy samples out as `f16` (only when the sample type is `F16`).
pub fn to_f16_samples(&self) -> Option<Vec<f16>> { pub fn to_f16_samples(&self) -> Option<Vec<f16>> {
(self.sample == SampleType::F16).then(|| self.data.chunks_exact(2).map(|c| f16::from_bits(u16::from_ne_bytes([c[0], c[1]]))).collect()) (self.sample == SampleType::F16).then(|| self.data.as_chunks::<2>().0.iter().map(|c| f16::from_bits(u16::from_ne_bytes([c[0], c[1]]))).collect())
} }
/// Copy samples out as `f32` (only when the sample type is `F32`). /// Copy samples out as `f32` (only when the sample type is `F32`).
pub fn to_f32_samples(&self) -> Option<Vec<f32>> { pub fn to_f32_samples(&self) -> Option<Vec<f32>> {
(self.sample == SampleType::F32).then(|| self.data.chunks_exact(4).map(|c| f32::from_ne_bytes([c[0], c[1], c[2], c[3]])).collect()) (self.sample == SampleType::F32).then(|| self.data.as_chunks::<4>().0.iter().map(|c| f32::from_ne_bytes([c[0], c[1], c[2], c[3]])).collect())
} }
/// Sample `index` (in samples, not bytes) as a normalized f32. /// Sample `index` (in samples, not bytes) as a normalized f32.
@ -363,9 +363,9 @@ pub(crate) fn read_normalized(data: &[u8], sample: SampleType, i: usize) -> f32
pub(crate) fn to_normalized(data: &[u8], sample: SampleType) -> Vec<f32> { pub(crate) fn to_normalized(data: &[u8], sample: SampleType) -> Vec<f32> {
match sample { match sample {
SampleType::U8 => data.iter().map(|&v| v as f32 / 255.0).collect(), SampleType::U8 => data.iter().map(|&v| v as f32 / 255.0).collect(),
SampleType::U16 => data.chunks_exact(2).map(|c| u16::from_ne_bytes([c[0], c[1]]) as f32 / 65535.0).collect(), SampleType::U16 => data.as_chunks::<2>().0.iter().map(|c| u16::from_ne_bytes([c[0], c[1]]) as f32 / 65535.0).collect(),
SampleType::F16 => data.chunks_exact(2).map(|c| f16::from_bits(u16::from_ne_bytes([c[0], c[1]])).to_f32()).collect(), SampleType::F16 => data.as_chunks::<2>().0.iter().map(|c| f16::from_bits(u16::from_ne_bytes([c[0], c[1]])).to_f32()).collect(),
SampleType::F32 => data.chunks_exact(4).map(|c| f32::from_ne_bytes([c[0], c[1], c[2], c[3]])).collect(), SampleType::F32 => data.as_chunks::<4>().0.iter().map(|c| f32::from_ne_bytes([c[0], c[1], c[2], c[3]])).collect(),
} }
} }

View File

@ -48,7 +48,7 @@ impl Buffer {
} }
pub fn to_rgba8(&self) -> Rgba8Image { pub fn to_rgba8(&self) -> Rgba8Image {
let mut img = Rgba8Image::new(self.rect.width(), self.rect.height()); let mut img = Rgba8Image::new(self.rect.width(), self.rect.height());
for (o, p) in img.pixels.chunks_exact_mut(4).zip(&self.px) { for (o, p) in img.pixels.as_chunks_mut::<4>().0.iter_mut().zip(&self.px) {
for (dst, v) in o.iter_mut().zip(p) { for (dst, v) in o.iter_mut().zip(p) {
*dst = (v.clamp(0.0, 1.0) * 255.0 + 0.5) as u8; *dst = (v.clamp(0.0, 1.0) * 255.0 + 0.5) as u8;
} }

View File

@ -329,7 +329,7 @@ impl ColorState {
let check = let check =
if pv.gamut_warning { Some(GamutCheck::new(&composite_profile(doc), &pv.setup.profile, pv.gamut_threshold).map_err(cms_err)?) } else { None }; if pv.gamut_warning { Some(GamutCheck::new(&composite_profile(doc), &pv.setup.profile, pv.gamut_threshold).map_err(cms_err)?) } else { None };
let s = (size - 1) as f32; let s = (size - 1) as f32;
for (i, px) in bytes.chunks_exact_mut(4).enumerate() { for (i, px) in bytes.as_chunks_mut::<4>().0.iter_mut().enumerate() {
let rgb = [(i % size) as f32 / s, ((i / size) % size) as f32 / s, (i / (size * size)) as f32 / s]; let rgb = [(i % size) as f32 / s, ((i / size) % size) as f32 / s, (i / (size * size)) as f32 / s];
px[3] = if check.as_ref().is_some_and(|c| c.out_of_gamut(&rgb)) { 255 } else { 0 }; px[3] = if check.as_ref().is_some_and(|c| c.out_of_gamut(&rgb)) { 255 } else { 0 };
} }
@ -1127,7 +1127,7 @@ mod tests {
let surf = doc.layers[0].surface_mut().unwrap(); let surf = doc.layers[0].surface_mut().unwrap();
let mut bytes = vec![0u8; r.width() as usize * r.height() as usize * 4]; let mut bytes = vec![0u8; r.width() as usize * r.height() as usize * 4];
let mut x = 1u32; let mut x = 1u32;
for px in bytes.chunks_exact_mut(4) { for px in bytes.as_chunks_mut::<4>().0 {
x ^= x << 13; x ^= x << 13;
x ^= x >> 17; x ^= x >> 17;
x ^= x << 5; x ^= x << 5;

View File

@ -158,7 +158,7 @@ fn colour_filters_use_and_record_tool_colours() {
let r = s.execute("filter.render.fibers", json!({"seed": 1})).unwrap(); let r = s.execute("filter.render.fibers", json!({"seed": 1})).unwrap();
assert_eq!(r["filter"]["foreground"], json!([1.0, 0.0, 0.0, 1.0])); assert_eq!(r["filter"]["foreground"], json!([1.0, 0.0, 0.0, 1.0]));
let px = pixels(&s); let px = pixels(&s);
for p in px.chunks_exact(4) { for p in px.as_chunks::<4>().0 {
assert!(p[1] < 1e-2 && (p[0] + p[2] - 1.0).abs() < 2e-2, "{p:?}"); assert!(p[1] < 1e-2 && (p[0] + p[2] - 1.0).abs() < 2e-2, "{p:?}");
} }
} }

View File

@ -234,7 +234,7 @@ mod tests {
} }
fn opaque(s: &Session) -> usize { fn opaque(s: &Session) -> usize {
pixels(s).chunks_exact(4).filter(|p| p[3] > 0.05).count() pixels(s).as_chunks::<4>().0.iter().filter(|p| p[3] > 0.05).count()
} }
#[test] #[test]
@ -294,7 +294,7 @@ mod tests {
let r = s.execute("filter.render.tree", json!({"newLayer": true, "baseTreeType": 1, "leavesAmount": 100})).unwrap(); let r = s.execute("filter.render.tree", json!({"newLayer": true, "baseTreeType": 1, "leavesAmount": 100})).unwrap();
assert_eq!(r["newLayer"], json!(true)); assert_eq!(r["newLayer"], json!(true));
let px = pixels(&s); let px = pixels(&s);
let right = px.chunks_exact(4).enumerate().filter(|(i, p)| i % 160 >= 81 && p[3] > 0.0).count(); let right = px.as_chunks::<4>().0.iter().enumerate().filter(|(i, p)| i % 160 >= 81 && p[3] > 0.0).count();
assert_eq!(right, 0, "nothing drawn outside the selection"); assert_eq!(right, 0, "nothing drawn outside the selection");
assert!(opaque(&s) > 50); assert!(opaque(&s) > 50);
} }

View File

@ -56,7 +56,7 @@ pub(crate) fn warp_gray(s: &Surface, h: &Homography, interp: Interp) -> Surface
let b = w.content_bounds(); let b = w.content_bounds();
if !b.is_empty() { if !b.is_empty() {
let px = w.read_region(b); let px = w.read_region(b);
let flat: Vec<f32> = px.chunks_exact(2).map(|p| p[0] * p[1] + default * (1.0 - p[1])).collect(); let flat: Vec<f32> = px.as_chunks::<2>().0.iter().map(|p| p[0] * p[1] + default * (1.0 - p[1])).collect();
out.write_region(b, &flat); out.write_region(b, &flat);
} }
out.prune(); out.prune();

View File

@ -673,7 +673,7 @@ mod tests {
// Red pixels exist. // Red pixels exist.
let c = t.cache.as_ref().unwrap(); let c = t.cache.as_ref().unwrap();
let r = c.content_bounds(); let r = c.content_bounds();
assert!(c.read_region(r).chunks_exact(4).any(|p| p[3] > 0.9 && p[0] > 0.9 && p[1] < 0.1)); assert!(c.read_region(r).as_chunks::<4>().0.iter().any(|p| p[3] > 0.9 && p[0] > 0.9 && p[1] < 0.1));
// The TySh we generated parses back to the same model. // The TySh we generated parses back to the same model.
let back = photocraft_text::psd::text_layer_from_tysh(t.psd_raw.as_ref().unwrap(), 72.0).unwrap(); let back = photocraft_text::psd::text_layer_from_tysh(t.psd_raw.as_ref().unwrap(), 72.0).unwrap();
assert_eq!(back.text, "Hello"); assert_eq!(back.text, "Hello");

View File

@ -385,7 +385,7 @@ pub fn optimize(px: &[[f32; 4]], bw: usize, rect: Rect, st: &WebSettings, icc: O
data.extend_from_slice(&[to8(q[0]), to8(q[1]), to8(q[2]), to8(q[3])][..k]); data.extend_from_slice(&[to8(q[0]), to8(q[1]), to8(q[2]), to8(q[3])][..k]);
} }
if preview { if preview {
look = if alpha { data.clone() } else { data.chunks_exact(3).flat_map(|c| [c[0], c[1], c[2], 255]).collect() }; look = if alpha { data.clone() } else { data.as_chunks::<3>().0.iter().flat_map(|c| [c[0], c[1], c[2], 255]).collect() };
} }
let layout = if alpha { photocraft_codecs::ChannelLayout::Rgba } else { photocraft_codecs::ChannelLayout::Rgb }; let layout = if alpha { photocraft_codecs::ChannelLayout::Rgba } else { photocraft_codecs::ChannelLayout::Rgb };
let meta = photocraft_codecs::Metadata { dpi: Some((dpi, dpi)), xmp: xmp.map(str::to_string), ..Default::default() }; let meta = photocraft_codecs::Metadata { dpi: Some((dpi, dpi)), xmp: xmp.map(str::to_string), ..Default::default() };
@ -412,7 +412,7 @@ pub fn optimize(px: &[[f32; 4]], bw: usize, rect: Rect, st: &WebSettings, icc: O
// The preview shows the compression artefacts. // The preview shows the compression artefacts.
look = match photocraft_codecs::decode(&bytes) { look = match photocraft_codecs::decode(&bytes) {
Ok(img) => img.convert(photocraft_codecs::ChannelLayout::Rgba, photocraft_codecs::SampleType::U8).data().to_vec(), Ok(img) => img.convert(photocraft_codecs::ChannelLayout::Rgba, photocraft_codecs::SampleType::U8).data().to_vec(),
Err(_) => rgb.chunks_exact(3).flat_map(|c| [c[0], c[1], c[2], 255]).collect(), Err(_) => rgb.as_chunks::<3>().0.iter().flat_map(|c| [c[0], c[1], c[2], 255]).collect(),
}; };
} }
(bytes, None) (bytes, None)

View File

@ -60,8 +60,8 @@ pub(crate) fn swap_to_le(bytes: &mut [u8], sample: SampleType) {
if cfg!(target_endian = "big") { if cfg!(target_endian = "big") {
match sample { match sample {
SampleType::U8 => {} SampleType::U8 => {}
SampleType::U16 => bytes.chunks_exact_mut(2).for_each(|c| c.reverse()), SampleType::U16 => bytes.as_chunks_mut::<2>().0.iter_mut().for_each(|c| c.reverse()),
SampleType::F32 => bytes.chunks_exact_mut(4).for_each(|c| c.reverse()), SampleType::F32 => bytes.as_chunks_mut::<4>().0.iter_mut().for_each(|c| c.reverse()),
} }
} }
} }

View File

@ -103,7 +103,7 @@ fn color_from(space: u16, c: [u16; 4]) -> Color {
fn decode_text(data: &[u8]) -> String { fn decode_text(data: &[u8]) -> String {
let s = if data.len() >= 2 && data[0] == 0xFE && data[1] == 0xFF { let s = if data.len() >= 2 && data[0] == 0xFE && data[1] == 0xFF {
let units: Vec<u16> = data[2..].chunks_exact(2).map(|c| u16::from_be_bytes([c[0], c[1]])).collect(); let units: Vec<u16> = data[2..].as_chunks::<2>().0.iter().map(|c| u16::from_be_bytes([c[0], c[1]])).collect();
String::from_utf16_lossy(&units) String::from_utf16_lossy(&units)
} else { } else {
data.iter().map(|&c| c as char).collect() data.iter().map(|&c| c as char).collect()

View File

@ -10,7 +10,7 @@ use photocraft_psd::layer::BlendingRanges;
/// `brst` (channel blending restrictions): a list of big-endian u32 channel indices left out of /// `brst` (channel blending restrictions): a list of big-endian u32 channel indices left out of
/// blending → a bit mask (bit `i` = channel `i`; indices above 31 are ignored). /// blending → a bit mask (bit `i` = channel `i`; indices above 31 are ignored).
pub fn parse_brst(data: &[u8]) -> u32 { pub fn parse_brst(data: &[u8]) -> u32 {
data.chunks_exact(4).map(|c| u32::from_be_bytes([c[0], c[1], c[2], c[3]])).filter(|&i| i < 32).fold(0, |m, i| m | 1 << i) data.as_chunks::<4>().0.iter().map(|c| u32::from_be_bytes([c[0], c[1], c[2], c[3]])).filter(|&i| i < 32).fold(0, |m, i| m | 1 << i)
} }
/// Inverse of [`parse_brst`]; `None` when every channel blends (no block). /// Inverse of [`parse_brst`]; `None` when every channel blends (no block).

View File

@ -42,7 +42,7 @@ pub(crate) fn display_info(channels: &[&AlphaChannel]) -> Vec<u8> {
/// Applies a DisplayInfo resource (`versioned` = 1077) to the imported channels in order. /// Applies a DisplayInfo resource (`versioned` = 1077) to the imported channels in order.
pub(crate) fn apply_display_info(data: &[u8], versioned: bool, channels: &mut [AlphaChannel]) { pub(crate) fn apply_display_info(data: &[u8], versioned: bool, channels: &mut [AlphaChannel]) {
let body = if versioned { data.get(4..).unwrap_or(&[]) } else { data }; let body = if versioned { data.get(4..).unwrap_or(&[]) } else { data };
for (c, rec) in channels.iter_mut().zip(body.chunks_exact(14)) { for (c, rec) in channels.iter_mut().zip(body.as_chunks::<14>().0) {
let u = |i: usize| u16::from_be_bytes([rec[i], rec[i + 1]]); let u = |i: usize| u16::from_be_bytes([rec[i], rec[i + 1]]);
let comps = [u(2), u(4), u(6), u(8)]; let comps = [u(2), u(4), u(6), u(8)];
let color = decode_color(u(0), comps); let color = decode_color(u(0), comps);

View File

@ -148,7 +148,9 @@ pub fn merged_composite(file: &PsdFile) -> Result<Vec<[f32; 4]>, IoError> {
let unmatte = file.merged_has_alpha(); let unmatte = file.merged_has_alpha();
return Ok(img return Ok(img
.data .data
.chunks_exact(4) .as_chunks::<4>()
.0
.iter()
.map(|p| { .map(|p| {
let mut v: [f32; 4] = std::array::from_fn(|i| f32::from(p[i]) / 255.0); let mut v: [f32; 4] = std::array::from_fn(|i| f32::from(p[i]) / 255.0);
if unmatte { if unmatte {

View File

@ -43,7 +43,7 @@ impl<'a> Rd<'a> {
fn unicode(&mut self) -> Option<String> { fn unicode(&mut self) -> Option<String> {
let n = self.u32()? as usize; let n = self.u32()? as usize;
let raw = self.take(n.checked_mul(2)?)?; let raw = self.take(n.checked_mul(2)?)?;
let units: Vec<u16> = raw.chunks_exact(2).map(|c| u16::from_be_bytes([c[0], c[1]])).collect(); let units: Vec<u16> = raw.as_chunks::<2>().0.iter().map(|c| u16::from_be_bytes([c[0], c[1]])).collect();
Some(String::from_utf16_lossy(&units).trim_end_matches('\0').to_string()) Some(String::from_utf16_lossy(&units).trim_end_matches('\0').to_string())
} }
} }

View File

@ -351,7 +351,7 @@ fn unicode_names(data: &[u8]) -> Vec<String> {
let n = u32::from_be_bytes([data[at], data[at + 1], data[at + 2], data[at + 3]]) as usize; let n = u32::from_be_bytes([data[at], data[at + 1], data[at + 2], data[at + 3]]) as usize;
at += 4; at += 4;
let Some(b) = data.get(at..at + n * 2) else { break }; let Some(b) = data.get(at..at + n * 2) else { break };
let units: Vec<u16> = b.chunks_exact(2).map(|c| u16::from_be_bytes([c[0], c[1]])).collect(); let units: Vec<u16> = b.as_chunks::<2>().0.iter().map(|c| u16::from_be_bytes([c[0], c[1]])).collect();
out.push(String::from_utf16_lossy(&units).trim_end_matches('\0').to_string()); out.push(String::from_utf16_lossy(&units).trim_end_matches('\0').to_string());
at += n * 2; at += n * 2;
} }
@ -549,7 +549,9 @@ pub fn psd_to_document(file: &PsdFile) -> (Document, Vec<String>) {
let mut s = Surface::new(fmt); let mut s = Surface::new(fmt);
let vals: Vec<f32> = img let vals: Vec<f32> = img
.data .data
.chunks_exact(4) .as_chunks::<4>()
.0
.iter()
.flat_map(|p| { .flat_map(|p| {
let v = photocraft_raster::from_rgba(&fmt, [p[0], p[1], p[2], p[3]].map(|x| f32::from(x) / 255.0)); let v = photocraft_raster::from_rgba(&fmt, [p[0], p[1], p[2], p[3]].map(|x| f32::from(x) / 255.0));
v.into_iter() v.into_iter()

View File

@ -119,7 +119,7 @@ fn layered_to_png_warns_flatten() {
// Composite equals our flatten. // Composite equals our flatten.
let flat = photocraft_compose::flatten(&d).px; let flat = photocraft_compose::flatten(&d).px;
let s = back.layers[0].surface().unwrap(); let s = back.layers[0].surface().unwrap();
let px: Vec<[f32; 4]> = s.read_region(back.bounds()).chunks_exact(4).map(|p| [p[0], p[1], p[2], p[3]]).collect(); let px: Vec<[f32; 4]> = s.read_region(back.bounds()).as_chunks::<4>().0.iter().map(|p| [p[0], p[1], p[2], p[3]]).collect();
assert!(max_diff(&px, &flat) <= 1.0 / 255.0 + 1e-4); assert!(max_diff(&px, &flat) <= 1.0 / 255.0 + 1e-4);
} }

View File

@ -88,7 +88,7 @@ impl<'de> Deserialize<'de> for GrayTile {
let data: Vec<u16> = match t.data { let data: Vec<u16> = match t.data {
DataIn::B64(s) => { DataIn::B64(s) => {
let b = b64_decode(&s).ok_or_else(|| D::Error::custom("invalid base64 in tile data"))?; let b = b64_decode(&s).ok_or_else(|| D::Error::custom("invalid base64 in tile data"))?;
b.chunks_exact(2).map(|c| u16::from_le_bytes([c[0], c[1]])).collect() b.as_chunks::<2>().0.iter().map(|c| u16::from_le_bytes([c[0], c[1]])).collect()
} }
DataIn::Floats(v) => v.iter().map(|&x| q16(x)).collect(), DataIn::Floats(v) => v.iter().map(|&x| q16(x)).collect(),
}; };

View File

@ -289,7 +289,7 @@ impl BlendingRanges {
} }
/// Parsed entries (incomplete trailing bytes are ignored). /// Parsed entries (incomplete trailing bytes are ignored).
pub fn ranges(&self) -> Vec<BlendRange> { pub fn ranges(&self) -> Vec<BlendRange> {
self.data.chunks_exact(8).map(|c| BlendRange { source: [c[0], c[1], c[2], c[3]], dest: [c[4], c[5], c[6], c[7]] }).collect() self.data.as_chunks::<8>().0.iter().map(|c| BlendRange { source: [c[0], c[1], c[2], c[3]], dest: [c[4], c[5], c[6], c[7]] }).collect()
} }
} }

View File

@ -115,7 +115,9 @@ impl PathData {
return Err(PsdError::LimitExceeded("path records")); return Err(PsdError::LimitExceeded("path records"));
} }
let records = data let records = data
.chunks_exact(RECORD_LEN) .as_chunks::<RECORD_LEN>()
.0
.iter()
.map(|c| { .map(|c| {
let mut d = [0u8; 24]; let mut d = [0u8; 24];
d.copy_from_slice(&c[2..]); d.copy_from_slice(&c[2..]);

View File

@ -105,7 +105,7 @@ fn read_pattern(r: &mut Rd) -> Result<PsdPattern> {
if n > 65_536 { if n > 65_536 {
return Err(PsdError::LimitExceeded("pattern name length")); return Err(PsdError::LimitExceeded("pattern name length"));
} }
let units: Vec<u16> = r.take(n * 2)?.chunks_exact(2).map(|c| u16::from_be_bytes([c[0], c[1]])).collect(); let units: Vec<u16> = r.take(n * 2)?.as_chunks::<2>().0.iter().map(|c| u16::from_be_bytes([c[0], c[1]])).collect();
let name = String::from_utf16_lossy(&units).trim_end_matches('\0').to_string(); let name = String::from_utf16_lossy(&units).trim_end_matches('\0').to_string();
let idl = r.u8()? as usize; let idl = r.u8()? as usize;
let id = String::from_utf8_lossy(r.take(idl)?).to_string(); let id = String::from_utf8_lossy(r.take(idl)?).to_string();

View File

@ -12,12 +12,12 @@ use crate::layer::{CHANNEL_REAL_USER_MASK, CHANNEL_TRANSPARENCY, CHANNEL_USER_MA
/// Interprets planar big-endian bytes as u16 samples. /// Interprets planar big-endian bytes as u16 samples.
pub fn samples_u16(bytes: &[u8]) -> Vec<u16> { pub fn samples_u16(bytes: &[u8]) -> Vec<u16> {
bytes.chunks_exact(2).map(|c| u16::from_be_bytes([c[0], c[1]])).collect() bytes.as_chunks::<2>().0.iter().map(|c| u16::from_be_bytes([c[0], c[1]])).collect()
} }
/// Interprets planar big-endian bytes as f32 samples. /// Interprets planar big-endian bytes as f32 samples.
pub fn samples_f32(bytes: &[u8]) -> Vec<f32> { pub fn samples_f32(bytes: &[u8]) -> Vec<f32> {
bytes.chunks_exact(4).map(|c| f32::from_be_bytes([c[0], c[1], c[2], c[3]])).collect() bytes.as_chunks::<4>().0.iter().map(|c| f32::from_be_bytes([c[0], c[1], c[2], c[3]])).collect()
} }
/// Encodes u16 samples as big-endian bytes. /// Encodes u16 samples as big-endian bytes.

View File

@ -185,7 +185,7 @@ impl ImageResource {
if !d.len().is_multiple_of(2) { if !d.len().is_multiple_of(2) {
Err(PsdError::invalid("layer group info has odd length")) Err(PsdError::invalid("layer group info has odd length"))
} else { } else {
Ok(ResourceData::LayerGroupInfo(d.chunks_exact(2).map(|c| u16::from_be_bytes([c[0], c[1]])).collect())) Ok(ResourceData::LayerGroupInfo(d.as_chunks::<2>().0.iter().map(|c| u16::from_be_bytes([c[0], c[1]])).collect()))
} }
} }
ids::THUMBNAIL | ids::THUMBNAIL_PS4 => Ok(ResourceData::Thumbnail(d)), ids::THUMBNAIL | ids::THUMBNAIL_PS4 => Ok(ResourceData::Thumbnail(d)),

View File

@ -277,13 +277,13 @@ impl Surface {
match (fmt.mode, fmt.sample, fmt.alpha) { match (fmt.mode, fmt.sample, fmt.alpha) {
(ColorMode::Rgb, SampleType::U8, true) => { (ColorMode::Rgb, SampleType::U8, true) => {
let src = &t.data[base..base + dst.len() * 4]; let src = &t.data[base..base + dst.len() * 4];
for (d, s) in dst.iter_mut().zip(src.chunks_exact(4)) { for (d, s) in dst.iter_mut().zip(src.as_chunks::<4>().0) {
*d = [s[0] as f32 / 255.0, s[1] as f32 / 255.0, s[2] as f32 / 255.0, s[3] as f32 / 255.0]; *d = [s[0] as f32 / 255.0, s[1] as f32 / 255.0, s[2] as f32 / 255.0, s[3] as f32 / 255.0];
} }
} }
(ColorMode::Grayscale, SampleType::U8, true) => { (ColorMode::Grayscale, SampleType::U8, true) => {
let src = &t.data[base..base + dst.len() * 2]; let src = &t.data[base..base + dst.len() * 2];
for (d, s) in dst.iter_mut().zip(src.chunks_exact(2)) { for (d, s) in dst.iter_mut().zip(src.as_chunks::<2>().0) {
let g = s[0] as f32 / 255.0; let g = s[0] as f32 / 255.0;
*d = [g, g, g, s[1] as f32 / 255.0]; *d = [g, g, g, s[1] as f32 / 255.0];
} }
@ -324,7 +324,7 @@ impl Surface {
Some(t) if rgba8 => { Some(t) if rgba8 => {
let base = (((y - tc.ty * TILE_SIZE) as usize) * TILE_SIZE as usize + (tr.x0 - tc.tx * TILE_SIZE) as usize) * 4; let base = (((y - tc.ty * TILE_SIZE) as usize) * TILE_SIZE as usize + (tr.x0 - tc.tx * TILE_SIZE) as usize) * 4;
let len = dst.len(); let len = dst.len();
for (d, s) in dst.iter_mut().zip(t.data[base..base + len * 4].chunks_exact(4)) { for (d, s) in dst.iter_mut().zip(t.data[base..base + len * 4].as_chunks::<4>().0) {
*d = [s[0], s[1], s[2], s[3]]; *d = [s[0], s[1], s[2], s[3]];
} }
} }

View File

@ -206,7 +206,7 @@ impl Parser<'_> {
fn decode_string(raw: &[u8]) -> String { fn decode_string(raw: &[u8]) -> String {
if raw.len() >= 2 && raw[0] == 0xFE && raw[1] == 0xFF { if raw.len() >= 2 && raw[0] == 0xFE && raw[1] == 0xFF {
let u: Vec<u16> = raw[2..].chunks_exact(2).map(|c| u16::from_be_bytes([c[0], c[1]])).collect(); let u: Vec<u16> = raw[2..].as_chunks::<2>().0.iter().map(|c| u16::from_be_bytes([c[0], c[1]])).collect();
String::from_utf16_lossy(&u) String::from_utf16_lossy(&u)
} else { } else {
raw.iter().map(|&c| c as char).collect() raw.iter().map(|&c| c as char).collect()

View File

@ -192,9 +192,9 @@ pub fn write_style_sheets(tysh: &[u8], layer: &TextLayer, styles: &TextStyles, d
} }
} }
let raw = crate::engine_data::write(&e); let raw = crate::engine_data::write(&e);
match t.text.items.iter_mut().find(|(k, _)| k.is("EngineData")) { {
Some(item) => item.1 = photocraft_psd::descriptor::Value::RawData(raw), let item = t.text.items.iter_mut().find(|(k, _)| k.is("EngineData"))?;
None => return None, item.1 = photocraft_psd::descriptor::Value::RawData(raw)
} }
Some(write_tysh(&t)) Some(write_tysh(&t))
} }

View File

@ -272,12 +272,12 @@ fn multicolor_runs() {
let (l, r) = e.render(&t, 72.0, PixelFormat::RGBA8); let (l, r) = e.render(&t, 72.0, PixelFormat::RGBA8);
assert_eq!(l.glyphs.len(), 2); assert_eq!(l.glyphs.len(), 2);
let px = r.surface.read_region(r.rect); let px = r.surface.read_region(r.rect);
let opaque: Vec<&[f32]> = px.chunks_exact(4).filter(|p| p[3] > 0.99).collect(); let opaque: Vec<&[f32; 4]> = px.as_chunks::<4>().0.iter().filter(|p| p[3] > 0.99).collect();
assert!(opaque.iter().any(|p| p[0] > 0.99 && p[2] < 0.01)); assert!(opaque.iter().any(|p| p[0] > 0.99 && p[2] < 0.01));
assert!(opaque.iter().any(|p| p[2] > 0.99 && p[0] < 0.01)); assert!(opaque.iter().any(|p| p[2] > 0.99 && p[0] < 0.01));
// CMYK target keeps colour in the document model. // CMYK target keeps colour in the document model.
let (_, rc) = e.render(&t, 72.0, PixelFormat::CMYKA8); let (_, rc) = e.render(&t, 72.0, PixelFormat::CMYKA8);
assert!(rc.surface.read_region(rc.rect).chunks_exact(5).any(|p| p[4] > 0.99 && p[1] > 0.9 && p[2] > 0.9)); assert!(rc.surface.read_region(rc.rect).as_chunks::<5>().0.iter().any(|p| p[4] > 0.99 && p[1] > 0.9 && p[2] > 0.9));
} }
#[test] #[test]

View File

@ -481,7 +481,7 @@ mod tests {
let mut b = BrushSettings { size: 400.0, ..Default::default() }; let mut b = BrushSettings { size: 400.0, ..Default::default() };
let px = preview_pixels(&b, 120, 40, [1.0, 0.0, 0.0, 1.0]); let px = preview_pixels(&b, 120, 40, [1.0, 0.0, 0.0, 1.0]);
assert_eq!(px.len(), 120 * 40 * 4); assert_eq!(px.len(), 120 * 40 * 4);
let covered = px.chunks_exact(4).filter(|p| p[3] > 128).count(); let covered = px.as_chunks::<4>().0.iter().filter(|p| p[3] > 128).count();
assert!(covered > 200 && covered < 120 * 40 / 2, "{covered}"); assert!(covered > 200 && covered < 120 * 40 / 2, "{covered}");
// Dynamics change the preview. // Dynamics change the preview.
b.scattering.enabled = true; b.scattering.enabled = true;

View File

@ -341,7 +341,7 @@ impl Perf {
/// Straight-alpha f32 RGBA (sRGB-encoded) → premultiplied RGBA8, split across threads on native. /// Straight-alpha f32 RGBA (sRGB-encoded) → premultiplied RGBA8, split across threads on native.
pub fn premultiply_rgba8(px: &[[f32; 4]]) -> Vec<u8> { pub fn premultiply_rgba8(px: &[[f32; 4]]) -> Vec<u8> {
fn convert(src: &[[f32; 4]], dst: &mut [u8]) { fn convert(src: &[[f32; 4]], dst: &mut [u8]) {
for (o, p) in dst.chunks_exact_mut(4).zip(src) { for (o, p) in dst.as_chunks_mut::<4>().0.iter_mut().zip(src) {
let a = p[3].clamp(0.0, 1.0); let a = p[3].clamp(0.0, 1.0);
o[0] = (p[0].clamp(0.0, 1.0) * a * 255.0 + 0.5) as u8; o[0] = (p[0].clamp(0.0, 1.0) * a * 255.0 + 0.5) as u8;
o[1] = (p[1].clamp(0.0, 1.0) * a * 255.0 + 0.5) as u8; o[1] = (p[1].clamp(0.0, 1.0) * a * 255.0 + 0.5) as u8;

View File

@ -686,7 +686,7 @@ fn draw_mesh(d: &LiquifyDialog, painter: &egui::Painter, area: ERect) {
pts.push([pa[0] + (pb[0] - pa[0]) * t, pa[1] + (pb[1] - pa[1]) * t]); pts.push([pa[0] + (pb[0] - pa[0]) * t, pa[1] + (pb[1] - pa[1]) * t]);
} }
} }
for seg in pts.chunks_exact(2) { for seg in pts.as_chunks::<2>().0 {
painter.line_segment([to_screen(d, area, seg[0]), to_screen(d, area, seg[1])], stroke); painter.line_segment([to_screen(d, area, seg[0]), to_screen(d, area, seg[1])], stroke);
} }
k += 1; k += 1;

View File

@ -90,9 +90,7 @@ pub fn show(app: &mut PhotocraftApp, ctx: &egui::Context) {
s, s,
m.id, m.id,
m.label.trim_end_matches('…').to_string(), m.label.trim_end_matches('…').to_string(),
Some(path) Some(path + &m.shortcut.map(|s| format!(" {}", crate::shortcuts::pretty(&s))).unwrap_or_default()),
.filter(|_| true)
.map(|p| p + &m.shortcut.map(|s| format!(" {}", crate::shortcuts::pretty(&s))).unwrap_or_default()),
m.enabled, m.enabled,
)) ))
}) })

View File

@ -26,7 +26,7 @@ fn compute_histograms(doc: &Document, hide: LayerId) -> Histograms {
} }
let img = photocraft_compose::thumbnail(&d, 384); let img = photocraft_compose::thumbnail(&d, 384);
let mut h = [[0u32; 256]; 4]; let mut h = [[0u32; 256]; 4];
for p in img.pixels.chunks_exact(4) { for p in img.pixels.as_chunks::<4>().0 {
if p[3] == 0 { if p[3] == 0 {
continue; continue;
} }