///|
fn render_command_estimated_dirty_rect(
  command : RenderCommand,
  width : Int,
  height : Int,
) -> @graphics.PixelRect? {
  match command {
    Clear(_) =>
      non_empty_pixel_rect(@graphics.PixelRect::new(x=0, y=0, width~, height~))
    FillRect(x, y, rect_width, rect_height, _) =>
      pixel_rect_from_double_bounds(x, y, x + rect_width, y + rect_height)
    StrokeRect(x, y, rect_width, rect_height, paint) => {
      let expand = paint.stroke_width.max(0.0) / 2.0
      pixel_rect_from_double_bounds(
        x - expand,
        y - expand,
        x + rect_width + expand,
        y + rect_height + expand,
      )
    }
    FillPath(path, _) => render_path_estimated_dirty_rect(path, 0.0)
    StrokePath(path, paint) =>
      render_path_estimated_dirty_rect(path, paint.stroke_width.max(0.0) / 2.0)
    DrawPixmap(pixmap, x, y, _) =>
      render_pixmap_estimated_dirty_rect(pixmap, x, y)
    DrawPixmapKeyed(_, pixmap, x, y, _) =>
      render_pixmap_estimated_dirty_rect(pixmap, x, y)
    DrawBmpBytesKeyed(_, image_data, x, y, _) =>
      render_image_bytes_estimated_dirty_rect(
        Bmp(image_data),
        x,
        y,
        width,
        height,
      )
    DrawPngBytesKeyed(_, image_data, x, y, _) =>
      render_image_bytes_estimated_dirty_rect(
        Png(image_data),
        x,
        y,
        width,
        height,
      )
    DrawRgbaBytesKeyed(_, image_data, image_width, image_height, x, y, _) =>
      render_image_bytes_estimated_dirty_rect(
        Rgba(image_data, image_width, image_height),
        x,
        y,
        width,
        height,
      )
    DrawSampledBmpBytesKeyed(_, image_data, x, y, width, height, _, _) =>
      render_scaled_image_bytes_estimated_dirty_rect(
        Bmp(image_data),
        x,
        y,
        width,
        height,
      )
    DrawSampledPngBytesKeyed(_, image_data, x, y, width, height, _, _) =>
      render_scaled_image_bytes_estimated_dirty_rect(
        Png(image_data),
        x,
        y,
        width,
        height,
      )
    DrawSampledRgbaBytesKeyed(
      _,
      image_data,
      image_width,
      image_height,
      x,
      y,
      width,
      height,
      _,
      _
    ) =>
      render_scaled_image_bytes_estimated_dirty_rect(
        Rgba(image_data, image_width, image_height),
        x,
        y,
        width,
        height,
      )
    DrawQualityBmpBytesKeyed(_, image_data, x, y, width, height, _, _) =>
      render_scaled_image_bytes_estimated_dirty_rect(
        Bmp(image_data),
        x,
        y,
        width,
        height,
      )
    DrawQualityBmpBytesRectKeyed(
      _,
      image_data,
      src_rect,
      x,
      y,
      width,
      height,
      _,
      _
    ) =>
      render_scaled_image_bytes_rect_estimated_dirty_rect(
        Bmp(image_data),
        src_rect,
        x,
        y,
        width,
        height,
      )
    DrawQualityPngBytesKeyed(_, image_data, x, y, width, height, _, _) =>
      render_scaled_image_bytes_estimated_dirty_rect(
        Png(image_data),
        x,
        y,
        width,
        height,
      )
    DrawQualityPngBytesRectKeyed(
      _,
      image_data,
      src_rect,
      x,
      y,
      width,
      height,
      _,
      _
    ) =>
      render_scaled_image_bytes_rect_estimated_dirty_rect(
        Png(image_data),
        src_rect,
        x,
        y,
        width,
        height,
      )
    DrawQualityRgbaBytesKeyed(
      _,
      image_data,
      image_width,
      image_height,
      x,
      y,
      width,
      height,
      _,
      _
    ) =>
      render_scaled_image_bytes_estimated_dirty_rect(
        Rgba(image_data, image_width, image_height),
        x,
        y,
        width,
        height,
      )
    DrawQualityRgbaBytesRectKeyed(
      _,
      image_data,
      image_width,
      image_height,
      src_rect,
      x,
      y,
      width,
      height,
      _,
      _
    ) =>
      render_scaled_image_bytes_rect_estimated_dirty_rect(
        Rgba(image_data, image_width, image_height),
        src_rect,
        x,
        y,
        width,
        height,
      )
    DrawTransformedSampledBmpBytesKeyed(
      _,
      image_data,
      x,
      y,
      width,
      height,
      _,
      _
    ) =>
      render_transformed_image_bytes_estimated_dirty_rect(
        Bmp(image_data),
        x,
        y,
        width,
        height,
      )
    DrawTransformedSampledBmpBytesRectKeyed(
      _,
      image_data,
      src_rect,
      x,
      y,
      width,
      height,
      _,
      _
    ) =>
      render_transformed_image_bytes_rect_estimated_dirty_rect(
        Bmp(image_data),
        src_rect,
        x,
        y,
        width,
        height,
      )
    DrawTransformedQualityBmpBytesKeyed(
      _,
      image_data,
      x,
      y,
      width,
      height,
      _,
      _
    ) =>
      render_transformed_image_bytes_estimated_dirty_rect(
        Bmp(image_data),
        x,
        y,
        width,
        height,
      )
    DrawTransformedQualityBmpBytesRectKeyed(
      _,
      image_data,
      src_rect,
      x,
      y,
      width,
      height,
      _,
      _
    ) =>
      render_transformed_image_bytes_rect_estimated_dirty_rect(
        Bmp(image_data),
        src_rect,
        x,
        y,
        width,
        height,
      )
    DrawTransformedSampledPngBytesKeyed(
      _,
      image_data,
      x,
      y,
      width,
      height,
      _,
      _
    ) =>
      render_transformed_image_bytes_estimated_dirty_rect(
        Png(image_data),
        x,
        y,
        width,
        height,
      )
    DrawTransformedSampledPngBytesRectKeyed(
      _,
      image_data,
      src_rect,
      x,
      y,
      width,
      height,
      _,
      _
    ) =>
      render_transformed_image_bytes_rect_estimated_dirty_rect(
        Png(image_data),
        src_rect,
        x,
        y,
        width,
        height,
      )
    DrawTransformedQualityPngBytesKeyed(
      _,
      image_data,
      x,
      y,
      width,
      height,
      _,
      _
    ) =>
      render_transformed_image_bytes_estimated_dirty_rect(
        Png(image_data),
        x,
        y,
        width,
        height,
      )
    DrawTransformedQualityPngBytesRectKeyed(
      _,
      image_data,
      src_rect,
      x,
      y,
      width,
      height,
      _,
      _
    ) =>
      render_transformed_image_bytes_rect_estimated_dirty_rect(
        Png(image_data),
        src_rect,
        x,
        y,
        width,
        height,
      )
    DrawTransformedSampledRgbaBytesKeyed(
      _,
      image_data,
      image_width,
      image_height,
      x,
      y,
      width,
      height,
      _,
      _
    ) =>
      render_transformed_image_bytes_estimated_dirty_rect(
        Rgba(image_data, image_width, image_height),
        x,
        y,
        width,
        height,
      )
    DrawTransformedSampledRgbaBytesRectKeyed(
      _,
      image_data,
      image_width,
      image_height,
      src_rect,
      x,
      y,
      width,
      height,
      _,
      _
    ) =>
      render_transformed_image_bytes_rect_estimated_dirty_rect(
        Rgba(image_data, image_width, image_height),
        src_rect,
        x,
        y,
        width,
        height,
      )
    DrawTransformedQualityRgbaBytesKeyed(
      _,
      image_data,
      image_width,
      image_height,
      x,
      y,
      width,
      height,
      _,
      _
    ) =>
      render_transformed_image_bytes_estimated_dirty_rect(
        Rgba(image_data, image_width, image_height),
        x,
        y,
        width,
        height,
      )
    DrawTransformedQualityRgbaBytesRectKeyed(
      _,
      image_data,
      image_width,
      image_height,
      src_rect,
      x,
      y,
      width,
      height,
      _,
      _
    ) =>
      render_transformed_image_bytes_rect_estimated_dirty_rect(
        Rgba(image_data, image_width, image_height),
        src_rect,
        x,
        y,
        width,
        height,
      )
    DrawSampledBmpBytesRectKeyed(
      _,
      image_data,
      src_rect,
      x,
      y,
      width,
      height,
      _,
      _
    ) =>
      render_scaled_image_bytes_rect_estimated_dirty_rect(
        Bmp(image_data),
        src_rect,
        x,
        y,
        width,
        height,
      )
    DrawSampledPngBytesRectKeyed(
      _,
      image_data,
      src_rect,
      x,
      y,
      width,
      height,
      _,
      _
    ) =>
      render_scaled_image_bytes_rect_estimated_dirty_rect(
        Png(image_data),
        src_rect,
        x,
        y,
        width,
        height,
      )
    DrawSampledRgbaBytesRectKeyed(
      _,
      image_data,
      image_width,
      image_height,
      src_rect,
      x,
      y,
      width,
      height,
      _,
      _
    ) =>
      render_scaled_image_bytes_rect_estimated_dirty_rect(
        Rgba(image_data, image_width, image_height),
        src_rect,
        x,
        y,
        width,
        height,
      )
    DrawBmpBytesRectKeyed(_, image_data, src_rect, x, y, _) =>
      render_image_bytes_rect_estimated_dirty_rect(
        Bmp(image_data),
        src_rect,
        x,
        y,
      )
    DrawPngBytesRectKeyed(_, image_data, src_rect, x, y, _) =>
      render_image_bytes_rect_estimated_dirty_rect(
        Png(image_data),
        src_rect,
        x,
        y,
      )
    DrawRgbaBytesRectKeyed(
      _,
      image_data,
      image_width,
      image_height,
      src_rect,
      x,
      y,
      _
    ) =>
      render_image_bytes_rect_estimated_dirty_rect(
        Rgba(image_data, image_width, image_height),
        src_rect,
        x,
        y,
      )
    DrawTiledBmpBytesRectKeyed(_, image_data, src_rect, x, y, width, height, _) =>
      render_tiled_image_bytes_rect_estimated_dirty_rect(
        Bmp(image_data),
        src_rect,
        x,
        y,
        width,
        height,
      )
    DrawTiledPngBytesRectKeyed(_, image_data, src_rect, x, y, width, height, _) =>
      render_tiled_image_bytes_rect_estimated_dirty_rect(
        Png(image_data),
        src_rect,
        x,
        y,
        width,
        height,
      )
    DrawTiledRgbaBytesRectKeyed(
      _,
      image_data,
      image_width,
      image_height,
      src_rect,
      x,
      y,
      width,
      height,
      _
    ) =>
      render_tiled_image_bytes_rect_estimated_dirty_rect(
        Rgba(image_data, image_width, image_height),
        src_rect,
        x,
        y,
        width,
        height,
      )
    DrawNinePatchBmpBytesKeyed(_, image_data, x, y, width, height, _, _, _) =>
      render_nine_patch_image_bytes_estimated_dirty_rect(
        Bmp(image_data),
        x,
        y,
        width,
        height,
      )
    DrawNinePatchPngBytesKeyed(_, image_data, x, y, width, height, _, _, _) =>
      render_nine_patch_image_bytes_estimated_dirty_rect(
        Png(image_data),
        x,
        y,
        width,
        height,
      )
    DrawNinePatchRgbaBytesKeyed(
      _,
      image_data,
      image_width,
      image_height,
      x,
      y,
      width,
      height,
      _,
      _,
      _
    ) =>
      render_nine_patch_image_bytes_estimated_dirty_rect(
        Rgba(image_data, image_width, image_height),
        x,
        y,
        width,
        height,
      )
    DrawPixmapRect(pixmap, src_rect, x, y, _) =>
      render_pixmap_rect_estimated_dirty_rect(pixmap, src_rect, x, y)
    DrawPixmapRectKeyed(_, pixmap, src_rect, x, y, _) =>
      render_pixmap_rect_estimated_dirty_rect(pixmap, src_rect, x, y)
    DrawTextFace(face, text, x, y, config, _) => {
      guard text.length() > 0 else { return None }
      let layout = @text.TextLayout::layout(face, text, config)
      pixel_rect_from_double_bounds(
        x,
        y,
        x + layout.get_width(),
        y + layout.get_height(),
      )
    }
    DrawTextFaceKeyed(_, face, text, x, y, config, _) => {
      guard text.length() > 0 else { return None }
      let layout = @text.TextLayout::layout(face, text, config)
      pixel_rect_from_double_bounds(
        x,
        y,
        x + layout.get_width(),
        y + layout.get_height(),
      )
    }
    DrawFontBytesKeyed(_, font_data, _, text, x, y, config, _) => {
      guard text.length() > 0 else { return None }
      match @text.FontFace::from_bytes(font_data) {
        Ok(face) => {
          let layout = @text.TextLayout::layout(face, text, config)
          pixel_rect_from_double_bounds(
            x,
            y,
            x + layout.get_width(),
            y + layout.get_height(),
          )
        }
        Err(_) =>
          pixel_rect_from_double_bounds(
            x,
            y,
            x + render_pipeline_fallback_text_width(text, config),
            y + config.line_height.max(config.font_size),
          )
      }
    }
    DrawFontBytesAtlasKeyed(_, font_data, _, text, x, y, config, _) => {
      guard text.length() > 0 else { return None }
      match @text.FontFace::from_bytes(font_data) {
        Ok(face) => {
          let layout = @text.TextLayout::layout(face, text, config)
          pixel_rect_from_double_bounds(
            x,
            y,
            x + layout.get_width(),
            y + layout.get_height(),
          )
        }
        Err(_) =>
          pixel_rect_from_double_bounds(
            x,
            y,
            x + render_pipeline_fallback_text_width(text, config),
            y + config.line_height.max(config.font_size),
          )
      }
    }
    DrawFontBytesFallbackKeyed(_, _, text, x, y, config, _) => {
      guard text.length() > 0 else { return None }
      pixel_rect_from_double_bounds(
        x,
        y,
        x + render_pipeline_fallback_text_width(text, config),
        y + config.line_height.max(config.font_size),
      )
    }
    DrawFontBytesFallbackAtlasKeyed(_, _, text, x, y, config, _) => {
      guard text.length() > 0 else { return None }
      pixel_rect_from_double_bounds(
        x,
        y,
        x + render_pipeline_fallback_text_width(text, config),
        y + config.line_height.max(config.font_size),
      )
    }
    DrawLoadedFontKeyed(_, _, text, x, y, config, _) => {
      guard text.length() > 0 else { return None }
      pixel_rect_from_double_bounds(
        x,
        y,
        x + render_pipeline_fallback_text_width(text, config),
        y + config.line_height.max(config.font_size),
      )
    }
    DrawLoadedFontFallbackKeyed(_, _, text, x, y, config, _) => {
      guard text.length() > 0 else { return None }
      pixel_rect_from_double_bounds(
        x,
        y,
        x + render_pipeline_fallback_text_width(text, config),
        y + config.line_height.max(config.font_size),
      )
    }
    DrawLoadedFontFallbackAtlasKeyed(_, _, text, x, y, config, _) => {
      guard text.length() > 0 else { return None }
      pixel_rect_from_double_bounds(
        x,
        y,
        x + render_pipeline_fallback_text_width(text, config),
        y + config.line_height.max(config.font_size),
      )
    }
    DrawLoadedFontAtlasKeyed(_, _, text, x, y, config, _) => {
      guard text.length() > 0 else { return None }
      pixel_rect_from_double_bounds(
        x,
        y,
        x + render_pipeline_fallback_text_width(text, config),
        y + config.line_height.max(config.font_size),
      )
    }
  }
}

///|
fn render_pixmap_estimated_dirty_rect(
  pixmap : @graphics.Pixmap,
  x : Int,
  y : Int,
) -> @graphics.PixelRect? {
  non_empty_pixel_rect(
    @graphics.PixelRect::new(
      x~,
      y~,
      width=pixmap.get_width(),
      height=pixmap.get_height(),
    ),
  )
}

///|
fn render_scaled_pixmap_estimated_dirty_rect(
  pixmap : @graphics.Pixmap,
  x : Int,
  y : Int,
  width : Int,
  height : Int,
) -> @graphics.PixelRect? {
  guard width > 0 && height > 0 else { return None }
  guard pixmap.get_width() > 0 && pixmap.get_height() > 0 else { return None }
  non_empty_pixel_rect(@graphics.PixelRect::new(x~, y~, width~, height~))
}

///|
fn render_transformed_pixmap_estimated_dirty_rect(
  pixmap : @graphics.Pixmap,
  x : Double,
  y : Double,
  width : Double,
  height : Double,
) -> @graphics.PixelRect? {
  guard width > 0.0 && height > 0.0 else { return None }
  guard pixmap.get_width() > 0 && pixmap.get_height() > 0 else { return None }
  pixel_rect_from_double_bounds(x, y, x + width, y + height)
}

///|
fn render_transformed_pixmap_rect_estimated_dirty_rect(
  pixmap : @graphics.Pixmap,
  src_rect : @graphics.PixelRect,
  x : Double,
  y : Double,
  width : Double,
  height : Double,
) -> @graphics.PixelRect? {
  guard width > 0.0 && height > 0.0 else { return None }
  let src_bounds = @graphics.PixelRect::new(
    x=0,
    y=0,
    width=pixmap.get_width(),
    height=pixmap.get_height(),
  )
  match src_rect.clip_to(src_bounds) {
    Some(clipped_src) =>
      if clipped_src.is_empty() {
        None
      } else {
        pixel_rect_from_double_bounds(x, y, x + width, y + height)
      }
    None => None
  }
}

///|
fn render_scaled_pixmap_rect_estimated_dirty_rect(
  pixmap : @graphics.Pixmap,
  src_rect : @graphics.PixelRect,
  x : Int,
  y : Int,
  width : Int,
  height : Int,
) -> @graphics.PixelRect? {
  guard width > 0 && height > 0 else { return None }
  let src_bounds = @graphics.PixelRect::new(
    x=0,
    y=0,
    width=pixmap.get_width(),
    height=pixmap.get_height(),
  )
  match src_rect.clip_to(src_bounds) {
    Some(clipped_src) =>
      if clipped_src.is_empty() {
        None
      } else {
        non_empty_pixel_rect(@graphics.PixelRect::new(x~, y~, width~, height~))
      }
    None => None
  }
}

///|
fn render_nine_patch_pixmap_estimated_dirty_rect(
  pixmap : @graphics.Pixmap,
  x : Int,
  y : Int,
  width : Int,
  height : Int,
) -> @graphics.PixelRect? {
  guard width > 0 && height > 0 else { return None }
  guard pixmap.get_width() > 0 && pixmap.get_height() > 0 else { return None }
  non_empty_pixel_rect(@graphics.PixelRect::new(x~, y~, width~, height~))
}

///|
fn render_tiled_pixmap_rect_estimated_dirty_rect(
  pixmap : @graphics.Pixmap,
  src_rect : @graphics.PixelRect,
  x : Int,
  y : Int,
  width : Int,
  height : Int,
) -> @graphics.PixelRect? {
  guard width > 0 && height > 0 else { return None }
  let src_bounds = @graphics.PixelRect::new(
    x=0,
    y=0,
    width=pixmap.get_width(),
    height=pixmap.get_height(),
  )
  match src_rect.clip_to(src_bounds) {
    Some(clipped_src) =>
      if clipped_src.is_empty() {
        None
      } else {
        non_empty_pixel_rect(@graphics.PixelRect::new(x~, y~, width~, height~))
      }
    None => None
  }
}

///|
fn render_pixmap_rect_estimated_dirty_rect(
  pixmap : @graphics.Pixmap,
  src_rect : @graphics.PixelRect,
  x : Int,
  y : Int,
) -> @graphics.PixelRect? {
  let src_bounds = @graphics.PixelRect::new(
    x=0,
    y=0,
    width=pixmap.get_width(),
    height=pixmap.get_height(),
  )
  match src_rect.clip_to(src_bounds) {
    Some(clipped_src) =>
      non_empty_pixel_rect(
        @graphics.PixelRect::new(
          x=x + clipped_src.x - src_rect.x,
          y=y + clipped_src.y - src_rect.y,
          width=clipped_src.width,
          height=clipped_src.height,
        ),
      )
    None => None
  }
}

///|
fn render_path_estimated_dirty_rect(
  path : @graphics.Path,
  expand : Double,
) -> @graphics.PixelRect? {
  match path.control_bounds() {
    Some(bounds) =>
      pixel_rect_from_double_bounds(
        bounds.x - expand,
        bounds.y - expand,
        bounds.right() + expand,
        bounds.bottom() + expand,
      )
    None => None
  }
}

///|
fn pixel_rect_from_double_bounds(
  left : Double,
  top : Double,
  right : Double,
  bottom : Double,
) -> @graphics.PixelRect? {
  guard right > left && bottom > top else { return None }
  let x0 = floor_to_int(left)
  let y0 = floor_to_int(top)
  let x1 = ceil_to_int(right)
  let y1 = ceil_to_int(bottom)
  non_empty_pixel_rect(
    @graphics.PixelRect::new(x=x0, y=y0, width=x1 - x0, height=y1 - y0),
  )
}

///|
fn non_empty_pixel_rect(rect : @graphics.PixelRect) -> @graphics.PixelRect? {
  if rect.is_empty() {
    None
  } else {
    Some(rect)
  }
}