rkgk/crates/haku/src/render.rs

200 lines
5.8 KiB
Rust
Raw Normal View History

2024-08-10 23:10:03 +02:00
use alloc::vec::Vec;
2024-08-10 23:13:20 +02:00
use tiny_skia::{
2024-08-21 21:11:30 +02:00
BlendMode, Color, LineCap, Paint, PathBuilder, Pixmap, Rect, Shader, Stroke as SStroke,
Transform,
2024-08-10 23:13:20 +02:00
};
2024-08-10 23:10:03 +02:00
use crate::{
2024-08-10 23:13:20 +02:00
value::{Ref, Rgba, Scribble, Shape, Stroke, Value},
2024-08-10 23:10:03 +02:00
vm::{Exception, Vm},
};
2024-08-10 23:13:20 +02:00
pub use tiny_skia;
2024-08-10 23:10:03 +02:00
pub struct RendererLimits {
2024-08-10 23:13:20 +02:00
pub pixmap_stack_capacity: usize,
2024-08-10 23:10:03 +02:00
pub transform_stack_capacity: usize,
}
2024-08-15 20:01:23 +02:00
pub enum RenderTarget<'a> {
Borrowed(&'a mut Pixmap),
Owned(Pixmap),
}
pub struct Renderer<'a> {
pixmap_stack: Vec<RenderTarget<'a>>,
2024-08-10 23:13:20 +02:00
transform_stack: Vec<Transform>,
2024-08-10 23:10:03 +02:00
}
2024-08-15 20:01:23 +02:00
impl<'a> Renderer<'a> {
pub fn new(pixmap: &'a mut Pixmap, limits: &RendererLimits) -> Self {
2024-08-10 23:13:20 +02:00
assert!(limits.pixmap_stack_capacity > 0);
2024-08-10 23:10:03 +02:00
assert!(limits.transform_stack_capacity > 0);
2024-08-10 23:13:20 +02:00
let mut blend_stack = Vec::with_capacity(limits.pixmap_stack_capacity);
2024-08-15 20:01:23 +02:00
blend_stack.push(RenderTarget::Borrowed(pixmap));
2024-08-10 23:10:03 +02:00
let mut transform_stack = Vec::with_capacity(limits.transform_stack_capacity);
2024-08-10 23:13:20 +02:00
transform_stack.push(Transform::identity());
2024-08-10 23:10:03 +02:00
Self {
2024-08-10 23:13:20 +02:00
pixmap_stack: blend_stack,
2024-08-10 23:10:03 +02:00
transform_stack,
}
}
fn create_exception(_vm: &Vm, _at: Value, message: &'static str) -> Exception {
Exception { message }
}
2024-08-10 23:13:20 +02:00
fn transform(&self) -> Transform {
self.transform_stack.last().copied().unwrap()
2024-08-10 23:10:03 +02:00
}
2024-08-10 23:13:20 +02:00
fn transform_mut(&mut self) -> &mut Transform {
2024-08-10 23:10:03 +02:00
self.transform_stack.last_mut().unwrap()
}
2024-08-10 23:13:20 +02:00
pub fn translate(&mut self, x: f32, y: f32) {
let translated = self.transform().post_translate(x, y);
*self.transform_mut() = translated;
2024-08-10 23:10:03 +02:00
}
2024-08-10 23:13:20 +02:00
fn pixmap_mut(&mut self) -> &mut Pixmap {
2024-08-15 20:01:23 +02:00
match self.pixmap_stack.last_mut().unwrap() {
RenderTarget::Borrowed(pixmap) => pixmap,
RenderTarget::Owned(pixmap) => pixmap,
}
2024-08-10 23:10:03 +02:00
}
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))?;
2024-08-20 23:00:39 +02:00
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)?,
},
_ => return Err(Self::create_exception(vm, value, NOT_A_SCRIBBLE))?,
2024-08-10 23:10:03 +02:00
}
Ok(())
}
fn render_stroke(&mut self, _vm: &Vm, _value: Value, stroke: &Stroke) -> Result<(), Exception> {
2024-08-10 23:13:20 +02:00
let paint = Paint {
shader: Shader::SolidColor(tiny_skia_color(stroke.color)),
..default_paint()
};
let transform = self.transform();
2024-08-10 23:10:03 +02:00
match stroke.shape {
Shape::Point(vec) => {
2024-08-10 23:13:20 +02:00
let mut pb = PathBuilder::new();
pb.move_to(vec.x, vec.y);
pb.line_to(vec.x, vec.y);
let path = pb.finish().unwrap();
self.pixmap_mut().stroke_path(
&path,
&paint,
&SStroke {
width: stroke.thickness,
line_cap: LineCap::Square,
..Default::default()
},
transform,
None,
);
}
Shape::Line(start, end) => {
let mut pb = PathBuilder::new();
pb.move_to(start.x, start.y);
pb.line_to(end.x, end.y);
let path = pb.finish().unwrap();
self.pixmap_mut().stroke_path(
&path,
&paint,
&SStroke {
width: stroke.thickness,
line_cap: LineCap::Square,
..Default::default()
},
transform,
None,
);
2024-08-10 23:10:03 +02:00
}
2024-08-21 21:11:30 +02:00
Shape::Rect(position, size) => {
let mut pb = PathBuilder::new();
if let Some(rect) =
tiny_skia::Rect::from_xywh(position.x, position.y, size.x, size.y)
{
pb.push_rect(rect);
}
let path = pb.finish().unwrap();
self.pixmap_mut().stroke_path(
&path,
&paint,
&SStroke {
width: stroke.thickness,
line_cap: LineCap::Square,
..Default::default()
},
transform,
None,
);
}
Shape::Circle(position, radius) => {
let mut pb = PathBuilder::new();
pb.push_circle(position.x, position.y, radius);
let path = pb.finish().unwrap();
self.pixmap_mut().stroke_path(
&path,
&paint,
&SStroke {
width: stroke.thickness,
line_cap: LineCap::Square,
..Default::default()
},
transform,
None,
);
}
2024-08-10 23:10:03 +02:00
}
Ok(())
}
}
2024-08-10 23:13:20 +02:00
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()
}