///|
/// A gradient interpolation strategy for procedural canvas backgrounds.
pub(all) enum GradientMode {
  LinearHorizontal
  LinearVertical
  LinearDiagonal
  Radial
  Diamond
} derive(Eq, Debug)

///|
pub(all) struct GradientStop {
  position : Int
  color : Rgba
} derive(Debug)

///|
pub fn GradientStop::new(position : Int, color : Rgba) -> GradientStop {
  { position, color }
}

///|
pub fn GradientStop::position(self : GradientStop) -> Int {
  self.position
}

///|
pub fn GradientStop::color(self : GradientStop) -> Rgba {
  self.color
}

///|
/// Fill a canvas with a two-color deterministic gradient.
pub fn Canvas::gradient(
  self : Canvas,
  mode : GradientMode,
  first : Rgba,
  second : Rgba,
) -> Canvas {
  let result = self.copy()
  for y in 0.. x
        LinearVertical => y
        LinearDiagonal => x + y
        Radial => {
          let dx = x - self.width / 2
          let dy = y - self.height / 2
          int_sqrt(dx * dx + dy * dy)
        }
        Diamond => {
          let dx = if x > self.width / 2 {
            x - self.width / 2
          } else {
            self.width / 2 - x
          }
          let dy = if y > self.height / 2 {
            y - self.height / 2
          } else {
            self.height / 2 - y
          }
          dx + dy
        }
      }
      let denominator = match mode {
        LinearHorizontal => if self.width <= 1 { 1 } else { self.width - 1 }
        LinearVertical => if self.height <= 1 { 1 } else { self.height - 1 }
        LinearDiagonal =>
          if self.width + self.height <= 2 {
            1
          } else {
            self.width + self.height - 2
          }
        Radial =>
          if self.width > self.height {
            self.width / 2
          } else {
            self.height / 2
          }
        Diamond => (self.width + self.height) / 2
      }
      result.pixels[x + y * self.width] = mix_color(
        first, second, numerator, denominator,
      )
    }
  }
  result
}

///|
/// Fill using multiple stops. Positions are integer percentages 0..1000.
pub fn Canvas::gradient_stops(
  self : Canvas,
  mode : GradientMode,
  stops : ArrayView[GradientStop],
) -> Canvas {
  if stops.length() == 0 {
    self.copy()
  } else if stops.length() == 1 {
    Canvas::new(width=self.width, height=self.height, fill=stops[0].color())
  } else {
    let result = self.copy()
    for y in 0..
            x * 1000 / (if self.width <= 1 { 1 } else { self.width - 1 })
          LinearVertical =>
            y * 1000 / (if self.height <= 1 { 1 } else { self.height - 1 })
          LinearDiagonal =>
            (x + y) *
            1000 /
            (if self.width + self.height <= 2 {
              1
            } else {
              self.width + self.height - 2
            })
          Radial =>
            int_sqrt(
              (x - self.width / 2) * (x - self.width / 2) +
              (y - self.height / 2) * (y - self.height / 2),
            ) *
            1000 /
            (if self.width > self.height { self.width } else { self.height })
          Diamond => {
            let dx = if x > self.width / 2 {
              x - self.width / 2
            } else {
              self.width / 2 - x
            }
            let dy = if y > self.height / 2 {
              y - self.height / 2
            } else {
              self.height / 2 - y
            }
            (dx + dy) *
            1000 /
            (if self.width + self.height <= 2 {
              1
            } else {
              self.width + self.height
            })
          }
        }
        let position = if raw < 0 { 0 } else if raw > 1000 { 1000 } else { raw }
        result.pixels[x + y * self.width] = interpolate_stops(stops, position)
      }
    }
    result
  }
}

///|
fn interpolate_stops(stops : ArrayView[GradientStop], position : Int) -> Rgba {
  if position <= stops[0].position() {
    stops[0].color()
  } else if position >= stops[stops.length() - 1].position() {
    stops[stops.length() - 1].color()
  } else {
    let mut result = stops[0].color()
    for i in 1..= left.position() && position <= right.position() {
        result = mix_color(
          left.color(),
          right.color(),
          position - left.position(),
          right.position() - left.position(),
        )
        break
      }
    }
    result
  }
}

///|
/// Fill a clipped polygon using an integer scanline test.
pub fn Canvas::fill_polygon(
  self : Canvas,
  points : ArrayView[(Int, Int)],
  color : Rgba,
) -> Int {
  if points.length() < 3 {
    0
  } else {
    let mut min_y = self.height
    let mut max_y = 0
    for point in points {
      let (_, y) = point
      if y < min_y {
        min_y = y
      }
      if y > max_y {
        max_y = y
      }
    }
    let first_y = if min_y < 0 { 0 } else { min_y }
    let last_y = if max_y >= self.height { self.height - 1 } else { max_y }
    let mut written = 0
    for y in first_y..<(last_y + 1) {
      for x in 0.. Bool {
  let mut inside = false
  let mut j = points.length() - 1
  for i in 0.. y) != (yj > y) {
      let denominator = yj - yi
      if denominator != 0 {
        let crossing = (xj - xi) * (y - yi) / denominator + xi
        if x < crossing {
          inside = !inside
        }
      }
    }
    j = i
  }
  inside
}

///|
/// Fill a rectangle with rounded corners using a distance test.
pub fn Canvas::fill_rounded_rect(
  self : Canvas,
  x~ : Int,
  y~ : Int,
  width~ : Int,
  height~ : Int,
  radius~ : Int,
  color~ : Rgba,
) -> Int {
  if width <= 0 || height <= 0 {
    0
  } else {
    let safe_radius = if radius < 0 {
      0
    } else if radius * 2 > width {
      width / 2
    } else if radius * 2 > height {
      height / 2
    } else {
      radius
    }
    let mut written = 0
    for row in y..<(y + height) {
      for column in x..<(x + width) {
        let near_left = column < x + safe_radius
        let near_right = column >= x + width - safe_radius
        let near_top = row < y + safe_radius
        let near_bottom = row >= y + height - safe_radius
        let corner_x = if near_left {
          x + safe_radius
        } else if near_right {
          x + width - safe_radius - 1
        } else {
          column
        }
        let corner_y = if near_top {
          y + safe_radius
        } else if near_bottom {
          y + height - safe_radius - 1
        } else {
          row
        }
        let dx = column - corner_x
        let dy = row - corner_y
        if (!near_left && !near_right) ||
          (!near_top && !near_bottom) ||
          dx * dx + dy * dy <= safe_radius * safe_radius {
          if self.set(x=column, y=row, color~) {
            written += 1
          }
        }
      }
    }
    written
  }
}

///|
/// Draw a regular polygon outline with a deterministic integer approximation.
pub fn Canvas::draw_polygon(
  self : Canvas,
  points : ArrayView[(Int, Int)],
  color : Rgba,
) -> Int {
  if points.length() < 2 {
    0
  } else {
    let mut count = 0
    for i in 0.. Array[(Int, Int)] {
  if points < 2 || outer_radius < 0 || inner_radius < 0 {
    []
  } else {
    let result : Array[(Int, Int)] = []
    let count = points * 2
    for i in 0.. (Int, Int) {
  let direction = (degrees % 360 + 360) % 360
  if direction < 23 {
    (1000, 0)
  } else if direction < 68 {
    (707, 707)
  } else if direction < 113 {
    (0, 1000)
  } else if direction < 158 {
    (-707, 707)
  } else if direction < 203 {
    (-1000, 0)
  } else if direction < 248 {
    (-707, -707)
  } else if direction < 293 {
    (0, -1000)
  } else if direction < 338 {
    (707, -707)
  } else {
    (1000, 0)
  }
}

///|
/// Paint a repeating checker or stripe pattern into a copy of a canvas.
pub fn Canvas::pattern(
  self : Canvas,
  cell_size : Int,
  first : Rgba,
  second : Rgba,
  stripes : Bool,
) -> Canvas {
  if cell_size <= 0 {
    self.copy()
  } else {
    let result = self.copy()
    for y in 0.. Canvas {
  let safe_amount = if amount < 0 {
    0
  } else if amount > 255 {
    255
  } else {
    amount
  }
  let result = self.copy()
  let mut state = seed
  for i in 0.. Canvas {
  let safe_levels = if levels < 2 {
    2
  } else if levels > 16 {
    16
  } else {
    levels
  }
  let matrix = [[0, 2], [3, 1]]
  let result = self.copy()
  for y in 0.. Int {
  if width <= 0 || height <= 0 {
    0
  } else {
    let inner = if thickness < 0 { 0 } else { thickness }
    let mut count = self.fill_rect(x~, y~, width~, height~, color=fill)
    for layer in 0..