///|
/// Draw a grayscale coverage mask with the given paint
pub fn Canvas::draw_mask(
  self : Canvas,
  x : Int,
  y : Int,
  width : Int,
  height : Int,
  mask : FixedArray[Byte],
  paint : Paint,
) -> Unit {
  guard paint.style is Fill else { return }
  guard width > 0 && height > 0 && mask.length() >= width * height else {
    return
  }
  let (x_start, y_start, x_end, y_end) = clipped_int_bounds(
    self,
    x,
    y,
    x + width,
    y + height,
  )
  guard x_start < x_end && y_start < y_end else { return }
  for py in y_start.. 0 {
        let color = paint.sample_color(
          px.to_double() + 0.5,
          py.to_double() + 0.5,
        )
        let alpha = (color.a.to_int() * coverage + 127) / 255
        if blend_sampled_source_pixel(
            self.pixmap,
            px,
            py,
            color.with_alpha(clamp_byte(alpha)),
            paint.blend_mode,
          ) {
          self.mark_dirty_bounds(px, py, px + 1, py + 1)
        }
      }
    }
  }
}

///|
/// Draw a source rectangle from a grayscale coverage mask with the given paint.
pub fn Canvas::draw_mask_rect(
  self : Canvas,
  x : Int,
  y : Int,
  mask_width : Int,
  mask_height : Int,
  mask : FixedArray[Byte],
  src_rect : PixelRect,
  paint : Paint,
) -> Unit {
  guard paint.style is Fill else { return }
  guard mask_width > 0 &&
    mask_height > 0 &&
    mask.length() >= mask_width * mask_height else {
    return
  }
  let mask_bounds = PixelRect::new(
    x=0,
    y=0,
    width=mask_width,
    height=mask_height,
  )
  match src_rect.clip_to(mask_bounds) {
    Some(clipped_src) => {
      let dst_x = x + clipped_src.x - src_rect.x
      let dst_y = y + clipped_src.y - src_rect.y
      let (x_start, y_start, x_end, y_end) = clipped_int_bounds(
        self,
        dst_x,
        dst_y,
        dst_x + clipped_src.width,
        dst_y + clipped_src.height,
      )
      guard x_start < x_end && y_start < y_end else { return }
      for py in y_start.. 0 {
            let color = paint.sample_color(
              px.to_double() + 0.5,
              py.to_double() + 0.5,
            )
            let alpha = (color.a.to_int() * coverage + 127) / 255
            if blend_sampled_source_pixel(
                self.pixmap,
                px,
                py,
                color.with_alpha(clamp_byte(alpha)),
                paint.blend_mode,
              ) {
              self.mark_dirty_bounds(px, py, px + 1, py + 1)
            }
          }
        }
      }
    }
    None => ()
  }
}

///|
/// Draw a source pixmap at the given destination position.
pub fn Canvas::draw_pixmap(
  self : Canvas,
  src : Pixmap,
  x : Int,
  y : Int,
  paint : Paint,
) -> Unit {
  guard paint.style is Fill else { return }
  guard src.get_width() > 0 && src.get_height() > 0 else { return }
  let (x_start, y_start, x_end, y_end) = clipped_int_bounds(
    self,
    x,
    y,
    x + src.get_width(),
    y + src.get_height(),
  )
  guard x_start < x_end && y_start < y_end else { return }
  for py in y_start.. Unit {
  guard paint.style is Fill else { return }
  guard src.get_width() > 0 && src.get_height() > 0 else { return }
  let src_bounds = PixelRect::new(
    x=0,
    y=0,
    width=src.get_width(),
    height=src.get_height(),
  )
  match src_rect.clip_to(src_bounds) {
    Some(clipped_src) => {
      let dst_x = x + clipped_src.x - src_rect.x
      let dst_y = y + clipped_src.y - src_rect.y
      let (x_start, y_start, x_end, y_end) = clipped_int_bounds(
        self,
        dst_x,
        dst_y,
        dst_x + clipped_src.width,
        dst_y + clipped_src.height,
      )
      guard x_start < x_end && y_start < y_end else { return }
      for py in y_start.. ()
  }
}

///|
fn Canvas::draw_layer_pixmap(
  self : Canvas,
  src : Pixmap,
  x : Int,
  y : Int,
  paint : Paint,
) -> Unit {
  guard paint.has_shader() else {
    self.draw_pixmap(src, x, y, paint)
    return
  }
  self.draw_layer_pixmap_rect(
    src,
    PixelRect::new(x=0, y=0, width=src.get_width(), height=src.get_height()),
    x,
    y,
    paint,
  )
}

///|
fn Canvas::draw_layer_pixmap_rect(
  self : Canvas,
  src : Pixmap,
  src_rect : PixelRect,
  x : Int,
  y : Int,
  paint : Paint,
) -> Unit {
  guard paint.has_shader() else {
    self.draw_pixmap_rect(src, src_rect, x, y, paint)
    return
  }
  guard paint.style is Fill else { return }
  guard src.get_width() > 0 && src.get_height() > 0 else { return }
  let src_bounds = PixelRect::new(
    x=0,
    y=0,
    width=src.get_width(),
    height=src.get_height(),
  )
  match src_rect.clip_to(src_bounds) {
    Some(clipped_src) => {
      let dst_x = x + clipped_src.x - src_rect.x
      let dst_y = y + clipped_src.y - src_rect.y
      let (x_start, y_start, x_end, y_end) = clipped_int_bounds(
        self,
        dst_x,
        dst_y,
        dst_x + clipped_src.width,
        dst_y + clipped_src.height,
      )
      guard x_start < x_end && y_start < y_end else { return }
      for py in y_start.. ()
  }
}

///|
/// Draw a source pixmap scaled to the given destination rectangle.
pub fn Canvas::draw_scaled_pixmap(
  self : Canvas,
  src : Pixmap,
  x : Int,
  y : Int,
  width : Int,
  height : Int,
  paint : Paint,
) -> Unit {
  guard paint.style is Fill else { return }
  guard width > 0 && height > 0 else { return }
  guard src.get_width() > 0 && src.get_height() > 0 else { return }
  let (x_start, y_start, x_end, y_end) = clipped_int_bounds(
    self,
    x,
    y,
    x + width,
    y + height,
  )
  guard x_start < x_end && y_start < y_end else { return }
  if blend_scaled_pixmap_region(
      self.pixmap,
      x,
      y,
      width,
      height,
      src,
      paint.opacity,
      paint.filter,
      paint.blend_mode,
      x_start,
      y_start,
      x_end,
      y_end,
    ) {
    self.mark_dirty_bounds(x_start, y_start, x_end, y_end)
  }
}

///|
/// Draw a source pixmap scaled with bilinear sampling.
pub fn Canvas::draw_scaled_pixmap_bilinear(
  self : Canvas,
  src : Pixmap,
  x : Int,
  y : Int,
  width : Int,
  height : Int,
  paint : Paint,
) -> Unit {
  guard paint.style is Fill else { return }
  guard width > 0 && height > 0 else { return }
  guard src.get_width() > 0 && src.get_height() > 0 else { return }
  let (x_start, y_start, x_end, y_end) = clipped_int_bounds(
    self,
    x,
    y,
    x + width,
    y + height,
  )
  guard x_start < x_end && y_start < y_end else { return }
  if blend_scaled_pixmap_bilinear_region(
      self.pixmap,
      x,
      y,
      width,
      height,
      src,
      paint.opacity,
      paint.filter,
      paint.blend_mode,
      x_start,
      y_start,
      x_end,
      y_end,
    ) {
    self.mark_dirty_bounds(x_start, y_start, x_end, y_end)
  }
}

///|
/// Draw a source pixmap scaled with bicubic sampling.
pub fn Canvas::draw_scaled_pixmap_bicubic(
  self : Canvas,
  src : Pixmap,
  x : Int,
  y : Int,
  width : Int,
  height : Int,
  paint : Paint,
) -> Unit {
  guard paint.style is Fill else { return }
  guard width > 0 && height > 0 else { return }
  guard src.get_width() > 0 && src.get_height() > 0 else { return }
  let (x_start, y_start, x_end, y_end) = clipped_int_bounds(
    self,
    x,
    y,
    x + width,
    y + height,
  )
  guard x_start < x_end && y_start < y_end else { return }
  if blend_scaled_pixmap_bicubic_region(
      self.pixmap,
      x,
      y,
      width,
      height,
      src,
      paint.opacity,
      paint.filter,
      paint.blend_mode,
      x_start,
      y_start,
      x_end,
      y_end,
    ) {
    self.mark_dirty_bounds(x_start, y_start, x_end, y_end)
  }
}

///|
/// Draw a source pixmap scaled with an explicit sampling mode.
pub fn Canvas::draw_sampled_pixmap(
  self : Canvas,
  src : Pixmap,
  x : Int,
  y : Int,
  width : Int,
  height : Int,
  paint : Paint,
  sampling : ImageSampling,
) -> Unit {
  match sampling {
    Nearest => self.draw_scaled_pixmap(src, x, y, width, height, paint)
    Bilinear =>
      self.draw_scaled_pixmap_bilinear(src, x, y, width, height, paint)
    Bicubic => self.draw_scaled_pixmap_bicubic(src, x, y, width, height, paint)
  }
}

///|
/// Draw a source pixmap scaled with a quality strategy.
pub fn Canvas::draw_quality_pixmap(
  self : Canvas,
  src : Pixmap,
  x : Int,
  y : Int,
  width : Int,
  height : Int,
  paint : Paint,
  quality : ImageQuality,
) -> Unit {
  self.draw_sampled_pixmap(
    src,
    x,
    y,
    width,
    height,
    paint,
    quality.to_sampling(),
  )
}

///|
/// Draw a source pixmap rectangle scaled to the given destination rectangle.
pub fn Canvas::draw_scaled_pixmap_rect(
  self : Canvas,
  src : Pixmap,
  src_rect : PixelRect,
  x : Int,
  y : Int,
  width : Int,
  height : Int,
  paint : Paint,
) -> Unit {
  self.draw_sampled_pixmap_rect(
    src,
    src_rect,
    x,
    y,
    width,
    height,
    paint,
    Nearest,
  )
}

///|
/// Draw a source pixmap rectangle scaled with bilinear sampling.
pub fn Canvas::draw_scaled_pixmap_rect_bilinear(
  self : Canvas,
  src : Pixmap,
  src_rect : PixelRect,
  x : Int,
  y : Int,
  width : Int,
  height : Int,
  paint : Paint,
) -> Unit {
  self.draw_sampled_pixmap_rect(
    src,
    src_rect,
    x,
    y,
    width,
    height,
    paint,
    Bilinear,
  )
}

///|
/// Draw a source pixmap rectangle scaled with bicubic sampling.
pub fn Canvas::draw_scaled_pixmap_rect_bicubic(
  self : Canvas,
  src : Pixmap,
  src_rect : PixelRect,
  x : Int,
  y : Int,
  width : Int,
  height : Int,
  paint : Paint,
) -> Unit {
  self.draw_sampled_pixmap_rect(
    src,
    src_rect,
    x,
    y,
    width,
    height,
    paint,
    Bicubic,
  )
}

///|
/// Draw a source pixmap rectangle scaled with an explicit sampling mode.
pub fn Canvas::draw_sampled_pixmap_rect(
  self : Canvas,
  src : Pixmap,
  src_rect : PixelRect,
  x : Int,
  y : Int,
  width : Int,
  height : Int,
  paint : Paint,
  sampling : ImageSampling,
) -> Unit {
  guard paint.style is Fill else { return }
  guard width > 0 && height > 0 else { return }
  guard src.get_width() > 0 && src.get_height() > 0 else { return }
  let src_bounds = PixelRect::new(
    x=0,
    y=0,
    width=src.get_width(),
    height=src.get_height(),
  )
  match src_rect.clip_to(src_bounds) {
    Some(clipped_src) => {
      let (x_start, y_start, x_end, y_end) = clipped_int_bounds(
        self,
        x,
        y,
        x + width,
        y + height,
      )
      guard x_start < x_end && y_start < y_end else { return }
      if blend_sampled_pixmap_rect_region(
          self.pixmap,
          x,
          y,
          width,
          height,
          src,
          clipped_src,
          paint.opacity,
          paint.filter,
          sampling,
          paint.blend_mode,
          x_start,
          y_start,
          x_end,
          y_end,
        ) {
        self.mark_dirty_bounds(x_start, y_start, x_end, y_end)
      }
    }
    None => ()
  }
}

///|
/// Draw a source pixmap rectangle scaled with a quality strategy.
pub fn Canvas::draw_quality_pixmap_rect(
  self : Canvas,
  src : Pixmap,
  src_rect : PixelRect,
  x : Int,
  y : Int,
  width : Int,
  height : Int,
  paint : Paint,
  quality : ImageQuality,
) -> Unit {
  self.draw_sampled_pixmap_rect(
    src,
    src_rect,
    x,
    y,
    width,
    height,
    paint,
    quality.to_sampling(),
  )
}

///|
/// Draw a source pixmap transformed by the current canvas transform.
pub fn Canvas::draw_transformed_sampled_pixmap(
  self : Canvas,
  src : Pixmap,
  x : Double,
  y : Double,
  width : Double,
  height : Double,
  paint : Paint,
  sampling : ImageSampling,
) -> Unit {
  self.draw_transformed_sampled_pixmap_rect(
    src,
    PixelRect::new(x=0, y=0, width=src.get_width(), height=src.get_height()),
    x,
    y,
    width,
    height,
    paint,
    sampling,
  )
}

///|
/// Draw a source pixmap transformed by the current transform using a quality
/// strategy.
pub fn Canvas::draw_transformed_quality_pixmap(
  self : Canvas,
  src : Pixmap,
  x : Double,
  y : Double,
  width : Double,
  height : Double,
  paint : Paint,
  quality : ImageQuality,
) -> Unit {
  self.draw_transformed_sampled_pixmap(
    src,
    x,
    y,
    width,
    height,
    paint,
    quality.to_sampling(),
  )
}

///|
/// Draw a source pixmap rectangle transformed by the current canvas transform.
pub fn Canvas::draw_transformed_sampled_pixmap_rect(
  self : Canvas,
  src : Pixmap,
  src_rect : PixelRect,
  x : Double,
  y : Double,
  width : Double,
  height : Double,
  paint : Paint,
  sampling : ImageSampling,
) -> Unit {
  guard paint.style is Fill else { return }
  guard width > 0.0 && height > 0.0 else { return }
  guard src.get_width() > 0 && src.get_height() > 0 else { return }
  let src_bounds = PixelRect::new(
    x=0,
    y=0,
    width=src.get_width(),
    height=src.get_height(),
  )
  match (src_rect.clip_to(src_bounds), self.transform.inverse()) {
    (Some(clipped_src), Some(inverse_transform)) => {
      let (left, top, right, bottom) = transformed_rect_bounds(
        self.transform,
        x,
        y,
        width,
        height,
      )
      let (x_start, y_start, x_end, y_end) = clipped_int_bounds(
        self, left, top, right, bottom,
      )
      guard x_start < x_end && y_start < y_end else { return }
      let local_right = x + width
      let local_bottom = y + height
      let mut dirty_left = x_end
      let mut dirty_top = y_end
      let mut dirty_right = x_start
      let mut dirty_bottom = y_start
      for py in y_start..= x &&
            local_x < local_right &&
            local_y >= y &&
            local_y < local_bottom {
            let sampled = sample_transformed_pixmap_rect(
              src, clipped_src, local_x, local_y, x, y, width, height, sampling,
            )
            let color = paint.sample_source_color(sampled)
            if should_blend_source_color(color, paint.blend_mode) {
              self.pixmap.blend_pixel_with_mode(px, py, color, paint.blend_mode)
              dirty_left = dirty_left.min(px)
              dirty_top = dirty_top.min(py)
              dirty_right = dirty_right.max(px + 1)
              dirty_bottom = dirty_bottom.max(py + 1)
            }
          }
        }
      }
      if dirty_left < dirty_right && dirty_top < dirty_bottom {
        self.mark_dirty_bounds(dirty_left, dirty_top, dirty_right, dirty_bottom)
      }
    }
    _ => ()
  }
}

///|
/// Draw a source pixmap rectangle transformed by the current transform using a
/// quality strategy.
pub fn Canvas::draw_transformed_quality_pixmap_rect(
  self : Canvas,
  src : Pixmap,
  src_rect : PixelRect,
  x : Double,
  y : Double,
  width : Double,
  height : Double,
  paint : Paint,
  quality : ImageQuality,
) -> Unit {
  self.draw_transformed_sampled_pixmap_rect(
    src,
    src_rect,
    x,
    y,
    width,
    height,
    paint,
    quality.to_sampling(),
  )
}

///|
/// Draw a source pixmap rectangle repeatedly into the destination rectangle.
pub fn Canvas::draw_tiled_pixmap_rect(
  self : Canvas,
  src : Pixmap,
  src_rect : PixelRect,
  x : Int,
  y : Int,
  width : Int,
  height : Int,
  paint : Paint,
) -> Unit {
  guard paint.style is Fill else { return }
  guard width > 0 && height > 0 else { return }
  guard src.get_width() > 0 && src.get_height() > 0 else { return }
  let src_bounds = PixelRect::new(
    x=0,
    y=0,
    width=src.get_width(),
    height=src.get_height(),
  )
  match src_rect.clip_to(src_bounds) {
    Some(clipped_src) => {
      let (x_start, y_start, x_end, y_end) = clipped_int_bounds(
        self,
        x,
        y,
        x + width,
        y + height,
      )
      guard x_start < x_end && y_start < y_end else { return }
      if blend_tiled_pixmap_rect_region(
          self.pixmap,
          x,
          y,
          width,
          height,
          src,
          clipped_src,
          paint.opacity,
          paint.filter,
          paint.blend_mode,
          x_start,
          y_start,
          x_end,
          y_end,
        ) {
        self.mark_dirty_bounds(x_start, y_start, x_end, y_end)
      }
    }
    None => ()
  }
}

///|
/// Draw a source pixmap using nine-patch scaling.
pub fn Canvas::draw_nine_patch_pixmap(
  self : Canvas,
  src : Pixmap,
  x : Int,
  y : Int,
  width : Int,
  height : Int,
  insets : NinePatchInsets,
  paint : Paint,
  sampling : ImageSampling,
) -> Unit {
  guard paint.style is Fill else { return }
  guard width > 0 && height > 0 else { return }
  guard src.get_width() > 0 && src.get_height() > 0 else { return }
  let (x_start, y_start, x_end, y_end) = clipped_int_bounds(
    self,
    x,
    y,
    x + width,
    y + height,
  )
  guard x_start < x_end && y_start < y_end else { return }
  if blend_nine_patch_pixmap_region(
      self.pixmap,
      x,
      y,
      width,
      height,
      src,
      insets,
      paint.opacity,
      paint.filter,
      sampling,
      paint.blend_mode,
      x_start,
      y_start,
      x_end,
      y_end,
    ) {
    self.mark_dirty_bounds(x_start, y_start, x_end, y_end)
  }
}

///|
/// Draw a source pixmap using nine-patch scaling and a quality strategy.
pub fn Canvas::draw_quality_nine_patch_pixmap(
  self : Canvas,
  src : Pixmap,
  x : Int,
  y : Int,
  width : Int,
  height : Int,
  insets : NinePatchInsets,
  paint : Paint,
  quality : ImageQuality,
) -> Unit {
  self.draw_nine_patch_pixmap(
    src,
    x,
    y,
    width,
    height,
    insets,
    paint,
    quality.to_sampling(),
  )
}

///|
fn transformed_rect_bounds(
  transform : Transform,
  x : Double,
  y : Double,
  width : Double,
  height : Double,
) -> (Int, Int, Int, Int) {
  let (x0, y0) = transform.apply(x, y)
  let (x1, y1) = transform.apply(x + width, y)
  let (x2, y2) = transform.apply(x, y + height)
  let (x3, y3) = transform.apply(x + width, y + height)
  let min_x = x0.min(x1).min(x2).min(x3)
  let min_y = y0.min(y1).min(y2).min(y3)
  let max_x = x0.max(x1).max(x2).max(x3)
  let max_y = y0.max(y1).max(y2).max(y3)
  (
    floor_to_int(min_x),
    floor_to_int(min_y),
    ceil_to_int(max_x),
    ceil_to_int(max_y),
  )
}

///|
fn ceil_to_int(value : Double) -> Int {
  -floor_to_int(-value)
}

///|
fn sample_transformed_pixmap_rect(
  src : Pixmap,
  src_rect : PixelRect,
  local_x : Double,
  local_y : Double,
  dst_x : Double,
  dst_y : Double,
  dst_width : Double,
  dst_height : Double,
  sampling : ImageSampling,
) -> Color {
  let u = (local_x - dst_x) / dst_width
  let v = (local_y - dst_y) / dst_height
  match sampling {
    Nearest => {
      let src_x = src_rect.x +
        floor_to_int(u * src_rect.width.to_double())
        .max(0)
        .min(src_rect.width - 1)
      let src_y = src_rect.y +
        floor_to_int(v * src_rect.height.to_double())
        .max(0)
        .min(src_rect.height - 1)
      src.get_pixel(src_x, src_y)
    }
    Bilinear => {
      let src_x = src_rect.x.to_double() + u * src_rect.width.to_double() - 0.5
      let src_y = src_rect.y.to_double() + v * src_rect.height.to_double() - 0.5
      sample_bilinear_rect(src, src_x, src_y, src_rect)
    }
    Bicubic => {
      let src_x = src_rect.x.to_double() + u * src_rect.width.to_double() - 0.5
      let src_y = src_rect.y.to_double() + v * src_rect.height.to_double() - 0.5
      sample_bicubic_rect(src, src_x, src_y, src_rect)
    }
  }
}