///|
pub struct OverlayStyle {
  box_color : Rgb
  mask_color : Rgb
  keypoint_color : Rgb
  line_width : Int
  mask_alpha_per_1000 : Int
} derive(Debug, Eq)

///|
pub fn OverlayStyle::default() -> OverlayStyle {
  {
    box_color: Rgb::new(r=255, g=64, b=32),
    mask_color: Rgb::new(r=32, g=160, b=255),
    keypoint_color: Rgb::new(r=255, g=220, b=16),
    line_width: 1,
    mask_alpha_per_1000: 400,
  }
}

///|
pub fn OverlayStyle::new(
  box_color? : Rgb = Rgb::new(r=255, g=64, b=32),
  mask_color? : Rgb = Rgb::new(r=32, g=160, b=255),
  keypoint_color? : Rgb = Rgb::new(r=255, g=220, b=16),
  line_width? : Int = 1,
  mask_alpha_per_1000? : Int = 400,
) -> OverlayStyle {
  {
    box_color,
    mask_color,
    keypoint_color,
    line_width: max_i(1, line_width),
    mask_alpha_per_1000: clamp(mask_alpha_per_1000, low=0, high=1000),
  }
}

///|
pub fn overlay_mask(
  base : RgbImage,
  mask : Mask,
  id : Int,
  style : OverlayStyle,
) -> RgbImage {
  let out = base.copy()
  for y in 0.. RgbImage {
  let out = base.copy()
  let rect = box.rect
  let color = style.box_color
  for thickness in 0.. RgbImage {
  let mut out = base.copy()
  for box in boxes {
    out = overlay_box(out, box, style)
  }
  out
}

///|
pub fn overlay_keypoint(
  base : RgbImage,
  point : Point,
  radius : Int,
  style : OverlayStyle,
) -> RgbImage {
  let out = base.copy()
  let r = max_i(1, radius)
  for y in (point.y - r)..<=(point.y + r) {
    for x in (point.x - r)..<=(point.x + r) {
      if abs_i(x - point.x) + abs_i(y - point.y) <= r {
        out.set(x, y, style.keypoint_color)
      }
    }
  }
  out
}

///|
pub fn overlay_keypoints(
  base : RgbImage,
  points : Array[Point],
  radius : Int,
  style : OverlayStyle,
) -> RgbImage {
  let mut out = base.copy()
  for point in points {
    out = overlay_keypoint(out, point, radius, style)
  }
  out
}

///|
pub fn draw_rgb_line(
  image : RgbImage,
  segment : LineSegment,
  color : Rgb,
) -> Unit {
  for point in line_raster(segment.start, segment.end) {
    image.set(point.x, point.y, color)
  }
}

///|
pub fn overlay_flow(
  base : RgbImage,
  flow : Array[FlowVector],
  scale : Int,
  color : Rgb,
) -> RgbImage {
  let out = base.copy()
  let factor = max_i(1, scale)
  for vector in flow {
    let endpoint = Point::new(
      x=vector.x + vector.dx * factor,
      y=vector.y + vector.dy * factor,
    )
    draw_rgb_line(
      out,
      LineSegment::new(start=Point::new(x=vector.x, y=vector.y), end=endpoint),
      color,
    )
  }
  out
}

///|
pub fn render_scene_overlay(
  scene : SceneOutput,
  style? : OverlayStyle = OverlayStyle::default(),
) -> RgbImage {
  let mut out = RgbImage::from_gray(scene.image)
  if scene.mask.width > 0 && scene.mask.height > 0 {
    for id in 1.. RgbImage {
  overlay_boxes(RgbImage::from_gray(image), boxes, style)
}

///|
pub fn render_feature_map(image : GrayImage, threshold : Int) -> RgbImage {
  let edges = image.edge_map(threshold)
  let mut out = heatmap(edges)
  for corner in detect_corners(image, threshold) {
    out = overlay_keypoint(out, corner.point, 1, OverlayStyle::default())
  }
  out
}

///|
pub fn render_pipeline_result(result : PipelineResult) -> RgbImage {
  heatmap(result.image)
}

///|
pub fn render_histogram(
  image : GrayImage,
  width : Int,
  height : Int,
) -> GrayImage {
  let out = GrayImage::new(width=max_i(1, width), height=max_i(1, height))
  let histogram = image.histogram(256)
  let high = max_i(1, image.height * image.width)
  for x in 0..