rkgk/crates/haku/src/render.rs

150 lines
4.2 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::{
BlendMode, Color, LineCap, Paint, PathBuilder, Pixmap, Shader, Stroke as SStroke, Transform,
};
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,
}
pub struct Renderer {
2024-08-10 23:13:20 +02:00
pixmap_stack: Vec<Pixmap>,
transform_stack: Vec<Transform>,
2024-08-10 23:10:03 +02:00
}
impl Renderer {
2024-08-10 23:13:20 +02:00
pub fn new(pixmap: Pixmap, limits: &RendererLimits) -> Self {
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);
blend_stack.push(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 {
self.pixmap_stack.last_mut().unwrap()
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))?;
let Ref::Scribble(scribble) = scribble else {
return Err(Self::create_exception(vm, value, NOT_A_SCRIBBLE));
};
match scribble {
Scribble::Stroke(stroke) => self.render_stroke(vm, value, stroke)?,
}
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
}
}
Ok(())
}
2024-08-10 23:13:20 +02:00
pub fn finish(mut self) -> Pixmap {
self.pixmap_stack.drain(..).next().unwrap()
2024-08-10 23:10:03 +02:00
}
}
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()
}