use tiny_skia::{Color, FillRule, LineCap, Paint, PathBuilder, Pixmap, Stroke, Transform};
#[derive(Debug)]
pub struct Canvas {
pixmap: Pixmap,
}
impl Canvas {
#[must_use]
pub fn new(width: u32, height: u32) -> Self {
Self {
pixmap: Pixmap::new(width, height).expect("non-zero canvas dimensions"),
}
}
#[must_use]
pub fn width(&self) -> u32 {
self.pixmap.width()
}
#[must_use]
pub fn height(&self) -> u32 {
self.pixmap.height()
}
pub fn clear(&mut self, color: Color) {
self.pixmap.fill(color);
}
pub fn fill_rect(&mut self, x: f32, y: f32, w: f32, h: f32, color: Color) {
let mut paint = Paint::default();
paint.set_color(color);
paint.anti_alias = true;
if let Some(rect) = tiny_skia::Rect::from_xywh(x, y, w, h) {
let path = PathBuilder::from_rect(rect);
self.pixmap.fill_path(
&path,
&paint,
FillRule::Winding,
Transform::identity(),
None,
);
}
}
pub fn fill_circle(&mut self, cx: f32, cy: f32, radius: f32, color: Color) {
let mut paint = Paint::default();
paint.set_color(color);
paint.anti_alias = true;
if let Some(path) = {
let mut pb = PathBuilder::new();
pb.push_circle(cx, cy, radius);
pb.finish()
} {
self.pixmap.fill_path(
&path,
&paint,
FillRule::Winding,
Transform::identity(),
None,
);
}
}
pub fn hline(&mut self, y: f32, thickness: f32, color: Color) {
let w = self.pixmap.width() as f32;
self.stroke_line(0.0, y, w, y, thickness, color);
}
pub fn stroke_line(
&mut self,
x1: f32,
y1: f32,
x2: f32,
y2: f32,
thickness: f32,
color: Color,
) {
let mut paint = Paint::default();
paint.set_color(color);
paint.anti_alias = true;
let stroke = Stroke {
width: thickness,
line_cap: LineCap::Round,
..Stroke::default()
};
if let Some(path) = {
let mut pb = PathBuilder::new();
pb.move_to(x1, y1);
pb.line_to(x2, y2);
pb.finish()
} {
self.pixmap
.stroke_path(&path, &paint, &stroke, Transform::identity(), None);
}
}
pub fn fill_polygon(&mut self, vertices: &[(f32, f32)], color: Color) {
if vertices.len() < 3 {
return;
}
let mut paint = Paint::default();
paint.set_color(color);
paint.anti_alias = true;
if let Some(path) = {
let mut pb = PathBuilder::new();
pb.move_to(vertices[0].0, vertices[0].1);
for &(x, y) in &vertices[1..] {
pb.line_to(x, y);
}
pb.close();
pb.finish()
} {
self.pixmap.fill_path(
&path,
&paint,
FillRule::Winding,
Transform::identity(),
None,
);
}
}
pub fn stroke_polygon(&mut self, vertices: &[(f32, f32)], thickness: f32, color: Color) {
if vertices.len() < 3 {
return;
}
let mut paint = Paint::default();
paint.set_color(color);
paint.anti_alias = true;
let stroke = Stroke {
width: thickness,
..Stroke::default()
};
if let Some(path) = {
let mut pb = PathBuilder::new();
pb.move_to(vertices[0].0, vertices[0].1);
for &(x, y) in &vertices[1..] {
pb.line_to(x, y);
}
pb.close();
pb.finish()
} {
self.pixmap
.stroke_path(&path, &paint, &stroke, Transform::identity(), None);
}
}
#[must_use]
pub fn pixels_rgba(&self) -> &[u8] {
self.pixmap.data()
}
#[must_use]
pub fn pixels_argb32(&self) -> Vec<u32> {
self.pixmap
.pixels()
.iter()
.map(|px| {
let r = u32::from(px.red());
let g = u32::from(px.green());
let b = u32::from(px.blue());
let a = u32::from(px.alpha());
(a << 24) | (r << 16) | (g << 8) | b
})
.collect()
}
#[must_use]
pub fn pixels_rgb(&self) -> Vec<u8> {
self.pixmap
.pixels()
.iter()
.flat_map(|px| [px.red(), px.green(), px.blue()])
.collect()
}
}