///|
pub enum ResampleFilter {
  Nearest
  Bilinear
  Area
} derive(Debug, Eq)

///|
pub struct ResampleConfig {
  filter : ResampleFilter
  fill : Gray
} derive(Debug, Eq)

///|
pub fn ResampleConfig::new(
  filter? : ResampleFilter = Bilinear,
  fill? : Gray = Gray::black(),
) -> ResampleConfig {
  { filter, fill }
}

///|
fn interpolate_gray(
  image : GrayImage,
  x1000 : Int,
  y1000 : Int,
  fill : Gray,
) -> Gray {
  if image.width <= 0 || image.height <= 0 {
    fill
  } else {
    let x = clamp(x1000, low=0, high=max_i(0, image.width * 1000 - 1))
    let y = clamp(y1000, low=0, high=max_i(0, image.height * 1000 - 1))
    let x0 = x / 1000
    let y0 = y / 1000
    let x1 = min_i(image.width - 1, x0 + 1)
    let y1 = min_i(image.height - 1, y0 + 1)
    let fx = x % 1000
    let fy = y % 1000
    let a = image.get(x0, y0).value
    let b = image.get(x1, y0).value
    let c = image.get(x0, y1).value
    let d = image.get(x1, y1).value
    let top = a * (1000 - fx) + b * fx
    let bottom = c * (1000 - fx) + d * fx
    Gray::new((top * (1000 - fy) + bottom * fy) / 1000000)
  }
}

///|
pub fn GrayImage::resize(
  self : GrayImage,
  size : Size,
  config? : ResampleConfig = ResampleConfig::new(),
) -> GrayImage {
  let out = GrayImage::new(
    width=max_i(0, size.width),
    height=max_i(0, size.height),
    fill=config.fill,
  )
  if out.width == 0 || out.height == 0 {
    out
  } else {
    for y in 0..
            self.get(
              clamp((sx + 500) / 1000, low=0, high=max_i(0, self.width - 1)),
              clamp((sy + 500) / 1000, low=0, high=max_i(0, self.height - 1)),
            )
          Bilinear => interpolate_gray(self, sx, sy, config.fill)
          Area => {
            let x0 = x * self.width / out.width
            let y0 = y * self.height / out.height
            let x1 = max_i(x0 + 1, (x + 1) * self.width / out.width)
            let y1 = max_i(y0 + 1, (y + 1) * self.height / out.height)
            let mut total = 0
            let mut count = 0
            for yy in y0.. GrayImage {
  self.resize(size, config=ResampleConfig::new(filter=Nearest))
}

///|
pub fn GrayImage::resize_bilinear(self : GrayImage, size : Size) -> GrayImage {
  self.resize(size, config=ResampleConfig::new(filter=Bilinear))
}

///|
pub fn GrayImage::crop_padded(
  self : GrayImage,
  region : Rect,
  fill? : Gray = Gray::black(),
) -> GrayImage {
  let out = GrayImage::new(
    width=max_i(0, region.width),
    height=max_i(0, region.height),
    fill~,
  )
  for y in 0.. GrayImage {
  let b = max_i(0, border)
  let out = GrayImage::new(
    width=self.width + b * 2,
    height=self.height + b * 2,
    fill~,
  )
  for y in 0.. GrayImage {
  let mut out = self.copy()
  for _ in 0.. Array[GrayImage] {
  let out : Array[GrayImage] = [self.copy()]
  let mut current = self.copy()
  for _ in 0.. Int {
  let width = min_i(a.width, b.width)
  let height = min_i(a.height, b.height)
  if width <= 0 || height <= 0 {
    0
  } else {
    let mut total = 0
    for y in 0.. Int {
  let width = min_i(a.width, b.width)
  let height = min_i(a.height, b.height)
  if width <= 0 || height <= 0 {
    0
  } else {
    let mut total = 0
    for y in 0.. Int {
  let mse = image_mse(a, b)
  if mse == 0 {
    100000
  } else {
    let ratio = 65025 * 1000 / mse
    let mut guess = 0
    for value in 0..<=100000 {
      if value * value <= ratio * 1000 {
        guess = value
      }
    }
    guess
  }
}

///|
pub fn GrayImage::match_template(
  self : GrayImage,
  template : GrayImage,
  stride : Int,
) -> Point {
  let step = max_i(1, stride)
  if template.width <= 0 ||
    template.height <= 0 ||
    template.width > self.width ||
    template.height > self.height {
    Point::new(x=-1, y=-1)
  } else {
    let mut best = 2147483647
    let mut result = Point::new(x=0, y=0)
    for y in 0..<(self.height - template.height + 1) {
      if y % step == 0 {
        for x in 0..<(self.width - template.width + 1) {
          if x % step == 0 {
            let mut error = 0
            for yy in 0.. RgbImage {
  let out = RgbImage::new(
    width=max_i(0, size.width),
    height=max_i(0, size.height),
  )
  if out.width > 0 && out.height > 0 {
    for y in 0.. RgbImage {
  let out = RgbImage::new(
    width=max_i(0, region.width),
    height=max_i(0, region.height),
  )
  for y in 0.. String {
  "{\"mae\":\{image_mae(expected, actual)},\"mse\":\{image_mse(expected, actual)},\"psnr_x1000\":\{image_psnr_x1000(expected, actual)}}"
}