mirror of
https://github.com/penpot/penpot.git
synced 2025-05-20 02:06:11 +02:00
✨ Add support for WASM transforms
This commit is contained in:
parent
a3a757f842
commit
1bb337c3dd
18 changed files with 658 additions and 153 deletions
|
@ -1,7 +1,11 @@
|
|||
use std::collections::HashSet;
|
||||
use uuid::Uuid;
|
||||
|
||||
use skia_safe as skia;
|
||||
|
||||
mod debug;
|
||||
mod math;
|
||||
mod matrix;
|
||||
mod mem;
|
||||
mod render;
|
||||
mod shapes;
|
||||
|
@ -9,8 +13,8 @@ mod state;
|
|||
mod utils;
|
||||
mod view;
|
||||
|
||||
use crate::shapes::{BoolType, Kind, Path};
|
||||
|
||||
use crate::mem::SerializableResult;
|
||||
use crate::shapes::{BoolType, Kind, Path, TransformEntry};
|
||||
use crate::state::State;
|
||||
use crate::utils::uuid_from_u32_quartet;
|
||||
|
||||
|
@ -590,6 +594,67 @@ pub extern "C" fn set_shape_path_attrs(num_attrs: u32) {
|
|||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn propagate_modifiers() -> *mut u8 {
|
||||
let bytes = mem::bytes();
|
||||
|
||||
let mut entries: Vec<_> = bytes
|
||||
.chunks(size_of::<TransformEntry>())
|
||||
.map(|data| TransformEntry::from_bytes(data.try_into().unwrap()))
|
||||
.collect();
|
||||
|
||||
let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer");
|
||||
|
||||
let mut processed = HashSet::<Uuid>::new();
|
||||
|
||||
let mut result = Vec::<TransformEntry>::new();
|
||||
|
||||
// Propagate the transform to children
|
||||
while let Some(entry) = entries.pop() {
|
||||
if !processed.contains(&entry.id) {
|
||||
if let Some(shape) = state.shapes.get(&entry.id) {
|
||||
let mut children: Vec<TransformEntry> = shape
|
||||
.children
|
||||
.iter()
|
||||
.map(|id| TransformEntry {
|
||||
id: id.clone(),
|
||||
transform: entry.transform,
|
||||
})
|
||||
.collect();
|
||||
|
||||
entries.append(&mut children);
|
||||
|
||||
processed.insert(entry.id);
|
||||
result.push(entry.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
mem::write_vec(result)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn clean_modifiers() {
|
||||
if let Some(state) = unsafe { STATE.as_mut() } {
|
||||
state.modifiers.clear();
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn set_modifiers() {
|
||||
let bytes = mem::bytes();
|
||||
|
||||
let entries: Vec<_> = bytes
|
||||
.chunks(size_of::<TransformEntry>())
|
||||
.map(|data| TransformEntry::from_bytes(data.try_into().unwrap()))
|
||||
.collect();
|
||||
|
||||
let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer");
|
||||
|
||||
for entry in entries {
|
||||
state.modifiers.insert(entry.id, entry.transform);
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn add_shape_shadow(
|
||||
raw_color: u32,
|
||||
|
|
121
render-wasm/src/matrix.rs
Normal file
121
render-wasm/src/matrix.rs
Normal file
|
@ -0,0 +1,121 @@
|
|||
// allowing dead code so we can have some API's that are not used yet without warnings
|
||||
#![allow(dead_code)]
|
||||
use skia_safe as skia;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Matrix {
|
||||
pub a: f32,
|
||||
pub b: f32,
|
||||
pub c: f32,
|
||||
pub d: f32,
|
||||
pub e: f32,
|
||||
pub f: f32,
|
||||
}
|
||||
|
||||
impl Matrix {
|
||||
pub fn new(a: f32, b: f32, c: f32, d: f32, e: f32, f: f32) -> Self {
|
||||
Self { a, b, c, d, e, f }
|
||||
}
|
||||
|
||||
pub fn translate(x: f32, y: f32) -> Self {
|
||||
Self::new(0.0, 0.0, 0.0, 0.0, x, y)
|
||||
}
|
||||
|
||||
pub fn identity() -> Self {
|
||||
Self {
|
||||
a: 1.,
|
||||
b: 0.,
|
||||
c: 0.,
|
||||
d: 1.,
|
||||
e: 0.,
|
||||
f: 0.,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_skia_matrix(&self) -> skia::Matrix {
|
||||
let mut res = skia::Matrix::new_identity();
|
||||
|
||||
let (translate_x, translate_y) = self.translation();
|
||||
let (scale_x, scale_y) = self.scale();
|
||||
let (skew_x, skew_y) = self.skew();
|
||||
res.set_all(
|
||||
scale_x,
|
||||
skew_x,
|
||||
translate_x,
|
||||
skew_y,
|
||||
scale_y,
|
||||
translate_y,
|
||||
0.,
|
||||
0.,
|
||||
1.,
|
||||
);
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
pub fn no_translation(&self) -> Self {
|
||||
let mut res = Self::identity();
|
||||
res.c = self.c;
|
||||
res.b = self.b;
|
||||
res.a = self.a;
|
||||
res.d = self.d;
|
||||
res
|
||||
}
|
||||
|
||||
fn translation(&self) -> (f32, f32) {
|
||||
(self.e, self.f)
|
||||
}
|
||||
|
||||
fn scale(&self) -> (f32, f32) {
|
||||
(self.a, self.d)
|
||||
}
|
||||
|
||||
fn skew(&self) -> (f32, f32) {
|
||||
(self.c, self.b)
|
||||
}
|
||||
|
||||
pub fn product(&self, other: &Matrix) -> Matrix {
|
||||
let a = self.a * other.a + self.c * other.b;
|
||||
let b = self.b * other.a + self.d * other.b;
|
||||
let c = self.a * other.c + self.c * other.d;
|
||||
let d = self.b * other.c + self.d * other.d;
|
||||
let e = self.a * other.e + self.c * other.f + self.e;
|
||||
let f = self.b * other.e + self.d * other.f + self.f;
|
||||
Matrix::new(a, b, c, d, e, f)
|
||||
}
|
||||
|
||||
pub fn as_bytes(&self) -> [u8; 24] {
|
||||
let mut result = [0; 24];
|
||||
result[0..4].clone_from_slice(&self.a.to_le_bytes());
|
||||
result[4..8].clone_from_slice(&self.b.to_le_bytes());
|
||||
result[8..12].clone_from_slice(&self.c.to_le_bytes());
|
||||
result[12..16].clone_from_slice(&self.d.to_le_bytes());
|
||||
result[16..20].clone_from_slice(&self.e.to_le_bytes());
|
||||
result[20..24].clone_from_slice(&self.f.to_le_bytes());
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_product() {
|
||||
let a = Matrix::new(1.0, 2.0, 3.0, 4.0, 5.0, 6.0);
|
||||
let b = Matrix::new(6.0, 5.0, 4.0, 3.0, 2.0, 1.0);
|
||||
|
||||
assert_eq!(
|
||||
a.product(&b),
|
||||
Matrix::new(21.0, 32.0, 13.0, 20.0, 10.0, 14.0)
|
||||
);
|
||||
|
||||
let a = Matrix::new(7.0, 4.0, 8.0, 3.0, 9.0, 5.0);
|
||||
let b = Matrix::new(7.0, 4.0, 8.0, 3.0, 9.0, 5.0);
|
||||
|
||||
assert_eq!(
|
||||
a.product(&b),
|
||||
Matrix::new(81.0, 40.0, 80.0, 41.0, 112.0, 56.0)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -14,7 +14,20 @@ pub extern "C" fn alloc_bytes(len: usize) -> *mut u8 {
|
|||
return ptr;
|
||||
}
|
||||
|
||||
pub fn free_bytes() {
|
||||
pub fn write_bytes(bytes: Vec<u8>) -> *mut u8 {
|
||||
if unsafe { BUFFERU8.is_some() } {
|
||||
panic!("Bytes already allocated");
|
||||
}
|
||||
|
||||
let mut buffer = Box::new(bytes);
|
||||
let ptr = buffer.as_mut_ptr();
|
||||
|
||||
unsafe { BUFFERU8 = Some(buffer) };
|
||||
return ptr;
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn free_bytes() {
|
||||
if unsafe { BUFFERU8.is_some() } {
|
||||
let buffer = unsafe { BUFFERU8.take() }.expect("uninitialized buffer");
|
||||
std::mem::drop(buffer);
|
||||
|
@ -30,3 +43,31 @@ pub fn bytes() -> Vec<u8> {
|
|||
let buffer = unsafe { BUFFERU8.take() }.expect("uninitialized buffer");
|
||||
*buffer
|
||||
}
|
||||
|
||||
pub trait SerializableResult {
|
||||
type BytesType;
|
||||
fn from_bytes(bytes: Self::BytesType) -> Self;
|
||||
fn as_bytes(&self) -> Self::BytesType;
|
||||
fn clone_to_slice(&self, slice: &mut [u8]);
|
||||
}
|
||||
|
||||
/*
|
||||
Returns an array in the heap. The first 4 bytes is always the size
|
||||
of the array. Then the items are serialized one after the other
|
||||
by the implementation of SerializableResult trait
|
||||
*/
|
||||
pub fn write_vec<T: SerializableResult>(result: Vec<T>) -> *mut u8 {
|
||||
let elem_size = size_of::<T>();
|
||||
let bytes_len = 4 + result.len() * elem_size;
|
||||
let mut result_bytes = Vec::<u8>::with_capacity(bytes_len);
|
||||
|
||||
result_bytes.resize(bytes_len, 0);
|
||||
result_bytes[0..4].clone_from_slice(&result.len().to_le_bytes());
|
||||
|
||||
for i in 0..result.len() {
|
||||
let base = 4 + i * elem_size;
|
||||
result[i].clone_to_slice(&mut result_bytes[base..base + elem_size]);
|
||||
}
|
||||
|
||||
write_bytes(result_bytes)
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ use std::collections::HashMap;
|
|||
use uuid::Uuid;
|
||||
|
||||
use crate::math;
|
||||
use crate::matrix::Matrix;
|
||||
use crate::view::Viewbox;
|
||||
|
||||
mod blend;
|
||||
|
@ -232,7 +233,18 @@ impl RenderState {
|
|||
.clear(skia::Color::TRANSPARENT);
|
||||
}
|
||||
|
||||
pub fn render_shape(&mut self, shape: &mut Shape, clip_bounds: Option<skia::Rect>) {
|
||||
pub fn render_shape(
|
||||
&mut self,
|
||||
shape: &mut Shape,
|
||||
modifiers: Option<&Matrix>,
|
||||
clip_bounds: Option<skia::Rect>,
|
||||
) {
|
||||
if let Some(modifiers) = modifiers {
|
||||
self.drawing_surface
|
||||
.canvas()
|
||||
.concat(&modifiers.to_skia_matrix());
|
||||
}
|
||||
|
||||
let transform = shape.transform.to_skia_matrix();
|
||||
|
||||
// Check transform-matrix code from common/src/app/common/geom/shapes/transforms.cljc
|
||||
|
@ -289,6 +301,7 @@ impl RenderState {
|
|||
pub fn start_render_loop(
|
||||
&mut self,
|
||||
tree: &mut HashMap<Uuid, Shape>,
|
||||
modifiers: &HashMap<Uuid, Matrix>,
|
||||
timestamp: i32,
|
||||
) -> Result<(), String> {
|
||||
if self.render_in_progress {
|
||||
|
@ -304,7 +317,7 @@ impl RenderState {
|
|||
self.translate(self.viewbox.pan_x, self.viewbox.pan_y);
|
||||
self.pending_nodes = vec![(Uuid::nil(), false, None)];
|
||||
self.render_in_progress = true;
|
||||
self.process_animation_frame(tree, timestamp)?;
|
||||
self.process_animation_frame(tree, modifiers, timestamp)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -325,10 +338,11 @@ impl RenderState {
|
|||
pub fn process_animation_frame(
|
||||
&mut self,
|
||||
tree: &mut HashMap<Uuid, Shape>,
|
||||
modifiers: &HashMap<Uuid, Matrix>,
|
||||
timestamp: i32,
|
||||
) -> Result<(), String> {
|
||||
if self.render_in_progress {
|
||||
self.render_shape_tree(tree, timestamp)?;
|
||||
self.render_shape_tree(tree, modifiers, timestamp)?;
|
||||
if self.render_in_progress {
|
||||
if let Some(frame_id) = self.render_request_id {
|
||||
self.cancel_animation_frame(frame_id);
|
||||
|
@ -395,6 +409,7 @@ impl RenderState {
|
|||
pub fn render_shape_tree(
|
||||
&mut self,
|
||||
tree: &HashMap<Uuid, Shape>,
|
||||
modifiers: &HashMap<Uuid, Matrix>,
|
||||
timestamp: i32,
|
||||
) -> Result<(), String> {
|
||||
if !self.render_in_progress {
|
||||
|
@ -432,7 +447,11 @@ impl RenderState {
|
|||
|
||||
self.drawing_surface.canvas().save();
|
||||
if !node_id.is_nil() {
|
||||
self.render_shape(&mut element.clone(), clip_bounds);
|
||||
self.render_shape(
|
||||
&mut element.clone(),
|
||||
modifiers.get(&element.id),
|
||||
clip_bounds,
|
||||
);
|
||||
} else {
|
||||
self.apply_drawing_to_render_canvas();
|
||||
}
|
||||
|
|
|
@ -3,25 +3,26 @@ use skia_safe as skia;
|
|||
use std::collections::HashMap;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::matrix::Matrix;
|
||||
use crate::render::BlendMode;
|
||||
|
||||
mod blurs;
|
||||
mod bools;
|
||||
mod fills;
|
||||
mod matrix;
|
||||
mod paths;
|
||||
mod shadows;
|
||||
mod strokes;
|
||||
mod svgraw;
|
||||
mod transform;
|
||||
|
||||
pub use blurs::*;
|
||||
pub use bools::*;
|
||||
pub use fills::*;
|
||||
use matrix::*;
|
||||
pub use paths::*;
|
||||
pub use shadows::*;
|
||||
pub use strokes::*;
|
||||
pub use svgraw::*;
|
||||
pub use transform::*;
|
||||
|
||||
pub type CornerRadius = skia::Point;
|
||||
pub type Corners = [CornerRadius; 4];
|
||||
|
|
|
@ -1,70 +0,0 @@
|
|||
use skia_safe as skia;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Matrix {
|
||||
pub a: f32,
|
||||
pub b: f32,
|
||||
pub c: f32,
|
||||
pub d: f32,
|
||||
pub e: f32,
|
||||
pub f: f32,
|
||||
}
|
||||
|
||||
impl Matrix {
|
||||
pub fn new(a: f32, b: f32, c: f32, d: f32, e: f32, f: f32) -> Self {
|
||||
Self { a, b, c, d, e, f }
|
||||
}
|
||||
|
||||
pub fn identity() -> Self {
|
||||
Self {
|
||||
a: 1.,
|
||||
b: 0.,
|
||||
c: 0.,
|
||||
d: 1.,
|
||||
e: 0.,
|
||||
f: 0.,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_skia_matrix(&self) -> skia::Matrix {
|
||||
let mut res = skia::Matrix::new_identity();
|
||||
|
||||
let (translate_x, translate_y) = self.translation();
|
||||
let (scale_x, scale_y) = self.scale();
|
||||
let (skew_x, skew_y) = self.skew();
|
||||
res.set_all(
|
||||
scale_x,
|
||||
skew_x,
|
||||
translate_x,
|
||||
skew_y,
|
||||
scale_y,
|
||||
translate_y,
|
||||
0.,
|
||||
0.,
|
||||
1.,
|
||||
);
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
pub fn no_translation(&self) -> Self {
|
||||
let mut res = Self::identity();
|
||||
res.c = self.c;
|
||||
res.b = self.b;
|
||||
res.a = self.a;
|
||||
res.d = self.d;
|
||||
res
|
||||
}
|
||||
|
||||
fn translation(&self) -> (f32, f32) {
|
||||
(self.e, self.f)
|
||||
}
|
||||
|
||||
fn scale(&self) -> (f32, f32) {
|
||||
(self.a, self.d)
|
||||
}
|
||||
|
||||
fn skew(&self) -> (f32, f32) {
|
||||
(self.c, self.b)
|
||||
}
|
||||
}
|
71
render-wasm/src/shapes/transform.rs
Normal file
71
render-wasm/src/shapes/transform.rs
Normal file
|
@ -0,0 +1,71 @@
|
|||
use uuid::Uuid;
|
||||
|
||||
use crate::matrix::Matrix;
|
||||
use crate::mem::SerializableResult;
|
||||
use crate::utils::{uuid_from_u32_quartet, uuid_to_u32_quartet};
|
||||
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct TransformEntry {
|
||||
pub id: Uuid,
|
||||
pub transform: Matrix,
|
||||
}
|
||||
|
||||
impl SerializableResult for TransformEntry {
|
||||
type BytesType = [u8; size_of::<TransformEntry>()];
|
||||
|
||||
fn from_bytes(bytes: Self::BytesType) -> Self {
|
||||
let id = uuid_from_u32_quartet(
|
||||
u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]),
|
||||
u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]),
|
||||
u32::from_le_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]),
|
||||
u32::from_le_bytes([bytes[12], bytes[13], bytes[14], bytes[15]]),
|
||||
);
|
||||
|
||||
let transform = Matrix::new(
|
||||
f32::from_le_bytes([bytes[16], bytes[17], bytes[18], bytes[19]]),
|
||||
f32::from_le_bytes([bytes[20], bytes[21], bytes[22], bytes[23]]),
|
||||
f32::from_le_bytes([bytes[24], bytes[25], bytes[26], bytes[27]]),
|
||||
f32::from_le_bytes([bytes[28], bytes[29], bytes[30], bytes[31]]),
|
||||
f32::from_le_bytes([bytes[32], bytes[33], bytes[34], bytes[35]]),
|
||||
f32::from_le_bytes([bytes[36], bytes[37], bytes[38], bytes[39]]),
|
||||
);
|
||||
|
||||
TransformEntry { id, transform }
|
||||
}
|
||||
|
||||
fn as_bytes(&self) -> Self::BytesType {
|
||||
let mut result: [u8; 40] = [0; 40];
|
||||
let (a, b, c, d) = uuid_to_u32_quartet(&self.id);
|
||||
result[0..4].clone_from_slice(&a.to_le_bytes());
|
||||
result[4..8].clone_from_slice(&b.to_le_bytes());
|
||||
result[8..12].clone_from_slice(&c.to_le_bytes());
|
||||
result[12..16].clone_from_slice(&d.to_le_bytes());
|
||||
result[16..40].clone_from_slice(&self.transform.as_bytes());
|
||||
result
|
||||
}
|
||||
|
||||
// The generic trait doesn't know the size of the array. This is why the
|
||||
// clone needs to be here even if it could be generic.
|
||||
fn clone_to_slice(&self, slice: &mut [u8]) {
|
||||
slice.clone_from_slice(&self.as_bytes());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use uuid::uuid;
|
||||
|
||||
#[test]
|
||||
fn test_serialization() {
|
||||
let entry = TransformEntry {
|
||||
id: uuid!("550e8400-e29b-41d4-a716-446655440000"),
|
||||
transform: Matrix::new(1.0, 2.0, 3.0, 4.0, 5.0, 6.0),
|
||||
};
|
||||
|
||||
let bytes = entry.as_bytes();
|
||||
|
||||
assert_eq!(entry, TransformEntry::from_bytes(bytes));
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use crate::matrix;
|
||||
use skia_safe as skia;
|
||||
use uuid::Uuid;
|
||||
|
||||
|
@ -16,6 +17,7 @@ pub(crate) struct State<'a> {
|
|||
pub current_id: Option<Uuid>,
|
||||
pub current_shape: Option<&'a mut Shape>,
|
||||
pub shapes: HashMap<Uuid, Shape>,
|
||||
pub modifiers: HashMap<Uuid, matrix::Matrix>,
|
||||
}
|
||||
|
||||
impl<'a> State<'a> {
|
||||
|
@ -25,6 +27,7 @@ impl<'a> State<'a> {
|
|||
current_id: None,
|
||||
current_shape: None,
|
||||
shapes: HashMap::with_capacity(capacity),
|
||||
modifiers: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -38,13 +41,13 @@ impl<'a> State<'a> {
|
|||
|
||||
pub fn start_render_loop(&mut self, timestamp: i32) -> Result<(), String> {
|
||||
self.render_state
|
||||
.start_render_loop(&mut self.shapes, timestamp)?;
|
||||
.start_render_loop(&mut self.shapes, &self.modifiers, timestamp)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn process_animation_frame(&mut self, timestamp: i32) -> Result<(), String> {
|
||||
self.render_state
|
||||
.process_animation_frame(&mut self.shapes, timestamp)?;
|
||||
.process_animation_frame(&mut self.shapes, &self.modifiers, timestamp)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
@ -5,3 +5,12 @@ pub fn uuid_from_u32_quartet(a: u32, b: u32, c: u32, d: u32) -> Uuid {
|
|||
let lo: u64 = ((c as u64) << 32) | d as u64;
|
||||
Uuid::from_u64_pair(hi, lo)
|
||||
}
|
||||
|
||||
pub fn uuid_to_u32_quartet(id: &Uuid) -> (u32, u32, u32, u32) {
|
||||
let (hi, lo) = id.as_u64_pair();
|
||||
let hihi32 = (hi >> 32) as u32;
|
||||
let hilo32 = hi as u32;
|
||||
let lohi32 = (lo >> 32) as u32;
|
||||
let lolo32 = lo as u32;
|
||||
(hihi32, hilo32, lohi32, lolo32)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue