diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index 825c040f24..7487713274 100644 --- a/frontend/src/app/render_wasm/api.cljs +++ b/frontend/src/app/render_wasm/api.cljs @@ -10,7 +10,6 @@ ["react-dom/server" :as rds] [app.common.data :as d] [app.common.data.macros :as dm] - [app.common.math :as mth] [app.common.types.shape.layout :as ctl] [app.common.types.shape.path :as path] [app.common.uuid :as uuid] @@ -24,6 +23,8 @@ [app.render-wasm.mem :as mem] [app.render-wasm.performance :as perf] [app.render-wasm.serializers :as sr] + [app.render-wasm.serializers.color :as sr-clr] + [app.render-wasm.serializers.fills :as sr-fills] [app.render-wasm.wasm :as wasm] [app.util.debug :as dbg] [app.util.http :as http] @@ -49,11 +50,6 @@ (def GRID-LAYOUT-ROW-ENTRY-SIZE 5) (def GRID-LAYOUT-COLUMN-ENTRY-SIZE 5) (def GRID-LAYOUT-CELL-ENTRY-SIZE 37) -(def GRADIENT-STOP-SIZE 5) - -(defn gradient-stop-get-entries-size - [stops] - (mem/get-list-size stops GRADIENT-STOP-SIZE)) (defn modifier-get-entries-size "Returns the list of a modifier list in bytes" @@ -104,24 +100,7 @@ (h/call wasm/internal-module "_render") (set! wasm/internal-frame-id nil)) -(defn- rgba-from-hex - "Takes a hex color in #rrggbb format, and an opacity value from 0 to 1 and returns its 32-bit rgba representation" - [hex opacity] - (let [rgb (js/parseInt (subs hex 1) 16) - a (mth/floor (* (or opacity 1) 0xff))] - ;; rgba >>> 0 so we have an unsigned representation - (unsigned-bit-shift-right (bit-or (bit-shift-left a 24) rgb) 0))) -(defn- rgba-bytes-from-hex - "Takes a hex color in #rrggbb format, and an opacity value from 0 to 1 and returns an array with its r g b a values" - [hex opacity] - (let [rgb (js/parseInt (subs hex 1) 16) - a (mth/floor (* (or opacity 1) 0xff)) - ;; rgba >>> 0 so we have an unsigned representation - r (bit-shift-right rgb 16) - g (bit-and (bit-shift-right rgb 8) 255) - b (bit-and rgb 255)] - [r g b a])) (defn cancel-render [_] @@ -241,35 +220,19 @@ image (:fill-image fill)] (cond (some? color) - (let [rgba (rgba-from-hex color opacity)] + (let [rgba (sr-clr/hex->u32argb color opacity)] (h/call wasm/internal-module "_add_shape_solid_fill" rgba)) (some? gradient) - (let [stops (:stops gradient) - size (gradient-stop-get-entries-size stops) + (let [size (sr-fills/gradient-byte-size gradient) offset (mem/alloc-bytes size) - heap (mem/get-heap-u8) - mem (js/Uint8Array. (.-buffer heap) offset size)] - (if (= (:type gradient) :linear) - (h/call wasm/internal-module "_add_shape_linear_fill" - (:start-x gradient) - (:start-y gradient) - (:end-x gradient) - (:end-y gradient) - opacity) - (h/call wasm/internal-module "_add_shape_radial_fill" - (:start-x gradient) - (:start-y gradient) - (:end-x gradient) - (:end-y gradient) - opacity - (:width gradient))) - (.set mem (js/Uint8Array. (clj->js (flatten (map (fn [stop] - (let [[r g b a] (rgba-bytes-from-hex (:color stop) (:opacity stop)) - offset (:offset stop)] - [r g b a (* 100 offset)])) - stops))))) - (h/call wasm/internal-module "_add_shape_fill_stops")) + heap (mem/get-heap-u32)] + (sr-fills/write-gradient-fill! offset heap gradient opacity) + (case (:type gradient) + :linear + (h/call wasm/internal-module "_add_shape_linear_fill") + :radial + (h/call wasm/internal-module "_add_shape_radial_fill"))) (some? image) (let [id (dm/get-prop image :id) @@ -307,31 +270,15 @@ (cond (some? gradient) - (let [stops (:stops gradient) - size (gradient-stop-get-entries-size stops) + (let [size (sr-fills/gradient-byte-size gradient) offset (mem/alloc-bytes size) - heap (mem/get-heap-u8) - mem (js/Uint8Array. (.-buffer heap) offset size)] - (if (= (:type gradient) :linear) - (h/call wasm/internal-module "_add_shape_stroke_linear_fill" - (:start-x gradient) - (:start-y gradient) - (:end-x gradient) - (:end-y gradient) - opacity) - (h/call wasm/internal-module "_add_shape_stroke_radial_fill" - (:start-x gradient) - (:start-y gradient) - (:end-x gradient) - (:end-y gradient) - opacity - (:width gradient))) - (.set mem (js/Uint8Array. (clj->js (flatten (map (fn [stop] - (let [[r g b a] (rgba-bytes-from-hex (:color stop) (:opacity stop)) - offset (:offset stop)] - [r g b a (* 100 offset)])) - stops))))) - (h/call wasm/internal-module "_add_shape_stroke_stops")) + heap (mem/get-heap-u32)] + (sr-fills/write-gradient-fill! offset heap gradient opacity) + (case (:type gradient) + :linear + (h/call wasm/internal-module "_add_shape_stroke_linear_fill") + :radial + (h/call wasm/internal-module "_add_shape_stroke_radial_fill"))) (some? image) (let [id (dm/get-prop image :id) @@ -349,7 +296,7 @@ (store-image id))) (some? color) - (let [rgba (rgba-from-hex color opacity)] + (let [rgba (sr-clr/hex->u32argb color opacity)] (h/call wasm/internal-module "_add_shape_stroke_solid_fill" rgba))))) strokes)) @@ -641,7 +588,7 @@ (let [shadow (nth shadows index) color (dm/get-prop shadow :color) blur (dm/get-prop shadow :blur) - rgba (rgba-from-hex (dm/get-prop color :color) (dm/get-prop color :opacity)) + rgba (sr-clr/hex->u32argb (dm/get-prop color :color) (dm/get-prop color :opacity)) hidden (dm/get-prop shadow :hidden) x (dm/get-prop shadow :offset-x) y (dm/get-prop shadow :offset-y) @@ -864,7 +811,7 @@ (defn set-canvas-background [background] - (let [rgba (rgba-from-hex background 1)] + (let [rgba (sr-clr/hex->u32argb background 1)] (h/call wasm/internal-module "_set_canvas_background" rgba) (request-render "set-canvas-background"))) @@ -893,7 +840,7 @@ (defn initialize [base-objects zoom vbox background] - (let [rgba (rgba-from-hex background 1)] + (let [rgba (sr-clr/hex->u32argb background 1)] (h/call wasm/internal-module "_set_canvas_background" rgba) (h/call wasm/internal-module "_set_view" zoom (- (:x vbox)) (- (:y vbox))) (set-objects base-objects))) @@ -952,5 +899,4 @@ (p/merr (fn [cause] (js/console.error cause) (p/resolved false))))) - (p/resolved false)))) - + (p/resolved false)))) \ No newline at end of file diff --git a/frontend/src/app/render_wasm/serializers/color.cljs b/frontend/src/app/render_wasm/serializers/color.cljs new file mode 100644 index 0000000000..35fda2b756 --- /dev/null +++ b/frontend/src/app/render_wasm/serializers/color.cljs @@ -0,0 +1,11 @@ +(ns app.render-wasm.serializers.color + (:require + [app.common.math :as mth])) + +(defn hex->u32argb + "Takes a hex color in #rrggbb format, and an opacity value from 0 to 1 and returns its 32-bit argb representation" + [hex opacity] + (let [rgb (js/parseInt (subs hex 1) 16) + a (mth/floor (* (or opacity 1) 0xff))] + ;; rgba >>> 0 so we have an unsigned representation + (unsigned-bit-shift-right (bit-or (bit-shift-left a 24) rgb) 0))) \ No newline at end of file diff --git a/frontend/src/app/render_wasm/serializers/fills.cljs b/frontend/src/app/render_wasm/serializers/fills.cljs new file mode 100644 index 0000000000..e841bb0eb7 --- /dev/null +++ b/frontend/src/app/render_wasm/serializers/fills.cljs @@ -0,0 +1,38 @@ +(ns app.render-wasm.serializers.fills + (:require + [app.render-wasm.serializers.color :as clr])) + +(def GRADIENT-STOP-SIZE 8) +(def GRADIENT-BASE-SIZE 24) + +(defn gradient-byte-size + [gradient] + (let [stops (:stops gradient)] + (+ GRADIENT-BASE-SIZE (* (count stops) GRADIENT-STOP-SIZE)))) + +(defn write-gradient-fill! + [offset heap gradient opacity] + (let [dview (js/DataView. (.-buffer heap)) + start-x (:start-x gradient) + start-y (:start-y gradient) + end-x (:end-x gradient) + end-y (:end-y gradient) + width (or (:width gradient) 0) + stops (:stops gradient)] + (.setFloat32 dview offset start-x true) + (.setFloat32 dview (+ offset 4) start-y true) + (.setFloat32 dview (+ offset 8) end-x true) + (.setFloat32 dview (+ offset 12) end-y true) + (.setFloat32 dview (+ offset 16) opacity true) + (.setFloat32 dview (+ offset 20) width true) + (loop [stops (seq stops) offset (+ offset GRADIENT-BASE-SIZE)] + (if (empty? stops) + offset + (let [stop (first stops) + hex-color (:color stop) + opacity (:opacity stop) + argb (clr/hex->u32argb hex-color opacity) + stop-offset (:offset stop)] + (.setUint32 dview offset argb true) + (.setFloat32 dview (+ offset 4) stop-offset true) + (recur (rest stops) (+ offset GRADIENT-STOP-SIZE))))))) \ No newline at end of file diff --git a/render-wasm/src/main.rs b/render-wasm/src/main.rs index 3334be6506..3ea0e1a6a1 100644 --- a/render-wasm/src/main.rs +++ b/render-wasm/src/main.rs @@ -246,68 +246,6 @@ pub extern "C" fn set_children() { } } -#[no_mangle] -pub extern "C" fn add_shape_solid_fill(raw_color: u32) { - with_current_shape!(state, |shape: &mut Shape| { - let color = skia::Color::new(raw_color); - shape.add_fill(shapes::Fill::Solid(color)); - }); -} - -#[no_mangle] -pub extern "C" fn add_shape_linear_fill( - start_x: f32, - start_y: f32, - end_x: f32, - end_y: f32, - opacity: f32, -) { - with_current_shape!(state, |shape: &mut Shape| { - shape.add_fill(shapes::Fill::new_linear_gradient( - (start_x, start_y), - (end_x, end_y), - opacity, - )); - }); -} - -#[no_mangle] -pub extern "C" fn add_shape_radial_fill( - start_x: f32, - start_y: f32, - end_x: f32, - end_y: f32, - opacity: f32, - width: f32, -) { - with_current_shape!(state, |shape: &mut Shape| { - shape.add_fill(shapes::Fill::new_radial_gradient( - (start_x, start_y), - (end_x, end_y), - opacity, - width, - )); - }); -} - -#[no_mangle] -pub extern "C" fn add_shape_fill_stops() { - let bytes = mem::bytes(); - - let entries: Vec<_> = bytes - .chunks(size_of::()) - .map(|data| shapes::RawStopData::from_bytes(data.try_into().unwrap())) - .collect(); - - with_current_shape!(state, |shape: &mut Shape| { - shape - .add_fill_gradient_stops(entries) - .expect("could not add gradient stops"); - }); - - mem::free_bytes(); -} - #[no_mangle] pub extern "C" fn store_image(a: u32, b: u32, c: u32, d: u32) { with_state!(state, { @@ -333,33 +271,6 @@ pub extern "C" fn is_image_cached(a: u32, b: u32, c: u32, d: u32) -> bool { }); } -#[no_mangle] -pub extern "C" fn add_shape_image_fill( - a: u32, - b: u32, - c: u32, - d: u32, - alpha: f32, - width: i32, - height: i32, -) { - with_current_shape!(state, |shape: &mut Shape| { - let id = uuid_from_u32_quartet(a, b, c, d); - shape.add_fill(shapes::Fill::new_image_fill( - id, - (alpha * 0xff as f32).floor() as u8, - (width, height), - )); - }); -} - -#[no_mangle] -pub extern "C" fn clear_shape_fills() { - with_current_shape!(state, |shape: &mut Shape| { - shape.clear_fills(); - }); -} - #[no_mangle] pub extern "C" fn set_shape_svg_raw_content() { with_current_shape!(state, |shape: &mut Shape| { @@ -430,101 +341,6 @@ pub extern "C" fn set_shape_path_content() { }); } -#[no_mangle] -pub extern "C" fn add_shape_center_stroke(width: f32, style: u8, cap_start: u8, cap_end: u8) { - with_current_shape!(state, |shape: &mut Shape| { - shape.add_stroke(shapes::Stroke::new_center_stroke( - width, style, cap_start, cap_end, - )); - }); -} - -#[no_mangle] -pub extern "C" fn add_shape_inner_stroke(width: f32, style: u8, cap_start: u8, cap_end: u8) { - with_current_shape!(state, |shape: &mut Shape| { - shape.add_stroke(shapes::Stroke::new_inner_stroke( - width, style, cap_start, cap_end, - )); - }); -} - -#[no_mangle] -pub extern "C" fn add_shape_outer_stroke(width: f32, style: u8, cap_start: u8, cap_end: u8) { - with_current_shape!(state, |shape: &mut Shape| { - shape.add_stroke(shapes::Stroke::new_outer_stroke( - width, style, cap_start, cap_end, - )); - }); -} - -#[no_mangle] -pub extern "C" fn add_shape_stroke_solid_fill(raw_color: u32) { - with_current_shape!(state, |shape: &mut Shape| { - let color = skia::Color::new(raw_color); - shape - .set_stroke_fill(shapes::Fill::Solid(color)) - .expect("could not add stroke solid fill"); - }); -} - -#[no_mangle] -pub extern "C" fn add_shape_stroke_linear_fill( - start_x: f32, - start_y: f32, - end_x: f32, - end_y: f32, - opacity: f32, -) { - with_current_shape!(state, |shape: &mut Shape| { - shape - .set_stroke_fill(shapes::Fill::new_linear_gradient( - (start_x, start_y), - (end_x, end_y), - opacity, - )) - .expect("could not add stroke linear fill"); - }); -} - -#[no_mangle] -pub extern "C" fn add_shape_stroke_radial_fill( - start_x: f32, - start_y: f32, - end_x: f32, - end_y: f32, - opacity: f32, - width: f32, -) { - with_current_shape!(state, |shape: &mut Shape| { - shape - .set_stroke_fill(shapes::Fill::new_radial_gradient( - (start_x, start_y), - (end_x, end_y), - opacity, - width, - )) - .expect("could not add stroke radial fill"); - }); -} - -#[no_mangle] -pub extern "C" fn add_shape_stroke_stops() { - let bytes = mem::bytes(); - - let entries: Vec<_> = bytes - .chunks(size_of::()) - .map(|data| shapes::RawStopData::from_bytes(data.try_into().unwrap())) - .collect(); - - with_current_shape!(state, |shape: &mut Shape| { - shape - .add_stroke_gradient_stops(entries) - .expect("could not add gradient stops"); - }); - - mem::free_bytes(); -} - // Extracts a string from the bytes slice until the next null byte (0) and returns the result as a `String`. // Updates the `start` index to the end of the extracted string. fn extract_string(start: &mut usize, bytes: &[u8]) -> String { @@ -543,35 +359,6 @@ fn extract_string(start: &mut usize, bytes: &[u8]) -> String { } } -#[no_mangle] -pub extern "C" fn add_shape_image_stroke( - a: u32, - b: u32, - c: u32, - d: u32, - alpha: f32, - width: i32, - height: i32, -) { - with_current_shape!(state, |shape: &mut Shape| { - let id = uuid_from_u32_quartet(a, b, c, d); - shape - .set_stroke_fill(shapes::Fill::new_image_fill( - id, - (alpha * 0xff as f32).floor() as u8, - (width, height), - )) - .expect("could not add stroke image fill"); - }); -} - -#[no_mangle] -pub extern "C" fn clear_shape_strokes() { - with_current_shape!(state, |shape: &mut Shape| { - shape.clear_strokes(); - }); -} - #[no_mangle] pub extern "C" fn set_shape_corners(r1: f32, r2: f32, r3: f32, r4: f32) { with_current_shape!(state, |shape: &mut Shape| { diff --git a/render-wasm/src/mem.rs b/render-wasm/src/mem.rs index b70bd42f4a..0b0edfd9c4 100644 --- a/render-wasm/src/mem.rs +++ b/render-wasm/src/mem.rs @@ -20,7 +20,7 @@ pub extern "C" fn alloc_bytes(len: usize) -> *mut u8 { if ptr.is_null() { panic!("Allocation failed"); } - // TODO: Esto quizá se podría eliminar. + // TODO: Maybe this could be removed. ptr::write_bytes(ptr, 0, len); *guard = Some(Box::new(Vec::from_raw_parts(ptr, len, len))); ptr diff --git a/render-wasm/src/shapes.rs b/render-wasm/src/shapes.rs index ac13d7f37f..5b44806b86 100644 --- a/render-wasm/src/shapes.rs +++ b/render-wasm/src/shapes.rs @@ -456,21 +456,6 @@ impl Shape { self.fills.clear(); } - pub fn add_fill_gradient_stops(&mut self, buffer: Vec) -> Result<(), String> { - let fill = self.fills.last_mut().ok_or("Shape has no fills")?; - let gradient = match fill { - Fill::LinearGradient(g) => Ok(g), - Fill::RadialGradient(g) => Ok(g), - _ => Err("Active fill is not a gradient"), - }?; - - for stop in buffer.into_iter() { - gradient.add_stop(stop.color(), stop.offset()); - } - - Ok(()) - } - pub fn strokes(&self) -> std::slice::Iter { self.strokes.iter() } @@ -485,22 +470,6 @@ impl Shape { Ok(()) } - pub fn add_stroke_gradient_stops(&mut self, buffer: Vec) -> Result<(), String> { - let stroke = self.strokes.last_mut().ok_or("Shape has no strokes")?; - let fill = &mut stroke.fill; - let gradient = match fill { - Fill::LinearGradient(g) => Ok(g), - Fill::RadialGradient(g) => Ok(g), - _ => Err("Active stroke is not a gradient"), - }?; - - for stop in buffer.into_iter() { - gradient.add_stop(stop.color(), stop.offset()); - } - - Ok(()) - } - pub fn clear_strokes(&mut self) { self.strokes.clear(); } diff --git a/render-wasm/src/shapes/fills.rs b/render-wasm/src/shapes/fills.rs index 42ffa889d6..78bbff701e 100644 --- a/render-wasm/src/shapes/fills.rs +++ b/render-wasm/src/shapes/fills.rs @@ -3,26 +3,74 @@ use skia_safe::{self as skia, Rect}; use super::Color; use crate::uuid::Uuid; +pub const RAW_FILL_DATA_SIZE: usize = 24; + +#[derive(Debug)] +#[repr(C)] +pub struct RawGradientData { + start_x: f32, + start_y: f32, + end_x: f32, + end_y: f32, + opacity: f32, + width: f32, +} + +impl From<[u8; RAW_FILL_DATA_SIZE]> for RawGradientData { + fn from(bytes: [u8; RAW_FILL_DATA_SIZE]) -> Self { + Self { + start_x: f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]), + start_y: f32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]), + end_x: f32::from_le_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]), + end_y: f32::from_le_bytes([bytes[12], bytes[13], bytes[14], bytes[15]]), + opacity: f32::from_le_bytes([bytes[16], bytes[17], bytes[18], bytes[19]]), + width: f32::from_le_bytes([bytes[20], bytes[21], bytes[22], bytes[23]]), + } + } +} + +impl RawGradientData { + pub fn start(&self) -> (f32, f32) { + (self.start_x, self.start_y) + } + + pub fn end(&self) -> (f32, f32) { + (self.end_x, self.end_y) + } + + pub fn opacity(&self) -> f32 { + self.opacity + } + + pub fn width(&self) -> f32 { + self.width + } +} + +pub const RAW_STOP_DATA_SIZE: usize = 8; + #[derive(Debug)] #[repr(C)] pub struct RawStopData { - color: [u8; 4], - offset: u8, + color: u32, + offset: f32, } impl RawStopData { pub fn color(&self) -> skia::Color { - skia::Color::from_argb(self.color[3], self.color[0], self.color[1], self.color[2]) + skia::Color::from(self.color) } pub fn offset(&self) -> f32 { - self.offset as f32 / 100.0 + self.offset } +} - pub fn from_bytes(bytes: [u8; 5]) -> Self { +impl From<[u8; RAW_STOP_DATA_SIZE]> for RawStopData { + fn from(bytes: [u8; RAW_STOP_DATA_SIZE]) -> Self { Self { - color: [bytes[0], bytes[1], bytes[2], bytes[3]], - offset: bytes[4], + color: u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]), + offset: f32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]), } } } @@ -96,6 +144,42 @@ impl Gradient { } } +impl TryFrom<&[u8]> for Gradient { + type Error = String; + + fn try_from(bytes: &[u8]) -> Result { + let raw_gradient_bytes: [u8; RAW_FILL_DATA_SIZE] = bytes[0..RAW_FILL_DATA_SIZE] + .try_into() + .map_err(|_| "Invalid gradient data".to_string())?; + + let raw_gradient = RawGradientData::from(raw_gradient_bytes); + let stops: Vec = bytes[RAW_FILL_DATA_SIZE..] + .chunks(RAW_STOP_DATA_SIZE) + .map(|chunk| { + let data: [u8; RAW_STOP_DATA_SIZE] = chunk + .try_into() + .map_err(|_| "Invalid stop data".to_string())?; + Ok(RawStopData::from(data)) + }) + .collect::, Self::Error>>()?; + + let mut gradient = Gradient { + start: raw_gradient.start(), + end: raw_gradient.end(), + opacity: raw_gradient.opacity(), + colors: vec![], + offsets: vec![], + width: raw_gradient.width(), + }; + + for stop in stops { + gradient.add_stop(stop.color(), stop.offset()); + } + + Ok(gradient) + } +} + #[derive(Debug, Clone, PartialEq)] pub struct ImageFill { id: Uuid, @@ -123,32 +207,6 @@ pub enum Fill { } impl Fill { - pub fn new_linear_gradient(start: (f32, f32), end: (f32, f32), opacity: f32) -> Self { - Self::LinearGradient(Gradient { - start, - end, - opacity, - colors: vec![], - offsets: vec![], - width: 0., - }) - } - pub fn new_radial_gradient( - start: (f32, f32), - end: (f32, f32), - opacity: f32, - width: f32, - ) -> Self { - Self::RadialGradient(Gradient { - start, - end, - opacity, - colors: vec![], - offsets: vec![], - width, - }) - } - pub fn new_image_fill(id: Uuid, opacity: u8, (width, height): (i32, i32)) -> Self { Self::Image(ImageFill { id, diff --git a/render-wasm/src/wasm.rs b/render-wasm/src/wasm.rs index 31ae8a7c1d..7d45923d91 100644 --- a/render-wasm/src/wasm.rs +++ b/render-wasm/src/wasm.rs @@ -1,2 +1,4 @@ +pub mod fills; pub mod fonts; +pub mod strokes; pub mod text; diff --git a/render-wasm/src/wasm/fills.rs b/render-wasm/src/wasm/fills.rs new file mode 100644 index 0000000000..a6c47fdf1c --- /dev/null +++ b/render-wasm/src/wasm/fills.rs @@ -0,0 +1,60 @@ +use skia_safe as skia; + +use crate::mem; +use crate::shapes; +use crate::utils::uuid_from_u32_quartet; +use crate::with_current_shape; +use crate::STATE; + +#[no_mangle] +pub extern "C" fn add_shape_solid_fill(raw_color: u32) { + with_current_shape!(state, |shape: &mut Shape| { + let color = skia::Color::new(raw_color); + shape.add_fill(shapes::Fill::Solid(color)); + }); +} + +#[no_mangle] +pub extern "C" fn add_shape_linear_fill() { + with_current_shape!(state, |shape: &mut Shape| { + let bytes = mem::bytes(); + let gradient = shapes::Gradient::try_from(&bytes[..]).expect("Invalid gradient data"); + shape.add_fill(shapes::Fill::LinearGradient(gradient)); + }); +} + +#[no_mangle] +pub extern "C" fn add_shape_radial_fill() { + with_current_shape!(state, |shape: &mut Shape| { + let bytes = mem::bytes(); + let gradient = shapes::Gradient::try_from(&bytes[..]).expect("Invalid gradient data"); + shape.add_fill(shapes::Fill::RadialGradient(gradient)); + }); +} + +#[no_mangle] +pub extern "C" fn add_shape_image_fill( + a: u32, + b: u32, + c: u32, + d: u32, + alpha: f32, + width: i32, + height: i32, +) { + with_current_shape!(state, |shape: &mut Shape| { + let id = uuid_from_u32_quartet(a, b, c, d); + shape.add_fill(shapes::Fill::new_image_fill( + id, + (alpha * 0xff as f32).floor() as u8, + (width, height), + )); + }); +} + +#[no_mangle] +pub extern "C" fn clear_shape_fills() { + with_current_shape!(state, |shape: &mut Shape| { + shape.clear_fills(); + }); +} diff --git a/render-wasm/src/wasm/strokes.rs b/render-wasm/src/wasm/strokes.rs new file mode 100644 index 0000000000..5aea834cfb --- /dev/null +++ b/render-wasm/src/wasm/strokes.rs @@ -0,0 +1,97 @@ +use skia_safe as skia; + +use crate::mem; +use crate::shapes; +use crate::utils::uuid_from_u32_quartet; +use crate::with_current_shape; +use crate::STATE; + +#[no_mangle] +pub extern "C" fn add_shape_center_stroke(width: f32, style: u8, cap_start: u8, cap_end: u8) { + with_current_shape!(state, |shape: &mut Shape| { + shape.add_stroke(shapes::Stroke::new_center_stroke( + width, style, cap_start, cap_end, + )); + }); +} + +#[no_mangle] +pub extern "C" fn add_shape_inner_stroke(width: f32, style: u8, cap_start: u8, cap_end: u8) { + with_current_shape!(state, |shape: &mut Shape| { + shape.add_stroke(shapes::Stroke::new_inner_stroke( + width, style, cap_start, cap_end, + )); + }); +} + +#[no_mangle] +pub extern "C" fn add_shape_outer_stroke(width: f32, style: u8, cap_start: u8, cap_end: u8) { + with_current_shape!(state, |shape: &mut Shape| { + shape.add_stroke(shapes::Stroke::new_outer_stroke( + width, style, cap_start, cap_end, + )); + }); +} + +#[no_mangle] +pub extern "C" fn add_shape_stroke_solid_fill(raw_color: u32) { + with_current_shape!(state, |shape: &mut Shape| { + let color = skia::Color::new(raw_color); + shape + .set_stroke_fill(shapes::Fill::Solid(color)) + .expect("could not add stroke solid fill"); + }); +} + +#[no_mangle] +pub extern "C" fn add_shape_stroke_linear_fill() { + with_current_shape!(state, |shape: &mut Shape| { + let bytes = mem::bytes(); + let gradient = shapes::Gradient::try_from(&bytes[..]).expect("Invalid gradient data"); + + shape + .set_stroke_fill(shapes::Fill::LinearGradient(gradient)) + .expect("could not add stroke linear gradient fill"); + }); +} + +#[no_mangle] +pub extern "C" fn add_shape_stroke_radial_fill() { + with_current_shape!(state, |shape: &mut Shape| { + let bytes = mem::bytes(); + let gradient = shapes::Gradient::try_from(&bytes[..]).expect("Invalid gradient data"); + + shape + .set_stroke_fill(shapes::Fill::RadialGradient(gradient)) + .expect("could not add stroke radial gradient fill"); + }); +} + +#[no_mangle] +pub extern "C" fn add_shape_image_stroke( + a: u32, + b: u32, + c: u32, + d: u32, + alpha: f32, + width: i32, + height: i32, +) { + with_current_shape!(state, |shape: &mut Shape| { + let id = uuid_from_u32_quartet(a, b, c, d); + shape + .set_stroke_fill(shapes::Fill::new_image_fill( + id, + (alpha * 0xff as f32).floor() as u8, + (width, height), + )) + .expect("could not add stroke image fill"); + }); +} + +#[no_mangle] +pub extern "C" fn clear_shape_strokes() { + with_current_shape!(state, |shape: &mut Shape| { + shape.clear_strokes(); + }); +}