///|
/// Smaller of two integers, kept local so the block/window filters stay
/// dependency-free.
fn imin(a : Int, b : Int) -> Int {
  if a < b {
    a
  } else {
    b
  }
}

///|
/// Sepia tone: the classic warm photographic look, using the widely used
/// Microsoft sepia coefficients scaled by 1000 for exact integer math.
pub fn Image::sepia(self : Image) -> Image {
  self.map_rgb(fn(r, g, b) {
    let nr = (393 * r + 769 * g + 189 * b) / 1000
    let ng = (349 * r + 686 * g + 168 * b) / 1000
    let nb = (272 * r + 534 * g + 131 * b) / 1000
    (nr, ng, nb)
  })
}

///|
/// Binary threshold: pixels whose luma is at least `level` become white, the
/// rest black. Useful for masks and high-contrast stylization.
pub fn Image::threshold(self : Image, level : Int) -> Image {
  self.map_rgb(fn(r, g, b) {
    let y = (299 * r + 587 * g + 114 * b) / 1000
    let v = if y >= level { 255 } else { 0 }
    (v, v, v)
  })
}

///|
/// Pixelate (mosaic): replaces each `block` x `block` tile with its average
/// color. Edge tiles are clipped to the image bounds.
pub fn Image::pixelate(self : Image, block : Int) -> Image {
  let size = if block < 1 { 1 } else { block }
  let out = Image::new(self.width, self.height)
  let blocks_y = (self.height + size - 1) / size
  let blocks_x = (self.width + size - 1) / size
  for byi in 0.. Byte {
  let vals = FixedArray::make(9, 0)
  let mut k = 0
  for dy in -1..<2 {
    for dx in -1..<2 {
      let sx = clamp_coord(x + dx, img.width)
      let sy = clamp_coord(y + dy, img.height)
      vals[k] = img.data[(sy * img.width + sx) * 4 + ch].to_int()
      k = k + 1
    }
  }
  for i in 1..<9 {
    let v = vals[i]
    let mut j = i - 1
    while j >= 0 && vals[j] > v {
      vals[j + 1] = vals[j]
      j = j - 1
    }
    vals[j + 1] = v
  }
  clamp_byte(vals[4])
}

///|
/// 3x3 median filter applied independently to R, G and B; alpha is preserved.
pub fn Image::median(self : Image) -> Image {
  let out = Image::new(self.width, self.height)
  for y in 0.. Image {
  let n = self.pixel_count()
  let hist = FixedArray::make(256, 0)
  for p in 0.. Image {
  let l = if levels < 2 { 2 } else if levels > 256 { 256 } else { levels }
  self.map_rgb(fn(r, g, b) {
    let q = fn(c : Int) -> Int { c * l / 256 * 255 / (l - 1) }
    (q(r), q(g), q(b))
  })
}