///|
/// Zero-copy traversal helpers for bounded image processing.
///
/// `Image::for_each_tile` visits rectangular views over the source image. A
/// view only stores its origin and dimensions; it does not copy pixel bytes.
/// Call `ImageTile::copy` when an owned image is required. This makes the
/// traversal suitable for streaming decoders and bounded-memory processing.

/// A read/write view over one rectangular tile of an image.
pub(all) struct ImageTile {
  image : Image
  origin_x : Int
  origin_y : Int
  tile_width : Int
  tile_height : Int
}

/// Horizontal origin of this tile in the source image.
pub fn ImageTile::x(self : ImageTile) -> Int {
  self.origin_x
}

/// Vertical origin of this tile in the source image.
pub fn ImageTile::y(self : ImageTile) -> Int {
  self.origin_y
}

/// Width of this tile in pixels.
pub fn ImageTile::width(self : ImageTile) -> Int {
  self.tile_width
}

/// Height of this tile in pixels.
pub fn ImageTile::height(self : ImageTile) -> Int {
  self.tile_height
}

/// Reads a pixel relative to the tile origin.
pub fn ImageTile::get_pixel(
  self : ImageTile,
  x : Int,
  y : Int,
) -> (Byte, Byte, Byte, Byte) {
  if x < 0 || x >= self.tile_width || y < 0 || y >= self.tile_height {
    abort("ImageTile: pixel coordinates are out of bounds")
  }
  self.image.get_pixel(self.origin_x + x, self.origin_y + y)
}

/// Writes a pixel relative to the tile origin without copying the tile.
pub fn ImageTile::set_pixel(
  self : ImageTile,
  x : Int,
  y : Int,
  r : Byte,
  g : Byte,
  b : Byte,
  a : Byte,
) -> Unit {
  if x < 0 || x >= self.tile_width || y < 0 || y >= self.tile_height {
    abort("ImageTile: pixel coordinates are out of bounds")
  }
  self.image.set_pixel(self.origin_x + x, self.origin_y + y, r, g, b, a)
}

/// Copies the tile into an independently-owned image.
pub fn ImageTile::copy(self : ImageTile) -> Image {
  let out = Image::new(self.tile_width, self.tile_height)
  for y in 0.. Unit,
) -> Unit {
  if tile_width <= 0 || tile_height <= 0 {
    abort("Image::for_each_tile: tile dimensions must be positive")
  }
  for origin_y = 0; origin_y < self.height; origin_y = origin_y + tile_height {
    let h = if origin_y + tile_height > self.height {
      self.height - origin_y
    } else {
      tile_height
    }
    for origin_x = 0; origin_x < self.width; origin_x = origin_x + tile_width {
      let w = if origin_x + tile_width > self.width {
        self.width - origin_x
      } else {
        tile_width
      }
      visit({
        image: self,
        origin_x,
        origin_y,
        tile_width: w,
        tile_height: h,
      })
    }
  }
}

/// A zero-copy view of one image row for streaming scans.
pub(all) struct ImageRow {
  image : Image
  row_y : Int
}

/// Y coordinate of this row in the source image.
pub fn ImageRow::y(self : ImageRow) -> Int {
  self.row_y
}

/// Number of pixels in this row.
pub fn ImageRow::width(self : ImageRow) -> Int {
  self.image.width
}

/// Reads a pixel from this row.
pub fn ImageRow::get_pixel(self : ImageRow, x : Int) -> (Byte, Byte, Byte, Byte) {
  if x < 0 || x >= self.image.width {
    abort("ImageRow: pixel coordinate is out of bounds")
  }
  self.image.get_pixel(x, self.row_y)
}

/// Visits rows in order without allocating row buffers.
pub fn Image::for_each_row(
  self : Image,
  visit : (ImageRow) -> Unit,
) -> Unit {
  for row_y in 0..