Files
vectorcraft/crates/trace/src/mosaic.rs
T

57 lines
2.4 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Object → Create Object Mosaic: average colours of a grid of tiles over a raster.
use crate::Raster;
/// Average colour (straight RGBA) of each of `cols` × `rows` tiles covering `img`, row by row.
/// Transparent pixels count by their alpha, so a tile over transparency fades out.
pub fn mosaic(img: &Raster, cols: u32, rows: u32) -> Vec<[u8; 4]> {
let (cols, rows) = (cols.max(1), rows.max(1));
let mut out = Vec::with_capacity((cols * rows) as usize);
for r in 0..rows {
let y0 = (r as u64 * img.height as u64 / rows as u64) as u32;
let y1 = (((r + 1) as u64 * img.height as u64 / rows as u64) as u32).max(y0 + 1).min(img.height);
for c in 0..cols {
let x0 = (c as u64 * img.width as u64 / cols as u64) as u32;
let x1 = (((c + 1) as u64 * img.width as u64 / cols as u64) as u32).max(x0 + 1).min(img.width);
let (mut sr, mut sg, mut sb, mut sa, mut n) = (0u64, 0u64, 0u64, 0u64, 0u64);
for y in y0..y1.max(y0 + 1).min(img.height) {
for x in x0..x1.max(x0 + 1).min(img.width) {
let [pr, pg, pb, pa] = img.pixel(x, y);
let a = pa as u64;
sr += pr as u64 * a;
sg += pg as u64 * a;
sb += pb as u64 * a;
sa += a;
n += 1;
}
}
out.push(match (sr.checked_div(sa), sg.checked_div(sa), sb.checked_div(sa)) {
(Some(r), Some(g), Some(b)) => [r as u8, g as u8, b as u8, (sa / n.max(1)) as u8],
_ => [0, 0, 0, 0],
});
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn averages_each_tile() {
// Left half red, right half blue; a 2 × 1 mosaic gives one tile of each.
let img = Raster::from_fn(10, 4, |x, _| if x < 5 { [255, 0, 0, 255] } else { [0, 0, 255, 255] });
assert_eq!(mosaic(&img, 2, 1), vec![[255, 0, 0, 255], [0, 0, 255, 255]]);
// One tile: the average, purple.
let m = mosaic(&img, 1, 1);
assert_eq!(m[0][3], 255);
assert!((m[0][0] as i32 - 127).abs() <= 1 && (m[0][2] as i32 - 127).abs() <= 1);
// More tiles than pixels still works.
assert_eq!(mosaic(&img, 20, 8).len(), 160);
// Transparency.
let clear = Raster::from_fn(2, 2, |_, _| [9, 9, 9, 0]);
assert_eq!(mosaic(&clear, 1, 1), vec![[0, 0, 0, 0]]);
}
}