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_polyline(&mut self, points: &[(f32, f32)], thickness: f32, color: Color) {
if points.len() < 2 {
return;
}
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(points[0].0, points[0].1);
for &(x, y) in &points[1..] {
pb.line_to(x, y);
}
pb.finish()
} {
self.pixmap
.stroke_path(&path, &paint, &stroke, 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()
}
#[must_use]
pub fn decode_png_scaled(data: &[u8], w: u32, h: u32) -> Pixmap {
let src = Pixmap::decode_png(data).expect("valid PNG data");
if src.width() == w && src.height() == h {
return src;
}
let mut dst = Pixmap::new(w, h).expect("non-zero dimensions");
#[allow(clippy::cast_precision_loss)]
let sx = w as f32 / src.width() as f32;
#[allow(clippy::cast_precision_loss)]
let sy = h as f32 / src.height() as f32;
dst.draw_pixmap(
0,
0,
src.as_ref(),
&tiny_skia::PixmapPaint::default(),
Transform::from_scale(sx, sy),
None,
);
dst
}
pub fn blit(&mut self, x: i32, y: i32, src: &Pixmap) {
self.pixmap.draw_pixmap(
x,
y,
src.as_ref(),
&tiny_skia::PixmapPaint::default(),
Transform::identity(),
None,
);
}
pub fn blit_with_alpha(&mut self, x: i32, y: i32, src: &Pixmap, alpha: u8) {
let paint = tiny_skia::PixmapPaint {
opacity: f32::from(alpha) / 255.0,
..tiny_skia::PixmapPaint::default()
};
self.pixmap
.draw_pixmap(x, y, src.as_ref(), &paint, Transform::identity(), None);
}
}