///|
pub fn GrayImage::translate(
  self : GrayImage,
  dx : Int,
  dy : Int,
  fill : Gray,
) -> GrayImage {
  let out = GrayImage::new(width=self.width, height=self.height, fill~)
  for y in 0.. GrayImage {
  let out = GrayImage::new(width=self.height, height=self.width)
  for y in 0.. GrayImage {
  let out = GrayImage::new(width=self.height, height=self.width)
  for y in 0.. GrayImage {
  let normalized = (turns % 4 + 4) % 4
  if normalized == 0 {
    self.copy()
  } else if normalized == 1 {
    self.rotate90_cw()
  } else if normalized == 2 {
    self.rotate180()
  } else {
    self.rotate90_ccw()
  }
}

///|
pub fn GrayImage::multiply_contrast(
  self : GrayImage,
  numerator : Int,
  denominator : Int,
) -> GrayImage {
  let den = if denominator == 0 { 1 } else { denominator }
  self.map(fn(value) { value * numerator / den })
}

///|
pub fn GrayImage::add_brightness(self : GrayImage, delta : Int) -> GrayImage {
  self.map(fn(value) { value + delta })
}

///|
pub fn GrayImage::posterize(self : GrayImage, levels : Int) -> GrayImage {
  let n = max_i(2, levels)
  self.map(fn(value) { value / 256 * 256 / n * n + 255 / n })
}

///|
pub fn GrayImage::quantize(self : GrayImage, levels : Int) -> GrayImage {
  let n = max_i(2, levels)
  self.map(fn(value) { value * (n - 1) / 255 * 255 / (n - 1) })
}

///|
pub fn GrayImage::erode(self : GrayImage, radius : Int) -> GrayImage {
  let r = max_i(0, radius)
  let out = GrayImage::new(width=self.width, height=self.height)
  for y in 0.. GrayImage {
  let r = max_i(0, radius)
  let out = GrayImage::new(width=self.width, height=self.height)
  for y in 0.. GrayImage {
  self.erode(radius).dilate(radius)
}

///|
pub fn GrayImage::close(self : GrayImage, radius : Int) -> GrayImage {
  self.dilate(radius).erode(radius)
}

///|
pub fn GrayImage::sobel_x(self : GrayImage) -> GrayImage {
  let out = GrayImage::new(width=self.width, height=self.height)
  for y in 1..<(self.height - 1) {
    for x in 1..<(self.width - 1) {
      let value = -self.get(x - 1, y - 1).value -
        2 * self.get(x - 1, y).value -
        self.get(x - 1, y + 1).value +
        self.get(x + 1, y - 1).value +
        2 * self.get(x + 1, y).value +
        self.get(x + 1, y + 1).value
      out.set(x, y, Gray::new(abs_i(value)))
    }
  }
  out
}

///|
pub fn GrayImage::sobel_y(self : GrayImage) -> GrayImage {
  let out = GrayImage::new(width=self.width, height=self.height)
  for y in 1..<(self.height - 1) {
    for x in 1..<(self.width - 1) {
      let value = -self.get(x - 1, y - 1).value -
        2 * self.get(x, y - 1).value -
        self.get(x + 1, y - 1).value +
        self.get(x - 1, y + 1).value +
        2 * self.get(x, y + 1).value +
        self.get(x + 1, y + 1).value
      out.set(x, y, Gray::new(abs_i(value)))
    }
  }
  out
}

///|
pub fn GrayImage::edge_map(self : GrayImage, threshold : Int) -> GrayImage {
  let gx = self.sobel_x()
  let gy = self.sobel_y()
  let out = GrayImage::new(width=self.width, height=self.height)
  for y in 0..= threshold {
          Gray::white()
        } else {
          Gray::black()
        },
      )
    }
  }
  out
}

///|
pub fn GrayImage::warp_affine_integer(
  self : GrayImage,
  offset : Point,
  fill : Gray,
) -> GrayImage {
  self.translate(offset.x, offset.y, fill)
}

///|
pub fn transform_boxes(
  boxes : Array[Box2D],
  offset : Point,
  size : Size,
) -> Array[Box2D] {
  let out : Array[Box2D] = []
  for box in boxes {
    out.push(
      box.clip(size).rect
      .translate(offset.x, offset.y)
      .to_box(box.label, box.occluded)
      .clip(size),
    )
  }
  out
}

///|
fn Rect::to_box(self : Rect, label : Label, occluded : Bool) -> Box2D {
  Box2D::new(label~, rect=self, occluded~)
}