Files
DarkRoom/third_party/rawler-0.7.2/src/tiles.rs
T
dtourolle 77a1925bac Vendor rawler 0.7.2 unmodified
The crate as crates.io publishes it, minus .cargo-ok, its Cargo.lock and
data/testdata (13 MB of sample files only its own tests read). Not yet
routed through [patch.crates-io]; the next commit is the patch.
2026-09-27 17:33:19 -04:00

395 lines
11 KiB
Rust

// SPDX-License-Identifier: LGPL-2.1
// Copyright 2021 Daniel Vogelbacher <daniel@chaospixel.com>
use std::{iter, ops::Range};
use crate::pixarray::{LineMut, SubPixel};
#[derive(Debug)]
pub struct ErrorNotTileable;
/// Image tile generator
pub struct ImageTiler<'a, T> {
data: &'a [T],
width: usize,
#[allow(dead_code)]
height: usize,
cpp: usize,
tiles: Range<usize>,
tw: usize,
th: usize,
tcols: usize,
trows: usize,
}
impl<'a, T> ImageTiler<'a, T> {
pub fn new(data: &'a [T], width: usize, height: usize, cpp: usize, tw: usize, th: usize) -> Self {
assert!(data.len() >= height * width * cpp);
let tcols = width.div_ceil(tw);
let trows = height.div_ceil(th);
Self {
data,
width,
height,
cpp,
tiles: Range { start: 0, end: trows * tcols },
tw,
th,
tcols,
trows,
}
}
pub fn tile_cols(&self) -> usize {
self.tcols
}
pub fn tile_rows(&self) -> usize {
self.trows
}
pub fn tile_count(&self) -> usize {
self.tile_rows() * self.tile_cols()
}
fn needs_padding(&self) -> bool {
self.width % self.tw > 0
}
}
impl<'a, T> Iterator for ImageTiler<'a, T>
where
T: Copy + Default,
{
type Item = Vec<T>;
fn next(&mut self) -> Option<Self::Item> {
if let Some(i) = self.tiles.next() {
let mut buf = Vec::with_capacity(self.th * self.tw * self.cpp);
let tile_row = i / self.tile_cols();
let tile_col = i % self.tile_cols();
//println!("Tile row: {}, col: {}", tile_row, tile_col);
for row in 0..self.th {
let off_row = (tile_row * self.th) + row;
let offset = off_row * self.width * self.cpp + (tile_col * self.tw * self.cpp);
if offset < self.data.len() {
//println!("Fill row: {}", row);
if tile_col < self.tile_cols() - 1 || !self.needs_padding() {
let sub = &self.data[offset..offset + self.tw * self.cpp];
buf.extend_from_slice(sub);
} else {
buf.extend_from_slice(&self.data[offset..offset + (self.width % self.tw) * self.cpp]);
let last_pix = buf.last().copied().unwrap_or_default();
buf.extend(iter::repeat(last_pix).take((self.tw - (self.width % self.tw)) * self.cpp));
};
} else {
//println!("extend row: {}", row);
buf.extend_from_within((row - 1) * self.tw * self.cpp..((row - 1) * self.tw * self.cpp) + self.tw * self.cpp);
}
}
Some(buf)
} else {
None
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.tile_count(), Some(self.tile_count()))
}
}
/// A trait for types that can be partitioned into mutable tiles.
///
/// This trait allows splitting a collection or buffer of subpixels into
/// mutable tiles of specified dimensions, returning an iterator over the tiles.
///
/// # Type Parameters
/// - `'a`: Lifetime of the data being tiled.
/// - `T`: The subpixel type, which must implement the `SubPixel` trait.
///
/// # Required Methods
/// - `into_tiles_iter_mut`: Consumes `self` and returns a mutable iterator over tiles,
/// or an error if the data cannot be tiled with the given dimensions.
///
/// # Errors
/// Returns `ErrorNotTileable` if the data cannot be partitioned into tiles
/// with the specified width, tile width, or tile height.
pub trait TilesMut<'a, T>
where
T: SubPixel,
{
fn into_tiles_iter_mut(self, width: usize, cpp: usize, tile_width: usize, tile_height: usize) -> std::result::Result<IntoTilesIter<'a, T>, ErrorNotTileable>;
}
/// Implementation for mutable slices of T: SubPixel
impl<'a, T> TilesMut<'a, T> for &'a mut [T]
where
T: SubPixel,
{
fn into_tiles_iter_mut(self, width: usize, cpp: usize, tile_width: usize, tile_height: usize) -> std::result::Result<IntoTilesIter<'a, T>, ErrorNotTileable> {
assert!(width > 0, "Width and height must be greater than zero");
assert!(tile_width * tile_height > 0, "Tile width and height must be greater than zero");
assert!(cpp > 0, "cpp must be greater than zero");
if !self.len().is_multiple_of(tile_width * cpp * tile_height) {
return Err(ErrorNotTileable);
}
Ok(IntoTilesIter {
count: 0,
width,
cpp,
tile_width,
tile_height,
original: self,
})
}
}
/// An iterator that splits a mutable slice into tiles of specified width and height.
///
/// # Type Parameters
/// - `T`: The type of elements in the slice.
///
/// # Fields
/// - `count`: The current tile index or count of tiles processed.
/// - `width`: The width of the original image or data slice.
/// - `tile_width`: The width of each tile.
/// - `tile_height`: The height of each tile.
/// - `original`: A mutable reference to the original data slice to be tiled.
///
/// This iterator yields mutable references to tiles within the original slice,
/// allowing for in-place modification of each tile.
pub struct IntoTilesIter<'a, T> {
count: usize,
width: usize,
cpp: usize,
tile_width: usize,
tile_height: usize,
original: &'a mut [T],
}
impl<'a, T> IntoTilesIter<'a, T> {
/// Returns the total tile count
fn tile_count(&self) -> usize {
self.original.len() / self.cpp / (self.tile_height * self.tile_width)
}
}
/// We know the exact amount of tiles that can be
/// produced, so we mark the iterator as ExactSizeIterator
impl<'a, T> ExactSizeIterator for IntoTilesIter<'a, T> where T: Send {}
// unsafe impl<'a, T> TrustedLen for IntoTilesIter<'a, T> where T: Send {}
/// A iterator that gives owned Tiles
impl<'a, T> Iterator for IntoTilesIter<'a, T> {
type Item = Tile<'a, T>;
fn next(&mut self) -> Option<Self::Item> {
let tile_cols = self.width / self.tile_width;
let _tile_rows = self.original.len() / self.cpp / (self.tile_height * self.width);
let tile_x = self.count % tile_cols;
let tile_y = self.count / tile_cols;
assert!(self.count <= self.tile_count());
let start_index = tile_x * (self.tile_width * self.cpp) + tile_y * (self.width * self.cpp) * self.tile_height;
self.count += 1;
if start_index >= self.original.len() {
return None;
} else {
// The next tile line has always a distance equal to full image width.
let next_line_distance = self.width * self.cpp;
let first_line_begin = &mut self.original[start_index..];
if first_line_begin.len() < self.tile_height * next_line_distance - (tile_x * self.tile_width * self.cpp) {
// The tile input buffer is too small. Maybe an issue with component-per-pixels?
panic!("Tile buffer too small.")
}
let first_line = &mut first_line_begin[..self.tile_width * self.cpp];
Some(Tile {
first_line,
tile_height: self.tile_height,
width: self.width,
cpp: self.cpp,
_phantom: std::marker::PhantomData,
})
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.tile_count() - self.count, Some(self.tile_count() - self.count))
}
}
/// Represents a rectangular tile within an image buffer.
///
/// # Type Parameters
/// - `'a`: Lifetime of the data the tile references.
/// - `T`: Pixel type contained in the tile.
///
/// # Fields
/// - `first_line`: Pointer to the first line (row) of the tile's pixel data.
/// - `tile_height`: Number of rows in the tile.
/// - `width`: Width of the entire pixel buffer (not just the tile).
/// - `_phantom`: Marker to associate the lifetime `'a` and type `[T]` with the struct.
pub struct Tile<'a, T> {
// contains tile_width as well
first_line: *mut [T],
tile_height: usize,
width: usize, // of the pixbuf
cpp: usize,
_phantom: std::marker::PhantomData<&'a [T]>,
}
impl<'a, T> Tile<'a, T> {
pub fn into_iter_mut(self) -> TileIterMut<'a, T> {
TileIterMut { tile: self, current_line: 0 }
}
}
// TODO: Add safety note
unsafe impl<T: Send> Send for Tile<'_, T> {}
/// An iterator that allows mutable access to the lines of a `Tile`.
///
/// # Type Parameters
/// * `T` - The type of the elements contained in the tile.
///
/// # Fields
/// * `tile` - The tile being iterated over.
/// * `current_line` - The index of the current line in the tile.
pub struct TileIterMut<'a, T> {
tile: Tile<'a, T>,
current_line: usize,
}
impl<'a, T> Iterator for TileIterMut<'a, T>
where
T: SubPixel + 'a,
{
type Item = LineMut<'a, T>;
fn next(&mut self) -> Option<Self::Item> {
if self.current_line >= self.tile.tile_height {
return None;
}
// Calculating the next line offset is easy - each tile has same width/height,
// so the distance is simply the full width of the image.
let next_line_distance = self.tile.width * self.tile.cpp;
let line_ptr = unsafe { (self.tile.first_line as *mut T).offset((self.current_line * next_line_distance) as isize) };
self.current_line += 1;
// This is safe because we check in the constructor if the line_ptr
// can be advanced until tile end line.
Some(unsafe { std::slice::from_raw_parts_mut(line_ptr, self.tile.first_line.len()) })
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.tile.tile_height - self.current_line, Some(self.tile.tile_height - self.current_line))
}
}
impl<'a, T> ExactSizeIterator for TileIterMut<'a, T> where T: SubPixel + 'a {}
#[cfg(test)]
mod tests {
use super::*;
use rayon::prelude::*;
#[test]
fn tile_1x1() -> std::result::Result<(), Box<dyn std::error::Error>> {
crate::init_test_logger();
let w = 1;
let h = 1;
let c = 3;
let buf = vec![0_u16; w * h * c];
let tiles: Vec<Vec<u16>> = ImageTiler::new(&buf, w, h, c, 20, 20).collect();
assert_eq!(tiles.len(), 1);
assert_eq!(tiles[0].len(), c * 20 * 20);
Ok(())
}
#[test]
fn test_par_bridge() {
#[rustfmt::skip]
let mut vec = vec![
1, 2, 3, 4, 5, 6,
7, 8, 9, 10, 11, 12,
13, 14, 15, 16, 17, 18,
19, 20, 21, 22, 23, 24,
];
let cpp = 1;
let tiles = vec.into_tiles_iter_mut(6, cpp, 2, 2);
assert!(tiles.is_ok());
let tiles = tiles.expect("Tiling failed");
tiles.par_bridge().for_each(|tile| {
tile.into_iter_mut().for_each(|line| {
for p in line {
*p *= 2;
}
});
});
}
#[test]
fn test_par_bridge_without_collect() {
#[rustfmt::skip]
let mut vec = vec![
1, 2, 3, 4, 5, 6,
7, 8, 9, 10, 11, 12,
13, 14, 15, 16, 17, 18,
19, 20, 21, 22, 23, 24,
];
let expected_vec: Vec<u16> = vec.iter().map(|p| p * 2).collect();
let cpp = 1;
let tiles = vec.into_tiles_iter_mut(6, cpp, 2, 2);
assert!(tiles.is_ok());
let tiles = tiles.expect("Tiling failed");
tiles.par_bridge().for_each(|tile| {
tile.into_iter_mut().for_each(|line| {
for p in line {
*p *= 2;
}
});
});
assert_eq!(vec, expected_vec);
}
#[test]
fn test_tiles_mut() {
#[rustfmt::skip]
let mut vec = vec![
1, 2, 3, 4, 5, 6,
7, 8, 9, 10, 11, 12,
13, 14, 15, 16, 17, 18,
19, 20, 21, 22, 23, 24,
];
let cpp = 1;
let tiles = vec.into_tiles_iter_mut(6, cpp, 2, 2);
assert!(tiles.is_ok());
let tiles = tiles.expect("Tiling failed");
let tiles: Vec<Tile<u16>> = tiles.collect();
assert_eq!(tiles.len(), 6);
}
}