#![allow(clippy::cast_precision_loss)]
#![allow(clippy::cast_possible_truncation)]
#![allow(clippy::cast_sign_loss)]
use rayon::prelude::*;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Tile {
pub x: u32,
pub y: u32,
pub width: u32,
pub height: u32,
}
impl Tile {
pub fn new(x: u32, y: u32, width: u32, height: u32) -> Self {
Self {
x,
y,
width,
height,
}
}
pub fn area(&self) -> u64 {
self.width as u64 * self.height as u64
}
pub fn contains(&self, px: u32, py: u32) -> bool {
px >= self.x
&& px < self.x.saturating_add(self.width)
&& py >= self.y
&& py < self.y.saturating_add(self.height)
}
pub fn overlaps(&self, other: &Tile) -> bool {
let self_right = self.x.saturating_add(self.width);
let self_bottom = self.y.saturating_add(self.height);
let other_right = other.x.saturating_add(other.width);
let other_bottom = other.y.saturating_add(other.height);
self.x < other_right
&& self_right > other.x
&& self.y < other_bottom
&& self_bottom > other.y
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TileGrid {
pub cols: u32,
pub rows: u32,
pub tile_w: u32,
pub tile_h: u32,
}
impl TileGrid {
pub fn new(img_w: u32, img_h: u32, tile_size: u32) -> Self {
let tile_size = tile_size.max(1);
let cols = img_w.div_ceil(tile_size);
let rows = img_h.div_ceil(tile_size);
Self {
cols,
rows,
tile_w: tile_size,
tile_h: tile_size,
}
}
pub fn tile_count(&self) -> u32 {
self.cols * self.rows
}
pub fn tile_at(&self, col: u32, row: u32) -> Tile {
Tile {
x: col * self.tile_w,
y: row * self.tile_h,
width: self.tile_w,
height: self.tile_h,
}
}
pub fn tile_for_pixel(&self, px: u32, py: u32) -> Option<Tile> {
let col = px / self.tile_w;
let row = py / self.tile_h;
if col < self.cols && row < self.rows {
Some(self.tile_at(col, row))
} else {
None
}
}
}
#[inline]
fn bilinear_pixel(
src: &[u8],
src_w: u32,
src_h: u32,
dst_x: u32,
dst_y: u32,
dst_w: u32,
dst_h: u32,
channels: u8,
) -> [u8; 4] {
let ch = channels as usize;
let sw = src_w as f64;
let sh = src_h as f64;
let dw = dst_w as f64;
let dh = dst_h as f64;
let sx = (dst_x as f64 + 0.5) * sw / dw - 0.5;
let sy = (dst_y as f64 + 0.5) * sh / dh - 0.5;
let sx = sx.max(0.0).min(sw - 1.0);
let sy = sy.max(0.0).min(sh - 1.0);
let x0 = sx.floor() as usize;
let y0 = sy.floor() as usize;
let x1 = (x0 + 1).min(src_w as usize - 1);
let y1 = (y0 + 1).min(src_h as usize - 1);
let fx = sx - sx.floor();
let fy = sy - sy.floor();
let wx0 = 1.0 - fx;
let wx1 = fx;
let wy0 = 1.0 - fy;
let wy1 = fy;
let sw_usize = src_w as usize;
let mut out = [0u8; 4];
for c in 0..ch {
let v00 = src[(y0 * sw_usize + x0) * ch + c] as f64;
let v10 = src[(y0 * sw_usize + x1) * ch + c] as f64;
let v01 = src[(y1 * sw_usize + x0) * ch + c] as f64;
let v11 = src[(y1 * sw_usize + x1) * ch + c] as f64;
let v = wy0 * (wx0 * v00 + wx1 * v10) + wy1 * (wx0 * v01 + wx1 * v11);
out[c] = v.round().clamp(0.0, 255.0) as u8;
}
out
}
pub fn scale_tiled(
src: &[u8],
src_w: u32,
src_h: u32,
dst_w: u32,
dst_h: u32,
channels: u8,
tile_size: u32,
) -> Vec<u8> {
if src_w == 0 || src_h == 0 || dst_w == 0 || dst_h == 0 || channels == 0 {
return Vec::new();
}
let ch = channels as usize;
let expected = src_w as usize * src_h as usize * ch;
if src.len() < expected {
return Vec::new();
}
let tile_size = tile_size.max(1);
let grid = TileGrid::new(dst_w, dst_h, tile_size);
let n_tiles = grid.tile_count() as usize;
let dst_stride = dst_w as usize * ch;
let mut output: Vec<u8> = vec![0u8; dst_w as usize * dst_h as usize * ch];
let tile_indices: Vec<(u32, u32)> = (0..n_tiles as u32)
.map(|i| (i % grid.cols, i / grid.cols))
.collect();
let tile_results: Vec<(u32, u32, u32, u32, Vec<u8>)> = tile_indices
.par_iter()
.map(|&(col, row)| {
let tile = grid.tile_at(col, row);
let tx_end = (tile.x + tile.width).min(dst_w);
let ty_end = (tile.y + tile.height).min(dst_h);
let tw = tx_end.saturating_sub(tile.x);
let th = ty_end.saturating_sub(tile.y);
if tw == 0 || th == 0 {
return (tile.x, tile.y, 0, 0, Vec::new());
}
let mut tile_buf = vec![0u8; tw as usize * th as usize * ch];
for ty in 0..th {
for tx in 0..tw {
let dst_x = tile.x + tx;
let dst_y = tile.y + ty;
let pixel =
bilinear_pixel(src, src_w, src_h, dst_x, dst_y, dst_w, dst_h, channels);
let off = (ty as usize * tw as usize + tx as usize) * ch;
tile_buf[off..off + ch].copy_from_slice(&pixel[..ch]);
}
}
(tile.x, tile.y, tw, th, tile_buf)
})
.collect();
for (tx, ty, tw, th, tile_buf) in tile_results {
if tw == 0 || th == 0 {
continue;
}
for row in 0..th as usize {
let dst_row_off = (ty as usize + row) * dst_stride + tx as usize * ch;
let src_row_off = row * tw as usize * ch;
let len = tw as usize * ch;
output[dst_row_off..dst_row_off + len]
.copy_from_slice(&tile_buf[src_row_off..src_row_off + len]);
}
}
output
}
pub fn scale_reference(
src: &[u8],
src_w: u32,
src_h: u32,
dst_w: u32,
dst_h: u32,
channels: u8,
) -> Vec<u8> {
if src_w == 0 || src_h == 0 || dst_w == 0 || dst_h == 0 || channels == 0 {
return Vec::new();
}
let ch = channels as usize;
let expected = src_w as usize * src_h as usize * ch;
if src.len() < expected {
return Vec::new();
}
let mut output = vec![0u8; dst_w as usize * dst_h as usize * ch];
let dst_stride = dst_w as usize * ch;
for dy in 0..dst_h {
for dx in 0..dst_w {
let pixel = bilinear_pixel(src, src_w, src_h, dx, dy, dst_w, dst_h, channels);
let off = dy as usize * dst_stride + dx as usize * ch;
output[off..off + ch].copy_from_slice(&pixel[..ch]);
}
}
output
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tile_new() {
let t = Tile::new(10, 20, 64, 64);
assert_eq!(t.x, 10);
assert_eq!(t.y, 20);
assert_eq!(t.width, 64);
assert_eq!(t.height, 64);
}
#[test]
fn test_tile_area() {
let t = Tile::new(0, 0, 100, 200);
assert_eq!(t.area(), 20_000);
}
#[test]
fn test_tile_area_large() {
let t = Tile::new(0, 0, 65535, 65535);
assert_eq!(t.area(), 65535u64 * 65535u64);
}
#[test]
fn test_tile_contains_inside() {
let t = Tile::new(10, 10, 20, 20);
assert!(t.contains(15, 15));
assert!(t.contains(10, 10));
}
#[test]
fn test_tile_contains_outside() {
let t = Tile::new(10, 10, 20, 20);
assert!(!t.contains(30, 15)); assert!(!t.contains(5, 15)); assert!(!t.contains(15, 5)); }
#[test]
fn test_tile_contains_bottom_right() {
let t = Tile::new(0, 0, 8, 8);
assert!(t.contains(7, 7));
assert!(!t.contains(8, 7));
assert!(!t.contains(7, 8));
}
#[test]
fn test_tile_overlaps_true() {
let a = Tile::new(0, 0, 10, 10);
let b = Tile::new(5, 5, 10, 10);
assert!(a.overlaps(&b));
assert!(b.overlaps(&a));
}
#[test]
fn test_tile_overlaps_adjacent_no_overlap() {
let a = Tile::new(0, 0, 10, 10);
let b = Tile::new(10, 0, 10, 10); assert!(!a.overlaps(&b));
}
#[test]
fn test_tile_overlaps_self() {
let t = Tile::new(5, 5, 10, 10);
assert!(t.overlaps(&t));
}
#[test]
fn test_tile_overlaps_disjoint() {
let a = Tile::new(0, 0, 5, 5);
let b = Tile::new(100, 100, 5, 5);
assert!(!a.overlaps(&b));
}
#[test]
fn test_grid_new() {
let g = TileGrid::new(100, 100, 32);
assert_eq!(g.cols, 4); assert_eq!(g.rows, 4);
assert_eq!(g.tile_w, 32);
assert_eq!(g.tile_h, 32);
}
#[test]
fn test_grid_tile_count() {
let g = TileGrid::new(64, 64, 16);
assert_eq!(g.tile_count(), 16); }
#[test]
fn test_grid_tile_count_non_divisible() {
let g = TileGrid::new(100, 100, 32);
assert_eq!(g.tile_count(), 16);
}
#[test]
fn test_grid_tile_at() {
let g = TileGrid::new(256, 256, 64);
let t = g.tile_at(1, 2);
assert_eq!(t.x, 64);
assert_eq!(t.y, 128);
assert_eq!(t.width, 64);
assert_eq!(t.height, 64);
}
#[test]
fn test_grid_tile_for_pixel_found() {
let g = TileGrid::new(256, 256, 64);
let t = g.tile_for_pixel(70, 130);
assert!(t.is_some());
let t = t.expect("should succeed in test");
assert_eq!(t.x, 64);
assert_eq!(t.y, 128);
}
#[test]
fn test_grid_tile_for_pixel_out_of_range() {
let g = TileGrid::new(100, 100, 32);
let t = g.tile_for_pixel(200, 50);
assert!(t.is_none());
}
#[test]
fn test_grid_zero_size_image() {
let g = TileGrid::new(0, 0, 32);
assert_eq!(g.tile_count(), 0);
}
fn checkerboard_rgb(w: u32, h: u32, square: u32) -> Vec<u8> {
let mut buf = Vec::with_capacity(w as usize * h as usize * 3);
for y in 0..h {
for x in 0..w {
let on = ((x / square) + (y / square)) % 2 == 0;
let v = if on { 255u8 } else { 0u8 };
buf.push(v);
buf.push(v);
buf.push(v);
}
}
buf
}
#[test]
fn test_scale_tiled_bitexact_downscale() {
let src = checkerboard_rgb(256, 256, 32);
let tiled = scale_tiled(&src, 256, 256, 64, 64, 3, 16);
let reference = scale_reference(&src, 256, 256, 64, 64, 3);
assert_eq!(tiled.len(), reference.len());
assert_eq!(
tiled, reference,
"tiled downscale must be bit-exact vs reference"
);
}
#[test]
fn test_scale_tiled_bitexact_upscale() {
let src = checkerboard_rgb(64, 64, 8);
let tiled = scale_tiled(&src, 64, 64, 128, 128, 3, 32);
let reference = scale_reference(&src, 64, 64, 128, 128, 3);
assert_eq!(tiled.len(), reference.len());
assert_eq!(
tiled, reference,
"tiled upscale must be bit-exact vs reference"
);
}
#[test]
fn test_scale_tiled_bitexact_rgba() {
let src: Vec<u8> = (0u8..=255).cycle().take(64 * 64 * 4).collect();
let tiled = scale_tiled(&src, 64, 64, 32, 32, 4, 16);
let reference = scale_reference(&src, 64, 64, 32, 32, 4);
assert_eq!(tiled, reference, "RGBA tiled must match reference");
}
#[test]
fn test_scale_tiled_edge_tiles() {
let src = checkerboard_rgb(100, 100, 10);
let tiled = scale_tiled(&src, 100, 100, 100, 100, 3, 32);
let reference = scale_reference(&src, 100, 100, 100, 100, 3);
assert_eq!(
tiled.len(),
100 * 100 * 3,
"output size must be dst_w * dst_h * channels"
);
assert_eq!(tiled, reference, "edge-tile result must match reference");
}
#[test]
fn test_scale_tiled_zero_size() {
let empty: &[u8] = &[];
let r1 = scale_tiled(empty, 0, 0, 64, 64, 3, 32);
assert!(r1.is_empty(), "zero-size src → empty result");
let src = checkerboard_rgb(64, 64, 8);
let r2 = scale_tiled(&src, 64, 64, 0, 64, 3, 32);
assert!(r2.is_empty(), "zero dst_w → empty result");
let r3 = scale_tiled(&src, 64, 64, 64, 0, 3, 32);
assert!(r3.is_empty(), "zero dst_h → empty result");
}
#[test]
fn test_scale_tiled_single_pixel_tile() {
let src = checkerboard_rgb(32, 32, 4);
let tiled = scale_tiled(&src, 32, 32, 16, 16, 3, 1);
let reference = scale_reference(&src, 32, 32, 16, 16, 3);
assert_eq!(tiled, reference, "tile_size=1 must match reference");
}
#[test]
fn test_scale_tiled_output_size() {
let src = vec![128u8; 64 * 64 * 3];
let out = scale_tiled(&src, 64, 64, 128, 128, 3, 64);
assert_eq!(out.len(), 128 * 128 * 3);
}
}