liquidex
43e6951f7d
this can occur if tiny_skia decides it doesn't like the path (e.g. circle with negative radius) i don't really care to render these since they're degenerate cases anyways
174 lines
5.1 KiB
Rust
174 lines
5.1 KiB
Rust
use alloc::vec::Vec;
|
|
use tiny_skia::{
|
|
BlendMode, Color, FillRule, LineCap, Paint, Path, PathBuilder, Pixmap, Shader,
|
|
Stroke as SStroke, Transform,
|
|
};
|
|
|
|
use crate::{
|
|
value::{Fill, Ref, Rgba, Scribble, Shape, Stroke, Value},
|
|
vm::{Exception, Vm},
|
|
};
|
|
|
|
pub use tiny_skia;
|
|
|
|
pub struct RendererLimits {
|
|
pub pixmap_stack_capacity: usize,
|
|
pub transform_stack_capacity: usize,
|
|
}
|
|
|
|
pub enum RenderTarget<'a> {
|
|
Borrowed(&'a mut Pixmap),
|
|
Owned(Pixmap),
|
|
}
|
|
|
|
pub struct Renderer<'a> {
|
|
pixmap_stack: Vec<RenderTarget<'a>>,
|
|
transform_stack: Vec<Transform>,
|
|
}
|
|
|
|
impl<'a> Renderer<'a> {
|
|
pub fn new(pixmap: &'a mut Pixmap, limits: &RendererLimits) -> Self {
|
|
assert!(limits.pixmap_stack_capacity > 0);
|
|
assert!(limits.transform_stack_capacity > 0);
|
|
|
|
let mut blend_stack = Vec::with_capacity(limits.pixmap_stack_capacity);
|
|
blend_stack.push(RenderTarget::Borrowed(pixmap));
|
|
|
|
let mut transform_stack = Vec::with_capacity(limits.transform_stack_capacity);
|
|
transform_stack.push(Transform::identity());
|
|
|
|
Self {
|
|
pixmap_stack: blend_stack,
|
|
transform_stack,
|
|
}
|
|
}
|
|
|
|
fn create_exception(vm: &Vm, _at: Value, message: &'static str) -> Exception {
|
|
vm.create_exception(message)
|
|
}
|
|
|
|
fn transform(&self) -> Transform {
|
|
self.transform_stack.last().copied().unwrap()
|
|
}
|
|
|
|
fn transform_mut(&mut self) -> &mut Transform {
|
|
self.transform_stack.last_mut().unwrap()
|
|
}
|
|
|
|
pub fn translate(&mut self, x: f32, y: f32) {
|
|
let translated = self.transform().post_translate(x, y);
|
|
*self.transform_mut() = translated;
|
|
}
|
|
|
|
fn pixmap_mut(&mut self) -> &mut Pixmap {
|
|
match self.pixmap_stack.last_mut().unwrap() {
|
|
RenderTarget::Borrowed(pixmap) => pixmap,
|
|
RenderTarget::Owned(pixmap) => pixmap,
|
|
}
|
|
}
|
|
|
|
pub fn render(&mut self, vm: &Vm, value: Value) -> Result<(), Exception> {
|
|
static NOT_A_SCRIBBLE: &str = "cannot draw something that is not a scribble";
|
|
let (_id, scribble) = vm
|
|
.get_ref_value(value)
|
|
.ok_or_else(|| Self::create_exception(vm, value, NOT_A_SCRIBBLE))?;
|
|
|
|
match &scribble {
|
|
Ref::List(list) => {
|
|
for element in &list.elements {
|
|
self.render(vm, *element)?;
|
|
}
|
|
}
|
|
Ref::Scribble(scribble) => match scribble {
|
|
Scribble::Stroke(stroke) => self.render_stroke(vm, value, stroke)?,
|
|
Scribble::Fill(fill) => self.render_fill(vm, value, fill)?,
|
|
},
|
|
_ => return Err(Self::create_exception(vm, value, NOT_A_SCRIBBLE))?,
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn shape_to_path(shape: &Shape) -> Option<Path> {
|
|
let mut pb = PathBuilder::new();
|
|
match shape {
|
|
Shape::Point(vec) => {
|
|
pb.move_to(vec.x, vec.y);
|
|
pb.line_to(vec.x, vec.y);
|
|
}
|
|
Shape::Line(start, end) => {
|
|
pb.move_to(start.x, start.y);
|
|
pb.line_to(end.x, end.y);
|
|
}
|
|
Shape::Rect(position, size) => {
|
|
if let Some(rect) =
|
|
tiny_skia::Rect::from_xywh(position.x, position.y, size.x, size.y)
|
|
{
|
|
pb.push_rect(rect);
|
|
}
|
|
}
|
|
Shape::Circle(position, radius) => {
|
|
pb.push_circle(position.x, position.y, *radius);
|
|
}
|
|
}
|
|
pb.finish()
|
|
}
|
|
|
|
fn render_stroke(&mut self, _vm: &Vm, _value: Value, stroke: &Stroke) -> Result<(), Exception> {
|
|
let paint = Paint {
|
|
shader: Shader::SolidColor(tiny_skia_color(stroke.color)),
|
|
..default_paint()
|
|
};
|
|
let transform = self.transform();
|
|
if let Some(path) = Self::shape_to_path(&stroke.shape) {
|
|
self.pixmap_mut().stroke_path(
|
|
&path,
|
|
&paint,
|
|
&SStroke {
|
|
width: stroke.thickness,
|
|
line_cap: LineCap::Round,
|
|
..Default::default()
|
|
},
|
|
transform,
|
|
None,
|
|
);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn render_fill(&mut self, _vm: &Vm, _value: Value, fill: &Fill) -> Result<(), Exception> {
|
|
let paint = Paint {
|
|
shader: Shader::SolidColor(tiny_skia_color(fill.color)),
|
|
..default_paint()
|
|
};
|
|
|
|
let transform = self.transform();
|
|
if let Some(path) = Self::shape_to_path(&fill.shape) {
|
|
self.pixmap_mut()
|
|
.fill_path(&path, &paint, FillRule::EvenOdd, transform, None);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
fn default_paint() -> Paint<'static> {
|
|
Paint {
|
|
shader: Shader::SolidColor(Color::BLACK),
|
|
blend_mode: BlendMode::SourceOver,
|
|
anti_alias: false,
|
|
force_hq_pipeline: false,
|
|
}
|
|
}
|
|
|
|
fn tiny_skia_color(color: Rgba) -> Color {
|
|
Color::from_rgba(
|
|
color.r.clamp(0.0, 1.0),
|
|
color.g.clamp(0.0, 1.0),
|
|
color.b.clamp(0.0, 1.0),
|
|
color.a.clamp(0.0, 1.0),
|
|
)
|
|
.unwrap()
|
|
}
|