///|
pub struct HsvPixel {
  hue : Int
  saturation : Int
  value : Int
} derive(Debug, Eq)

///|
pub fn HsvPixel::new(hue~ : Int, saturation~ : Int, value~ : Int) -> HsvPixel {
  {
    hue: (hue % 360 + 360) % 360,
    saturation: clamp(saturation, low=0, high=1000),
    value: clamp(value, low=0, high=255),
  }
}

///|
pub fn Rgb::luminance(self : Rgb) -> Int {
  (self.r * 299 + self.g * 587 + self.b * 114) / 1000
}

///|
pub fn Rgb::max_channel(self : Rgb) -> Int {
  max_i(self.r, max_i(self.g, self.b))
}

///|
pub fn Rgb::min_channel(self : Rgb) -> Int {
  min_i(self.r, min_i(self.g, self.b))
}

///|
pub fn Rgb::to_hsv(self : Rgb) -> HsvPixel {
  let high = self.max_channel()
  let low = self.min_channel()
  let delta = high - low
  let saturation = if high == 0 { 0 } else { delta * 1000 / high }
  let mut hue = 0
  if delta != 0 {
    if high == self.r {
      hue = 60 * (self.g - self.b) / delta
    } else if high == self.g {
      hue = 60 * (self.b - self.r) / delta + 120
    } else {
      hue = 60 * (self.r - self.g) / delta + 240
    }
  }
  HsvPixel::new(hue~, saturation~, value=high)
}

///|
pub fn hsv_to_rgb(hsv : HsvPixel) -> Rgb {
  let chroma = hsv.value * hsv.saturation / 1000
  let section = hsv.hue / 60
  let remainder = hsv.hue % 60
  let intermediate = chroma * (60 - abs_i(remainder * 2 - 60)) / 60
  let offset = hsv.value - chroma
  let rgb = if section == 0 {
    (chroma, intermediate, 0)
  } else if section == 1 {
    (intermediate, chroma, 0)
  } else if section == 2 {
    (0, chroma, intermediate)
  } else if section == 3 {
    (0, intermediate, chroma)
  } else if section == 4 {
    (intermediate, 0, chroma)
  } else {
    (chroma, 0, intermediate)
  }
  Rgb::new(r=rgb.0 + offset, g=rgb.1 + offset, b=rgb.2 + offset)
}

///|
pub fn RgbImage::map_channels(
  self : RgbImage,
  red_delta : Int,
  green_delta : Int,
  blue_delta : Int,
) -> RgbImage {
  self.map(fn(pixel) {
    Rgb::new(
      r=pixel.r + red_delta,
      g=pixel.g + green_delta,
      b=pixel.b + blue_delta,
    )
  })
}

///|
pub fn RgbImage::blend(
  self : RgbImage,
  other : RgbImage,
  alpha_per_1000 : Int,
) -> RgbImage {
  let alpha = clamp(alpha_per_1000, low=0, high=1000)
  let out = RgbImage::new(width=self.width, height=self.height)
  for y in 0.. GrayImage {
  let out = GrayImage::new(width=self.width, height=self.height)
  for y in 0.. RgbImage {
  let out = RgbImage::new(width=image.width, height=image.height)
  for y in 0.. RgbImage {
  colorize_gray(image, Rgb::new(r=0, g=0, b=128), Rgb::new(r=255, g=64, b=0))
}

///|
pub fn RgbImage::mean_luminance(self : RgbImage) -> Int {
  if self.pixels.length() == 0 {
    0
  } else {
    let mut total = 0
    for pixel in self.pixels {
      total += pixel.luminance()
    }
    total / self.pixels.length()
  }
}

///|
pub fn RgbImage::luminance(self : RgbImage) -> GrayImage {
  let out = GrayImage::new(width=self.width, height=self.height)
  for y in 0.. RgbImage {
  self.map(fn(pixel) {
    let value = pixel.luminance()
    if value >= low.value && value <= high.value {
      replacement
    } else {
      pixel
    }
  })
}

///|
pub fn color_distance(a : Rgb, b : Rgb) -> Int {
  abs_i(a.r - b.r) + abs_i(a.g - b.g) + abs_i(a.b - b.b)
}

///|
pub fn rgb_checksum(image : RgbImage) -> UInt {
  let mut checksum : UInt = 2166136261
  for pixel in image.pixels {
    checksum = (checksum ^ pixel.r.reinterpret_as_uint()) * 16777619
    checksum = (checksum ^ pixel.g.reinterpret_as_uint()) * 16777619
    checksum = (checksum ^ pixel.b.reinterpret_as_uint()) * 16777619
  }
  checksum
}