From fccd1a5bd755d86702d28172619d714242ae1e8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Albeza?= Date: Mon, 14 Apr 2025 15:39:43 +0200 Subject: [PATCH 01/11] :sparkles: Send fill + stops data in one call for linear fills --- frontend/src/app/render_wasm/api.cljs | 74 +++++++----------- frontend/src/app/render_wasm/serializers.cljs | 20 +++++ render-wasm/src/main.rs | 35 ++++++--- render-wasm/src/shapes/fills.rs | 76 +++++++++++++++++-- 4 files changed, 143 insertions(+), 62 deletions(-) diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index f660c25b6..5d0dacd1d 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] @@ -50,6 +49,7 @@ (def GRID-LAYOUT-COLUMN-ENTRY-SIZE 5) (def GRID-LAYOUT-CELL-ENTRY-SIZE 37) (def GRADIENT-STOP-SIZE 5) +(def LINEAR-FILL-BASE-SIZE 21) (defn gradient-stop-get-entries-size [stops] @@ -104,24 +104,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 +224,37 @@ image (:fill-image fill)] (cond (some? color) - (let [rgba (rgba-from-hex color opacity)] + (let [rgba (sr/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) - 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) + (case (:type gradient) + :linear + (let [stops (:stops gradient) + size (+ LINEAR-FILL-BASE-SIZE (* (count stops) GRADIENT-STOP-SIZE)) + offset (mem/alloc-bytes size) + heap (mem/get-heap-u8)] + (sr/serialize-linear-fill gradient opacity heap offset) + (h/call wasm/internal-module "_add_shape_linear_fill")) + :radial + (let [stops (:stops gradient) + size (gradient-stop-get-entries-size stops) + offset (mem/alloc-bytes size) + heap (mem/get-heap-u8) + mem (js/Uint8Array. (.-buffer heap) offset size)] (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")) + (:width gradient)) + (.set mem (js/Uint8Array. (clj->js (flatten (map (fn [stop] + (let [[r g b a] (sr/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"))) (some? image) (let [id (dm/get-prop image :id) @@ -327,7 +312,7 @@ 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)) + (let [[r g b a] (sr/rgba-bytes-from-hex (:color stop) (:opacity stop)) offset (:offset stop)] [r g b a (* 100 offset)])) stops))))) @@ -349,7 +334,7 @@ (store-image id))) (some? color) - (let [rgba (rgba-from-hex color opacity)] + (let [rgba (sr/hex->u32argb color opacity)] (h/call wasm/internal-module "_add_shape_stroke_solid_fill" rgba))))) strokes)) @@ -641,7 +626,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/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 +849,7 @@ (defn set-canvas-background [background] - (let [rgba (rgba-from-hex background 1)] + (let [rgba (sr/hex->u32argb background 1)] (h/call wasm/internal-module "_set_canvas_background" rgba) (request-render "set-canvas-background"))) @@ -893,7 +878,7 @@ (defn initialize [base-objects zoom vbox background] - (let [rgba (rgba-from-hex background 1)] + (let [rgba (sr/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 +937,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.cljs b/frontend/src/app/render_wasm/serializers.cljs index e42ff8c45..a977a69b5 100644 --- a/frontend/src/app/render_wasm/serializers.cljs +++ b/frontend/src/app/render_wasm/serializers.cljs @@ -7,9 +7,29 @@ (ns app.render-wasm.serializers (:require [app.common.data.macros :as dm] + [app.common.math :as mth] [app.common.uuid :as uuid] [cuerdas.core :as str])) +(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))) + +(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 u8 [value] (let [u8-arr (js/Uint8Array. 1)] diff --git a/render-wasm/src/main.rs b/render-wasm/src/main.rs index 3334be650..839c94f25 100644 --- a/render-wasm/src/main.rs +++ b/render-wasm/src/main.rs @@ -16,7 +16,10 @@ mod wapi; mod wasm; use crate::mem::SerializableResult; -use crate::shapes::{BoolType, ConstraintH, ConstraintV, StructureEntry, TransformEntry, Type}; +use crate::shapes::{ + BoolType, ConstraintH, ConstraintV, StructureEntry, TransformEntry, Type, + RAW_LINEAR_FILL_DATA_SIZE, RAW_STOP_DATA_SIZE, +}; use crate::utils::uuid_from_u32_quartet; use crate::uuid::Uuid; use indexmap::IndexSet; @@ -255,18 +258,26 @@ pub extern "C" fn add_shape_solid_fill(raw_color: u32) { } #[no_mangle] -pub extern "C" fn add_shape_linear_fill( - start_x: f32, - start_y: f32, - end_x: f32, - end_y: f32, - opacity: f32, -) { +pub extern "C" fn add_shape_linear_fill() { with_current_shape!(state, |shape: &mut Shape| { - shape.add_fill(shapes::Fill::new_linear_gradient( - (start_x, start_y), - (end_x, end_y), - opacity, + let stops_offset = RAW_LINEAR_FILL_DATA_SIZE; + let bytes = mem::bytes(); + let raw_fill_data: [u8; RAW_LINEAR_FILL_DATA_SIZE] = + bytes[0..stops_offset].try_into().unwrap(); + let raw_fill = shapes::RawLinearFillData::from(raw_fill_data); + let stops: Vec = bytes[stops_offset..] + .chunks(RAW_STOP_DATA_SIZE) + .map(|chunk| { + let data: [u8; RAW_STOP_DATA_SIZE] = chunk.try_into().unwrap(); + shapes::RawStopData::from(data) + }) + .collect(); + + shape.add_fill(shapes::Fill::new_linear_gradient_with_stops( + raw_fill.start(), + raw_fill.end(), + raw_fill.opacity(), + stops, )); }); } diff --git a/render-wasm/src/shapes/fills.rs b/render-wasm/src/shapes/fills.rs index 42ffa889d..870425263 100644 --- a/render-wasm/src/shapes/fills.rs +++ b/render-wasm/src/shapes/fills.rs @@ -3,16 +3,58 @@ use skia_safe::{self as skia, Rect}; use super::Color; use crate::uuid::Uuid; +pub const RAW_LINEAR_FILL_DATA_SIZE: usize = 21; + +#[derive(Debug)] +#[repr(C)] +pub struct RawLinearFillData { + start_x: f32, + start_y: f32, + end_x: f32, + end_y: f32, + opacity: f32, + stop_count: u8, +} + +impl From<[u8; 21]> for RawLinearFillData { + fn from(bytes: [u8; 21]) -> 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]]), + stop_count: bytes[20], + } + } +} + +impl RawLinearFillData { + 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 const RAW_STOP_DATA_SIZE: usize = 5; + #[derive(Debug)] #[repr(C)] pub struct RawStopData { - color: [u8; 4], + color: u32, offset: u8, } 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 { @@ -20,13 +62,21 @@ impl RawStopData { } pub fn from_bytes(bytes: [u8; 5]) -> Self { + let color_bytes: [u8; 4] = bytes[0..4].try_into().unwrap(); Self { - color: [bytes[0], bytes[1], bytes[2], bytes[3]], + color: u32::from_le_bytes(color_bytes), offset: bytes[4], } } } +impl From<[u8; 5]> for RawStopData { + // TODO: remove from_bytes and copy its implementation here + fn from(bytes: [u8; 5]) -> Self { + Self::from_bytes(bytes) + } +} + #[derive(Debug, Clone, PartialEq)] pub struct Gradient { colors: Vec, @@ -124,15 +174,31 @@ pub enum Fill { impl Fill { pub fn new_linear_gradient(start: (f32, f32), end: (f32, f32), opacity: f32) -> Self { - Self::LinearGradient(Gradient { + Self::new_linear_gradient_with_stops(start, end, opacity, vec![]) + } + + pub fn new_linear_gradient_with_stops( + start: (f32, f32), + end: (f32, f32), + opacity: f32, + stops: Vec, + ) -> Self { + let mut gradient = Gradient { start, end, opacity, colors: vec![], offsets: vec![], width: 0., - }) + }; + + for stop in stops { + gradient.add_stop(stop.color(), stop.offset()); + } + + Self::LinearGradient(gradient) } + pub fn new_radial_gradient( start: (f32, f32), end: (f32, f32), From f40ef26c69af34b66fe8bebec99df06bca14133f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Albeza?= Date: Mon, 14 Apr 2025 16:09:07 +0200 Subject: [PATCH 02/11] :recycle: Move color and fill serializers to their own sub-namespaces --- frontend/src/app/render_wasm/api.cljs | 18 ++++++----- frontend/src/app/render_wasm/serializers.cljs | 18 ----------- .../app/render_wasm/serializers/color.cljs | 22 ++++++++++++++ .../app/render_wasm/serializers/fills.cljs | 30 +++++++++++++++++++ 4 files changed, 62 insertions(+), 26 deletions(-) create mode 100644 frontend/src/app/render_wasm/serializers/color.cljs create mode 100644 frontend/src/app/render_wasm/serializers/fills.cljs diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index 5d0dacd1d..ce7503a09 100644 --- a/frontend/src/app/render_wasm/api.cljs +++ b/frontend/src/app/render_wasm/api.cljs @@ -23,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] @@ -224,7 +226,7 @@ image (:fill-image fill)] (cond (some? color) - (let [rgba (sr/hex->u32argb color opacity)] + (let [rgba (sr-clr/hex->u32argb color opacity)] (h/call wasm/internal-module "_add_shape_solid_fill" rgba)) (some? gradient) @@ -234,7 +236,7 @@ size (+ LINEAR-FILL-BASE-SIZE (* (count stops) GRADIENT-STOP-SIZE)) offset (mem/alloc-bytes size) heap (mem/get-heap-u8)] - (sr/serialize-linear-fill gradient opacity heap offset) + (sr-fills/serialize-linear-fill gradient opacity heap offset) (h/call wasm/internal-module "_add_shape_linear_fill")) :radial (let [stops (:stops gradient) @@ -250,7 +252,7 @@ opacity (:width gradient)) (.set mem (js/Uint8Array. (clj->js (flatten (map (fn [stop] - (let [[r g b a] (sr/rgba-bytes-from-hex (:color stop) (:opacity stop)) + (let [[r g b a] (sr-clr/rgba-bytes-from-hex (:color stop) (:opacity stop)) offset (:offset stop)] [r g b a (* 100 offset)])) stops))))) @@ -312,7 +314,7 @@ opacity (:width gradient))) (.set mem (js/Uint8Array. (clj->js (flatten (map (fn [stop] - (let [[r g b a] (sr/rgba-bytes-from-hex (:color stop) (:opacity stop)) + (let [[r g b a] (sr-clr/rgba-bytes-from-hex (:color stop) (:opacity stop)) offset (:offset stop)] [r g b a (* 100 offset)])) stops))))) @@ -334,7 +336,7 @@ (store-image id))) (some? color) - (let [rgba (sr/hex->u32argb color opacity)] + (let [rgba (sr-clr/hex->u32argb color opacity)] (h/call wasm/internal-module "_add_shape_stroke_solid_fill" rgba))))) strokes)) @@ -626,7 +628,7 @@ (let [shadow (nth shadows index) color (dm/get-prop shadow :color) blur (dm/get-prop shadow :blur) - rgba (sr/hex->u32argb (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) @@ -849,7 +851,7 @@ (defn set-canvas-background [background] - (let [rgba (sr/hex->u32argb background 1)] + (let [rgba (sr-clr/hex->u32argb background 1)] (h/call wasm/internal-module "_set_canvas_background" rgba) (request-render "set-canvas-background"))) @@ -878,7 +880,7 @@ (defn initialize [base-objects zoom vbox background] - (let [rgba (sr/hex->u32argb 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))) diff --git a/frontend/src/app/render_wasm/serializers.cljs b/frontend/src/app/render_wasm/serializers.cljs index a977a69b5..0574b850a 100644 --- a/frontend/src/app/render_wasm/serializers.cljs +++ b/frontend/src/app/render_wasm/serializers.cljs @@ -7,28 +7,10 @@ (ns app.render-wasm.serializers (:require [app.common.data.macros :as dm] - [app.common.math :as mth] [app.common.uuid :as uuid] [cuerdas.core :as str])) -(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))) -(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 u8 [value] 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 000000000..af7fe36f4 --- /dev/null +++ b/frontend/src/app/render_wasm/serializers/color.cljs @@ -0,0 +1,22 @@ +(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))) + +(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])) \ 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 000000000..3864cb642 --- /dev/null +++ b/frontend/src/app/render_wasm/serializers/fills.cljs @@ -0,0 +1,30 @@ +(ns app.render-wasm.serializers.fills + (:require + [app.common.data.macros :as dm] + [app.render-wasm.serializers.color :as clr])) + +(defn serialize-linear-fill + [gradient opacity heap-u8 offset] + (let [dview (js/DataView. (.-buffer heap-u8)) + start-x (dm/get-prop gradient :start-x) + start-y (dm/get-prop gradient :start-y) + end-x (dm/get-prop gradient :end-x) + end-y (dm/get-prop gradient :end-y) + stops (dm/get-prop gradient :stops)] + (.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) + (.setUint8 dview (+ offset 20) (count stops)) + (loop [stops (seq stops) idx 0] + (when-not (empty? stops) + (let [stop (first stops) + hex-color (dm/get-prop stop :color) + opacity (dm/get-prop stop :opacity) + rgba (clr/hex->u32argb hex-color opacity) + stop-offset (* 100 (dm/get-prop stop :offset)) + dview-offset (+ (* idx 5) offset 21)] + (.setUint32 dview dview-offset rgba true) + (.setUint8 dview (+ dview-offset 4) stop-offset) + (recur (rest stops) (+ idx 1))))))) \ No newline at end of file From abcd050c69f9bbf6199a54e0185f569d99d84c02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Albeza?= Date: Mon, 14 Apr 2025 16:38:43 +0200 Subject: [PATCH 03/11] :recycle: Adapt linear gradient type so it can be used for radial too (wasm) --- frontend/src/app/render_wasm/api.cljs | 13 ++++++++----- frontend/src/app/render_wasm/serializers.cljs | 2 -- .../app/render_wasm/serializers/fills.cljs | 10 +++++++--- render-wasm/src/main.rs | 19 +++++++++---------- render-wasm/src/mem.rs | 2 +- render-wasm/src/shapes/fills.rs | 14 +++++++------- 6 files changed, 32 insertions(+), 28 deletions(-) diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index ce7503a09..cd3ebbeb5 100644 --- a/frontend/src/app/render_wasm/api.cljs +++ b/frontend/src/app/render_wasm/api.cljs @@ -50,12 +50,16 @@ (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) -(def LINEAR-FILL-BASE-SIZE 21) + (defn gradient-stop-get-entries-size [stops] - (mem/get-list-size stops GRADIENT-STOP-SIZE)) + (mem/get-list-size stops sr-fills/GRADIENT-STOP-SIZE)) + +(defn gradient-byte-size + [gradient] + (let [stops (:stops gradient)] + (+ sr-fills/GRADIENT-BASE-SIZE (* (count stops) sr-fills/GRADIENT-STOP-SIZE)))) (defn modifier-get-entries-size "Returns the list of a modifier list in bytes" @@ -232,8 +236,7 @@ (some? gradient) (case (:type gradient) :linear - (let [stops (:stops gradient) - size (+ LINEAR-FILL-BASE-SIZE (* (count stops) GRADIENT-STOP-SIZE)) + (let [size (gradient-byte-size gradient) offset (mem/alloc-bytes size) heap (mem/get-heap-u8)] (sr-fills/serialize-linear-fill gradient opacity heap offset) diff --git a/frontend/src/app/render_wasm/serializers.cljs b/frontend/src/app/render_wasm/serializers.cljs index 0574b850a..e42ff8c45 100644 --- a/frontend/src/app/render_wasm/serializers.cljs +++ b/frontend/src/app/render_wasm/serializers.cljs @@ -10,8 +10,6 @@ [app.common.uuid :as uuid] [cuerdas.core :as str])) - - (defn u8 [value] (let [u8-arr (js/Uint8Array. 1)] diff --git a/frontend/src/app/render_wasm/serializers/fills.cljs b/frontend/src/app/render_wasm/serializers/fills.cljs index 3864cb642..194080d23 100644 --- a/frontend/src/app/render_wasm/serializers/fills.cljs +++ b/frontend/src/app/render_wasm/serializers/fills.cljs @@ -3,6 +3,9 @@ [app.common.data.macros :as dm] [app.render-wasm.serializers.color :as clr])) +(def GRADIENT-STOP-SIZE 5) +(def GRADIENT-BASE-SIZE 24) + (defn serialize-linear-fill [gradient opacity heap-u8 offset] (let [dview (js/DataView. (.-buffer heap-u8)) @@ -10,13 +13,14 @@ start-y (dm/get-prop gradient :start-y) end-x (dm/get-prop gradient :end-x) end-y (dm/get-prop gradient :end-y) - stops (dm/get-prop gradient :stops)] + stops (dm/get-prop gradient :stops) + width 0] (.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) - (.setUint8 dview (+ offset 20) (count stops)) + (.setFloat32 dview (+ offset 20) width true) (loop [stops (seq stops) idx 0] (when-not (empty? stops) (let [stop (first stops) @@ -24,7 +28,7 @@ opacity (dm/get-prop stop :opacity) rgba (clr/hex->u32argb hex-color opacity) stop-offset (* 100 (dm/get-prop stop :offset)) - dview-offset (+ (* idx 5) offset 21)] + dview-offset (+ (* idx 5) offset 24)] (.setUint32 dview dview-offset rgba true) (.setUint8 dview (+ dview-offset 4) stop-offset) (recur (rest stops) (+ idx 1))))))) \ No newline at end of file diff --git a/render-wasm/src/main.rs b/render-wasm/src/main.rs index 839c94f25..291d76e79 100644 --- a/render-wasm/src/main.rs +++ b/render-wasm/src/main.rs @@ -17,8 +17,8 @@ mod wasm; use crate::mem::SerializableResult; use crate::shapes::{ - BoolType, ConstraintH, ConstraintV, StructureEntry, TransformEntry, Type, - RAW_LINEAR_FILL_DATA_SIZE, RAW_STOP_DATA_SIZE, + BoolType, ConstraintH, ConstraintV, StructureEntry, TransformEntry, Type, RAW_FILL_DATA_SIZE, + RAW_STOP_DATA_SIZE, }; use crate::utils::uuid_from_u32_quartet; use crate::uuid::Uuid; @@ -260,12 +260,11 @@ pub extern "C" fn add_shape_solid_fill(raw_color: u32) { #[no_mangle] pub extern "C" fn add_shape_linear_fill() { with_current_shape!(state, |shape: &mut Shape| { - let stops_offset = RAW_LINEAR_FILL_DATA_SIZE; let bytes = mem::bytes(); - let raw_fill_data: [u8; RAW_LINEAR_FILL_DATA_SIZE] = - bytes[0..stops_offset].try_into().unwrap(); - let raw_fill = shapes::RawLinearFillData::from(raw_fill_data); - let stops: Vec = bytes[stops_offset..] + let raw_gradient_bytes: [u8; RAW_FILL_DATA_SIZE] = + bytes[0..RAW_FILL_DATA_SIZE].try_into().unwrap(); + let raw_gradient = shapes::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().unwrap(); @@ -274,9 +273,9 @@ pub extern "C" fn add_shape_linear_fill() { .collect(); shape.add_fill(shapes::Fill::new_linear_gradient_with_stops( - raw_fill.start(), - raw_fill.end(), - raw_fill.opacity(), + raw_gradient.start(), + raw_gradient.end(), + raw_gradient.opacity(), stops, )); }); diff --git a/render-wasm/src/mem.rs b/render-wasm/src/mem.rs index b70bd42f4..0b0edfd9c 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/fills.rs b/render-wasm/src/shapes/fills.rs index 870425263..af6daa84a 100644 --- a/render-wasm/src/shapes/fills.rs +++ b/render-wasm/src/shapes/fills.rs @@ -3,33 +3,33 @@ use skia_safe::{self as skia, Rect}; use super::Color; use crate::uuid::Uuid; -pub const RAW_LINEAR_FILL_DATA_SIZE: usize = 21; +pub const RAW_FILL_DATA_SIZE: usize = 24; #[derive(Debug)] #[repr(C)] -pub struct RawLinearFillData { +pub struct RawGradientData { start_x: f32, start_y: f32, end_x: f32, end_y: f32, opacity: f32, - stop_count: u8, + width: f32, } -impl From<[u8; 21]> for RawLinearFillData { - fn from(bytes: [u8; 21]) -> Self { +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]]), - stop_count: bytes[20], + width: f32::from_le_bytes([bytes[20], bytes[21], bytes[22], bytes[23]]), } } } -impl RawLinearFillData { +impl RawGradientData { pub fn start(&self) -> (f32, f32) { (self.start_x, self.start_y) } From 5765d1c56c013bcc8ef9bdb2b2af52f978a0f02e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Albeza?= Date: Tue, 15 Apr 2025 11:04:36 +0200 Subject: [PATCH 04/11] :recycle: Switch to a f32 offset for gradient stops --- frontend/src/app/render_wasm/api.cljs | 17 ++++------ .../app/render_wasm/serializers/fills.cljs | 32 +++++++++++-------- render-wasm/src/main.rs | 10 ++++-- render-wasm/src/shapes/fills.rs | 24 ++++++-------- 4 files changed, 41 insertions(+), 42 deletions(-) diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index cd3ebbeb5..3fc36ccce 100644 --- a/frontend/src/app/render_wasm/api.cljs +++ b/frontend/src/app/render_wasm/api.cljs @@ -51,7 +51,7 @@ (def GRID-LAYOUT-COLUMN-ENTRY-SIZE 5) (def GRID-LAYOUT-CELL-ENTRY-SIZE 37) - +;; FIXME: use `gradient-byte-size` instead (defn gradient-stop-get-entries-size [stops] (mem/get-list-size stops sr-fills/GRADIENT-STOP-SIZE)) @@ -238,7 +238,7 @@ :linear (let [size (gradient-byte-size gradient) offset (mem/alloc-bytes size) - heap (mem/get-heap-u8)] + heap (mem/get-heap-u32)] (sr-fills/serialize-linear-fill gradient opacity heap offset) (h/call wasm/internal-module "_add_shape_linear_fill")) :radial @@ -297,11 +297,10 @@ (cond (some? gradient) - (let [stops (:stops gradient) - size (gradient-stop-get-entries-size stops) + (let [stops (:stops gradient) + size (gradient-stop-get-entries-size stops) offset (mem/alloc-bytes size) - heap (mem/get-heap-u8) - mem (js/Uint8Array. (.-buffer heap) offset size)] + heap (mem/get-heap-u8)] (if (= (:type gradient) :linear) (h/call wasm/internal-module "_add_shape_stroke_linear_fill" (:start-x gradient) @@ -316,11 +315,7 @@ (:end-y gradient) opacity (:width gradient))) - (.set mem (js/Uint8Array. (clj->js (flatten (map (fn [stop] - (let [[r g b a] (sr-clr/rgba-bytes-from-hex (:color stop) (:opacity stop)) - offset (:offset stop)] - [r g b a (* 100 offset)])) - stops))))) + (sr-fills/serialize-gradient-stops stops heap offset) (h/call wasm/internal-module "_add_shape_stroke_stops")) (some? image) diff --git a/frontend/src/app/render_wasm/serializers/fills.cljs b/frontend/src/app/render_wasm/serializers/fills.cljs index 194080d23..d92079459 100644 --- a/frontend/src/app/render_wasm/serializers/fills.cljs +++ b/frontend/src/app/render_wasm/serializers/fills.cljs @@ -3,12 +3,26 @@ [app.common.data.macros :as dm] [app.render-wasm.serializers.color :as clr])) -(def GRADIENT-STOP-SIZE 5) +(def GRADIENT-STOP-SIZE 8) (def GRADIENT-BASE-SIZE 24) +(defn serialize-gradient-stops + [stops heap offset] + (let [dview (js/DataView. (.-buffer heap))] + (loop [stops (seq stops) offset offset] + (when-not (empty? stops) + (let [stop (first stops) + hex-color (dm/get-prop stop :color) + opacity (dm/get-prop stop :opacity) + argb (clr/hex->u32argb hex-color opacity) + stop-offset (dm/get-prop stop :offset)] + (.setUint32 dview offset argb true) + (.setFloat32 dview (+ offset 4) stop-offset true) + (recur (rest stops) (+ offset GRADIENT-STOP-SIZE))))))) + (defn serialize-linear-fill - [gradient opacity heap-u8 offset] - (let [dview (js/DataView. (.-buffer heap-u8)) + [gradient opacity heap offset] + (let [dview (js/DataView. (.-buffer heap)) start-x (dm/get-prop gradient :start-x) start-y (dm/get-prop gradient :start-y) end-x (dm/get-prop gradient :end-x) @@ -21,14 +35,4 @@ (.setFloat32 dview (+ offset 12) end-y true) (.setFloat32 dview (+ offset 16) opacity true) (.setFloat32 dview (+ offset 20) width true) - (loop [stops (seq stops) idx 0] - (when-not (empty? stops) - (let [stop (first stops) - hex-color (dm/get-prop stop :color) - opacity (dm/get-prop stop :opacity) - rgba (clr/hex->u32argb hex-color opacity) - stop-offset (* 100 (dm/get-prop stop :offset)) - dview-offset (+ (* idx 5) offset 24)] - (.setUint32 dview dview-offset rgba true) - (.setUint8 dview (+ dview-offset 4) stop-offset) - (recur (rest stops) (+ idx 1))))))) \ No newline at end of file + (serialize-gradient-stops stops heap (+ offset GRADIENT-BASE-SIZE)))) \ No newline at end of file diff --git a/render-wasm/src/main.rs b/render-wasm/src/main.rs index 291d76e79..d37088b10 100644 --- a/render-wasm/src/main.rs +++ b/render-wasm/src/main.rs @@ -306,7 +306,10 @@ pub extern "C" fn add_shape_fill_stops() { let entries: Vec<_> = bytes .chunks(size_of::()) - .map(|data| shapes::RawStopData::from_bytes(data.try_into().unwrap())) + .map(|data| { + let raw_stop_bytes: [u8; RAW_STOP_DATA_SIZE] = data.try_into().unwrap(); + shapes::RawStopData::from(raw_stop_bytes) + }) .collect(); with_current_shape!(state, |shape: &mut Shape| { @@ -523,7 +526,10 @@ pub extern "C" fn add_shape_stroke_stops() { let entries: Vec<_> = bytes .chunks(size_of::()) - .map(|data| shapes::RawStopData::from_bytes(data.try_into().unwrap())) + .map(|data| { + let raw_stop_bytes: [u8; RAW_STOP_DATA_SIZE] = data.try_into().unwrap(); + shapes::RawStopData::from(raw_stop_bytes) + }) .collect(); with_current_shape!(state, |shape: &mut Shape| { diff --git a/render-wasm/src/shapes/fills.rs b/render-wasm/src/shapes/fills.rs index af6daa84a..370d049fa 100644 --- a/render-wasm/src/shapes/fills.rs +++ b/render-wasm/src/shapes/fills.rs @@ -43,13 +43,13 @@ impl RawGradientData { } } -pub const RAW_STOP_DATA_SIZE: usize = 5; +pub const RAW_STOP_DATA_SIZE: usize = 8; #[derive(Debug)] #[repr(C)] pub struct RawStopData { color: u32, - offset: u8, + offset: f32, } impl RawStopData { @@ -58,22 +58,16 @@ impl RawStopData { } pub fn offset(&self) -> f32 { - self.offset as f32 / 100.0 - } - - pub fn from_bytes(bytes: [u8; 5]) -> Self { - let color_bytes: [u8; 4] = bytes[0..4].try_into().unwrap(); - Self { - color: u32::from_le_bytes(color_bytes), - offset: bytes[4], - } + self.offset } } -impl From<[u8; 5]> for RawStopData { - // TODO: remove from_bytes and copy its implementation here - fn from(bytes: [u8; 5]) -> Self { - Self::from_bytes(bytes) +impl From<[u8; RAW_STOP_DATA_SIZE]> for RawStopData { + fn from(bytes: [u8; RAW_STOP_DATA_SIZE]) -> Self { + Self { + 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]]), + } } } From dc3d802d3de7fa52baded0d2fcc3332a3fe31041 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Albeza?= Date: Tue, 15 Apr 2025 11:53:09 +0200 Subject: [PATCH 05/11] :tada: Serialize radial fills in one go --- frontend/src/app/render_wasm/api.cljs | 25 ++++----------- .../app/render_wasm/serializers/fills.cljs | 17 ++++++++++ render-wasm/src/main.rs | 32 +++++++++++-------- render-wasm/src/shapes/fills.rs | 24 ++++++++++++-- 4 files changed, 65 insertions(+), 33 deletions(-) diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index 3fc36ccce..9e37c4322 100644 --- a/frontend/src/app/render_wasm/api.cljs +++ b/frontend/src/app/render_wasm/api.cljs @@ -242,24 +242,11 @@ (sr-fills/serialize-linear-fill gradient opacity heap offset) (h/call wasm/internal-module "_add_shape_linear_fill")) :radial - (let [stops (:stops gradient) - size (gradient-stop-get-entries-size stops) + (let [size (gradient-byte-size gradient) offset (mem/alloc-bytes size) - heap (mem/get-heap-u8) - mem (js/Uint8Array. (.-buffer heap) offset size)] - (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] (sr-clr/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/serialize-radial-fill gradient opacity heap offset) + (h/call wasm/internal-module "_add_shape_radial_fill"))) (some? image) (let [id (dm/get-prop image :id) @@ -301,13 +288,15 @@ size (gradient-stop-get-entries-size stops) offset (mem/alloc-bytes size) heap (mem/get-heap-u8)] - (if (= (:type gradient) :linear) + (case (: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) + :radial (h/call wasm/internal-module "_add_shape_stroke_radial_fill" (:start-x gradient) (:start-y gradient) diff --git a/frontend/src/app/render_wasm/serializers/fills.cljs b/frontend/src/app/render_wasm/serializers/fills.cljs index d92079459..cada7416a 100644 --- a/frontend/src/app/render_wasm/serializers/fills.cljs +++ b/frontend/src/app/render_wasm/serializers/fills.cljs @@ -35,4 +35,21 @@ (.setFloat32 dview (+ offset 12) end-y true) (.setFloat32 dview (+ offset 16) opacity true) (.setFloat32 dview (+ offset 20) width true) + (serialize-gradient-stops stops heap (+ offset GRADIENT-BASE-SIZE)))) + +(defn serialize-radial-fill + [gradient opacity heap offset] + (let [dview (js/DataView. (.-buffer heap)) + start-x (dm/get-prop gradient :start-x) + start-y (dm/get-prop gradient :start-y) + end-x (dm/get-prop gradient :end-x) + end-y (dm/get-prop gradient :end-y) + stops (dm/get-prop gradient :stops) + width (dm/get-prop gradient :width)] + (.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) (serialize-gradient-stops stops heap (+ offset GRADIENT-BASE-SIZE)))) \ No newline at end of file diff --git a/render-wasm/src/main.rs b/render-wasm/src/main.rs index d37088b10..66e5961ec 100644 --- a/render-wasm/src/main.rs +++ b/render-wasm/src/main.rs @@ -282,20 +282,26 @@ pub extern "C" fn add_shape_linear_fill() { } #[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, -) { +pub extern "C" fn add_shape_radial_fill() { 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, + let bytes = mem::bytes(); + let raw_gradient_bytes: [u8; RAW_FILL_DATA_SIZE] = + bytes[0..RAW_FILL_DATA_SIZE].try_into().unwrap(); + let raw_gradient = shapes::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().unwrap(); + shapes::RawStopData::from(data) + }) + .collect(); + + shape.add_fill(shapes::Fill::new_radial_gradient_with_stops( + raw_gradient.start(), + raw_gradient.end(), + raw_gradient.opacity(), + raw_gradient.width(), + stops, )); }); } diff --git a/render-wasm/src/shapes/fills.rs b/render-wasm/src/shapes/fills.rs index 370d049fa..f19cd107a 100644 --- a/render-wasm/src/shapes/fills.rs +++ b/render-wasm/src/shapes/fills.rs @@ -41,6 +41,10 @@ impl RawGradientData { pub fn opacity(&self) -> f32 { self.opacity } + + pub fn width(&self) -> f32 { + self.width + } } pub const RAW_STOP_DATA_SIZE: usize = 8; @@ -199,14 +203,30 @@ impl Fill { opacity: f32, width: f32, ) -> Self { - Self::RadialGradient(Gradient { + Self::new_radial_gradient_with_stops(start, end, opacity, width, vec![]) + } + + pub fn new_radial_gradient_with_stops( + start: (f32, f32), + end: (f32, f32), + opacity: f32, + width: f32, + stops: Vec, + ) -> Self { + let mut gradient = Gradient { start, end, opacity, colors: vec![], offsets: vec![], width, - }) + }; + + for stop in stops { + gradient.add_stop(stop.color(), stop.offset()); + } + + Self::RadialGradient(gradient) } pub fn new_image_fill(id: Uuid, opacity: u8, (width, height): (i32, i32)) -> Self { From 1f58f96e88a89f5417f958c3d168b6a3c0137d1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Albeza?= Date: Tue, 15 Apr 2025 12:16:40 +0200 Subject: [PATCH 06/11] :recycle: Refactor serializing gradient fill --- frontend/src/app/render_wasm/api.cljs | 43 ++++-------- .../app/render_wasm/serializers/fills.cljs | 56 +++++----------- render-wasm/src/main.rs | 65 +++++++++++-------- render-wasm/src/shapes/fills.rs | 13 ---- 4 files changed, 69 insertions(+), 108 deletions(-) diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index 9e37c4322..44321c28e 100644 --- a/frontend/src/app/render_wasm/api.cljs +++ b/frontend/src/app/render_wasm/api.cljs @@ -234,18 +234,14 @@ (h/call wasm/internal-module "_add_shape_solid_fill" rgba)) (some? gradient) - (case (:type gradient) - :linear - (let [size (gradient-byte-size gradient) - offset (mem/alloc-bytes size) - heap (mem/get-heap-u32)] - (sr-fills/serialize-linear-fill gradient opacity heap offset) - (h/call wasm/internal-module "_add_shape_linear_fill")) - :radial - (let [size (gradient-byte-size gradient) - offset (mem/alloc-bytes size) - heap (mem/get-heap-u32)] - (sr-fills/serialize-radial-fill gradient opacity heap offset) + (let [size (gradient-byte-size gradient) + offset (mem/alloc-bytes size) + heap (mem/get-heap-u32)] + (sr-fills/serialize-gradient-fill gradient opacity heap offset) + (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) @@ -284,28 +280,15 @@ (cond (some? gradient) - (let [stops (:stops gradient) - size (gradient-stop-get-entries-size stops) + (let [size (gradient-byte-size gradient) offset (mem/alloc-bytes size) - heap (mem/get-heap-u8)] + heap (mem/get-heap-u32)] + (sr-fills/serialize-gradient-fill gradient opacity heap offset) (case (: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_linear_fill") :radial - (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))) - (sr-fills/serialize-gradient-stops stops heap offset) - (h/call wasm/internal-module "_add_shape_stroke_stops")) + (h/call wasm/internal-module "_add_shape_stroke_radial_fill"))) (some? image) (let [id (dm/get-prop image :id) diff --git a/frontend/src/app/render_wasm/serializers/fills.cljs b/frontend/src/app/render_wasm/serializers/fills.cljs index cada7416a..cbcbb5b59 100644 --- a/frontend/src/app/render_wasm/serializers/fills.cljs +++ b/frontend/src/app/render_wasm/serializers/fills.cljs @@ -6,10 +6,22 @@ (def GRADIENT-STOP-SIZE 8) (def GRADIENT-BASE-SIZE 24) -(defn serialize-gradient-stops - [stops heap offset] - (let [dview (js/DataView. (.-buffer heap))] - (loop [stops (seq stops) offset offset] +(defn serialize-gradient-fill + [gradient opacity heap offset] + (let [dview (js/DataView. (.-buffer heap)) + start-x (dm/get-prop gradient :start-x) + start-y (dm/get-prop gradient :start-y) + end-x (dm/get-prop gradient :end-x) + end-y (dm/get-prop gradient :end-y) + width (or (dm/get-prop gradient :width) 0) + stops (dm/get-prop gradient :stops)] + (.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)] (when-not (empty? stops) (let [stop (first stops) hex-color (dm/get-prop stop :color) @@ -18,38 +30,4 @@ stop-offset (dm/get-prop stop :offset)] (.setUint32 dview offset argb true) (.setFloat32 dview (+ offset 4) stop-offset true) - (recur (rest stops) (+ offset GRADIENT-STOP-SIZE))))))) - -(defn serialize-linear-fill - [gradient opacity heap offset] - (let [dview (js/DataView. (.-buffer heap)) - start-x (dm/get-prop gradient :start-x) - start-y (dm/get-prop gradient :start-y) - end-x (dm/get-prop gradient :end-x) - end-y (dm/get-prop gradient :end-y) - stops (dm/get-prop gradient :stops) - width 0] - (.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) - (serialize-gradient-stops stops heap (+ offset GRADIENT-BASE-SIZE)))) - -(defn serialize-radial-fill - [gradient opacity heap offset] - (let [dview (js/DataView. (.-buffer heap)) - start-x (dm/get-prop gradient :start-x) - start-y (dm/get-prop gradient :start-y) - end-x (dm/get-prop gradient :end-x) - end-y (dm/get-prop gradient :end-y) - stops (dm/get-prop gradient :stops) - width (dm/get-prop gradient :width)] - (.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) - (serialize-gradient-stops stops heap (+ offset GRADIENT-BASE-SIZE)))) \ No newline at end of file + (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 66e5961ec..259fdd8c0 100644 --- a/render-wasm/src/main.rs +++ b/render-wasm/src/main.rs @@ -487,42 +487,55 @@ pub extern "C" fn add_shape_stroke_solid_fill(raw_color: u32) { } #[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, -) { +pub extern "C" fn add_shape_stroke_linear_fill() { with_current_shape!(state, |shape: &mut Shape| { + let bytes = mem::bytes(); + let raw_gradient_bytes: [u8; RAW_FILL_DATA_SIZE] = + bytes[0..RAW_FILL_DATA_SIZE].try_into().unwrap(); + let raw_gradient = shapes::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().unwrap(); + shapes::RawStopData::from(data) + }) + .collect(); + shape - .set_stroke_fill(shapes::Fill::new_linear_gradient( - (start_x, start_y), - (end_x, end_y), - opacity, + .set_stroke_fill(shapes::Fill::new_linear_gradient_with_stops( + raw_gradient.start(), + raw_gradient.end(), + raw_gradient.opacity(), + stops, )) - .expect("could not add stroke linear fill"); + .expect("could not add stroke linear gradient 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, -) { +pub extern "C" fn add_shape_stroke_radial_fill() { with_current_shape!(state, |shape: &mut Shape| { + let bytes = mem::bytes(); + let raw_gradient_bytes: [u8; RAW_FILL_DATA_SIZE] = + bytes[0..RAW_FILL_DATA_SIZE].try_into().unwrap(); + let raw_gradient = shapes::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().unwrap(); + shapes::RawStopData::from(data) + }) + .collect(); + shape - .set_stroke_fill(shapes::Fill::new_radial_gradient( - (start_x, start_y), - (end_x, end_y), - opacity, - width, + .set_stroke_fill(shapes::Fill::new_radial_gradient_with_stops( + raw_gradient.start(), + raw_gradient.end(), + raw_gradient.opacity(), + raw_gradient.width(), + stops, )) - .expect("could not add stroke radial fill"); + .expect("could not add stroke radial gradient fill"); }); } diff --git a/render-wasm/src/shapes/fills.rs b/render-wasm/src/shapes/fills.rs index f19cd107a..3fb126e4d 100644 --- a/render-wasm/src/shapes/fills.rs +++ b/render-wasm/src/shapes/fills.rs @@ -171,10 +171,6 @@ pub enum Fill { } impl Fill { - pub fn new_linear_gradient(start: (f32, f32), end: (f32, f32), opacity: f32) -> Self { - Self::new_linear_gradient_with_stops(start, end, opacity, vec![]) - } - pub fn new_linear_gradient_with_stops( start: (f32, f32), end: (f32, f32), @@ -197,15 +193,6 @@ impl Fill { Self::LinearGradient(gradient) } - pub fn new_radial_gradient( - start: (f32, f32), - end: (f32, f32), - opacity: f32, - width: f32, - ) -> Self { - Self::new_radial_gradient_with_stops(start, end, opacity, width, vec![]) - } - pub fn new_radial_gradient_with_stops( start: (f32, f32), end: (f32, f32), From 64a2a08d2420e9cae18649bea2b7428fd50b09e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Albeza?= Date: Tue, 15 Apr 2025 12:33:53 +0200 Subject: [PATCH 07/11] :recycle: Refactor gradient parsing from bytes --- render-wasm/src/main.rs | 123 +++----------------------------- render-wasm/src/shapes.rs | 31 -------- render-wasm/src/shapes/fills.rs | 81 ++++++++++----------- 3 files changed, 45 insertions(+), 190 deletions(-) diff --git a/render-wasm/src/main.rs b/render-wasm/src/main.rs index 259fdd8c0..3e09e3d7d 100644 --- a/render-wasm/src/main.rs +++ b/render-wasm/src/main.rs @@ -16,10 +16,7 @@ mod wapi; mod wasm; use crate::mem::SerializableResult; -use crate::shapes::{ - BoolType, ConstraintH, ConstraintV, StructureEntry, TransformEntry, Type, RAW_FILL_DATA_SIZE, - RAW_STOP_DATA_SIZE, -}; +use crate::shapes::{BoolType, ConstraintH, ConstraintV, StructureEntry, TransformEntry, Type}; use crate::utils::uuid_from_u32_quartet; use crate::uuid::Uuid; use indexmap::IndexSet; @@ -261,23 +258,8 @@ pub extern "C" fn add_shape_solid_fill(raw_color: u32) { pub extern "C" fn add_shape_linear_fill() { with_current_shape!(state, |shape: &mut Shape| { let bytes = mem::bytes(); - let raw_gradient_bytes: [u8; RAW_FILL_DATA_SIZE] = - bytes[0..RAW_FILL_DATA_SIZE].try_into().unwrap(); - let raw_gradient = shapes::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().unwrap(); - shapes::RawStopData::from(data) - }) - .collect(); - - shape.add_fill(shapes::Fill::new_linear_gradient_with_stops( - raw_gradient.start(), - raw_gradient.end(), - raw_gradient.opacity(), - stops, - )); + let gradient = shapes::Gradient::try_from(&bytes[..]).expect("Invalid gradient data"); + shape.add_fill(shapes::Fill::LinearGradient(gradient)); }); } @@ -285,48 +267,11 @@ pub extern "C" fn add_shape_linear_fill() { pub extern "C" fn add_shape_radial_fill() { with_current_shape!(state, |shape: &mut Shape| { let bytes = mem::bytes(); - let raw_gradient_bytes: [u8; RAW_FILL_DATA_SIZE] = - bytes[0..RAW_FILL_DATA_SIZE].try_into().unwrap(); - let raw_gradient = shapes::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().unwrap(); - shapes::RawStopData::from(data) - }) - .collect(); - - shape.add_fill(shapes::Fill::new_radial_gradient_with_stops( - raw_gradient.start(), - raw_gradient.end(), - raw_gradient.opacity(), - raw_gradient.width(), - stops, - )); + 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_fill_stops() { - let bytes = mem::bytes(); - - let entries: Vec<_> = bytes - .chunks(size_of::()) - .map(|data| { - let raw_stop_bytes: [u8; RAW_STOP_DATA_SIZE] = data.try_into().unwrap(); - shapes::RawStopData::from(raw_stop_bytes) - }) - .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, { @@ -490,24 +435,10 @@ pub extern "C" fn add_shape_stroke_solid_fill(raw_color: u32) { pub extern "C" fn add_shape_stroke_linear_fill() { with_current_shape!(state, |shape: &mut Shape| { let bytes = mem::bytes(); - let raw_gradient_bytes: [u8; RAW_FILL_DATA_SIZE] = - bytes[0..RAW_FILL_DATA_SIZE].try_into().unwrap(); - let raw_gradient = shapes::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().unwrap(); - shapes::RawStopData::from(data) - }) - .collect(); + let gradient = shapes::Gradient::try_from(&bytes[..]).expect("Invalid gradient data"); shape - .set_stroke_fill(shapes::Fill::new_linear_gradient_with_stops( - raw_gradient.start(), - raw_gradient.end(), - raw_gradient.opacity(), - stops, - )) + .set_stroke_fill(shapes::Fill::LinearGradient(gradient)) .expect("could not add stroke linear gradient fill"); }); } @@ -516,50 +447,14 @@ pub extern "C" fn add_shape_stroke_linear_fill() { pub extern "C" fn add_shape_stroke_radial_fill() { with_current_shape!(state, |shape: &mut Shape| { let bytes = mem::bytes(); - let raw_gradient_bytes: [u8; RAW_FILL_DATA_SIZE] = - bytes[0..RAW_FILL_DATA_SIZE].try_into().unwrap(); - let raw_gradient = shapes::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().unwrap(); - shapes::RawStopData::from(data) - }) - .collect(); + let gradient = shapes::Gradient::try_from(&bytes[..]).expect("Invalid gradient data"); shape - .set_stroke_fill(shapes::Fill::new_radial_gradient_with_stops( - raw_gradient.start(), - raw_gradient.end(), - raw_gradient.opacity(), - raw_gradient.width(), - stops, - )) + .set_stroke_fill(shapes::Fill::RadialGradient(gradient)) .expect("could not add stroke radial gradient fill"); }); } -#[no_mangle] -pub extern "C" fn add_shape_stroke_stops() { - let bytes = mem::bytes(); - - let entries: Vec<_> = bytes - .chunks(size_of::()) - .map(|data| { - let raw_stop_bytes: [u8; RAW_STOP_DATA_SIZE] = data.try_into().unwrap(); - shapes::RawStopData::from(raw_stop_bytes) - }) - .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 { diff --git a/render-wasm/src/shapes.rs b/render-wasm/src/shapes.rs index ac13d7f37..5b44806b8 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 3fb126e4d..78bbff701 100644 --- a/render-wasm/src/shapes/fills.rs +++ b/render-wasm/src/shapes/fills.rs @@ -144,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, @@ -171,51 +207,6 @@ pub enum Fill { } impl Fill { - pub fn new_linear_gradient_with_stops( - start: (f32, f32), - end: (f32, f32), - opacity: f32, - stops: Vec, - ) -> Self { - let mut gradient = Gradient { - start, - end, - opacity, - colors: vec![], - offsets: vec![], - width: 0., - }; - - for stop in stops { - gradient.add_stop(stop.color(), stop.offset()); - } - - Self::LinearGradient(gradient) - } - - pub fn new_radial_gradient_with_stops( - start: (f32, f32), - end: (f32, f32), - opacity: f32, - width: f32, - stops: Vec, - ) -> Self { - let mut gradient = Gradient { - start, - end, - opacity, - colors: vec![], - offsets: vec![], - width, - }; - - for stop in stops { - gradient.add_stop(stop.color(), stop.offset()); - } - - Self::RadialGradient(gradient) - } - pub fn new_image_fill(id: Uuid, opacity: u8, (width, height): (i32, i32)) -> Self { Self::Image(ImageFill { id, From f500a00d0401bc1135fb064605806a07a37cabeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Albeza?= Date: Tue, 15 Apr 2025 12:43:25 +0200 Subject: [PATCH 08/11] :recycle: Extract wasm-functions for fills and strokes out of main.rs --- render-wasm/src/main.rs | 143 -------------------------------- render-wasm/src/wasm.rs | 2 + render-wasm/src/wasm/fills.rs | 60 ++++++++++++++ render-wasm/src/wasm/strokes.rs | 97 ++++++++++++++++++++++ 4 files changed, 159 insertions(+), 143 deletions(-) create mode 100644 render-wasm/src/wasm/fills.rs create mode 100644 render-wasm/src/wasm/strokes.rs diff --git a/render-wasm/src/main.rs b/render-wasm/src/main.rs index 3e09e3d7d..3ea0e1a6a 100644 --- a/render-wasm/src/main.rs +++ b/render-wasm/src/main.rs @@ -246,32 +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() { - 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 store_image(a: u32, b: u32, c: u32, d: u32) { with_state!(state, { @@ -297,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| { @@ -394,67 +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() { - 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"); - }); -} - // 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 { @@ -473,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/wasm.rs b/render-wasm/src/wasm.rs index 31ae8a7c1..7d45923d9 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 000000000..a6c47fdf1 --- /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 000000000..5aea834cf --- /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(); + }); +} From b41a7b8547586a50a8c189d2c09a849a380847b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Albeza?= Date: Tue, 15 Apr 2025 12:54:17 +0200 Subject: [PATCH 09/11] :lipstick: Remove no longer used functions in wasm serialization --- frontend/src/app/render_wasm/api.cljs | 14 ++------------ .../src/app/render_wasm/serializers/color.cljs | 13 +------------ .../src/app/render_wasm/serializers/fills.cljs | 5 +++++ 3 files changed, 8 insertions(+), 24 deletions(-) diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index 44321c28e..89e96f323 100644 --- a/frontend/src/app/render_wasm/api.cljs +++ b/frontend/src/app/render_wasm/api.cljs @@ -51,16 +51,6 @@ (def GRID-LAYOUT-COLUMN-ENTRY-SIZE 5) (def GRID-LAYOUT-CELL-ENTRY-SIZE 37) -;; FIXME: use `gradient-byte-size` instead -(defn gradient-stop-get-entries-size - [stops] - (mem/get-list-size stops sr-fills/GRADIENT-STOP-SIZE)) - -(defn gradient-byte-size - [gradient] - (let [stops (:stops gradient)] - (+ sr-fills/GRADIENT-BASE-SIZE (* (count stops) sr-fills/GRADIENT-STOP-SIZE)))) - (defn modifier-get-entries-size "Returns the list of a modifier list in bytes" [modifiers] @@ -234,7 +224,7 @@ (h/call wasm/internal-module "_add_shape_solid_fill" rgba)) (some? gradient) - (let [size (gradient-byte-size gradient) + (let [size (sr-fills/gradient-byte-size gradient) offset (mem/alloc-bytes size) heap (mem/get-heap-u32)] (sr-fills/serialize-gradient-fill gradient opacity heap offset) @@ -280,7 +270,7 @@ (cond (some? gradient) - (let [size (gradient-byte-size gradient) + (let [size (sr-fills/gradient-byte-size gradient) offset (mem/alloc-bytes size) heap (mem/get-heap-u32)] (sr-fills/serialize-gradient-fill gradient opacity heap offset) diff --git a/frontend/src/app/render_wasm/serializers/color.cljs b/frontend/src/app/render_wasm/serializers/color.cljs index af7fe36f4..35fda2b75 100644 --- a/frontend/src/app/render_wasm/serializers/color.cljs +++ b/frontend/src/app/render_wasm/serializers/color.cljs @@ -8,15 +8,4 @@ (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])) \ No newline at end of file + (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 index cbcbb5b59..aae77bc58 100644 --- a/frontend/src/app/render_wasm/serializers/fills.cljs +++ b/frontend/src/app/render_wasm/serializers/fills.cljs @@ -6,6 +6,11 @@ (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 serialize-gradient-fill [gradient opacity heap offset] (let [dview (js/DataView. (.-buffer heap)) From 4bf9e24d438e512626dd7c5be43af000f7a1b82c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Albeza?= Date: Tue, 15 Apr 2025 14:22:42 +0200 Subject: [PATCH 10/11] :recycle: Avoid using get-prop when unneeded in serializers --- .../app/render_wasm/serializers/fills.cljs | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/frontend/src/app/render_wasm/serializers/fills.cljs b/frontend/src/app/render_wasm/serializers/fills.cljs index aae77bc58..4bdad7323 100644 --- a/frontend/src/app/render_wasm/serializers/fills.cljs +++ b/frontend/src/app/render_wasm/serializers/fills.cljs @@ -1,6 +1,5 @@ (ns app.render-wasm.serializers.fills (:require - [app.common.data.macros :as dm] [app.render-wasm.serializers.color :as clr])) (def GRADIENT-STOP-SIZE 8) @@ -13,13 +12,13 @@ (defn serialize-gradient-fill [gradient opacity heap offset] - (let [dview (js/DataView. (.-buffer heap)) - start-x (dm/get-prop gradient :start-x) - start-y (dm/get-prop gradient :start-y) - end-x (dm/get-prop gradient :end-x) - end-y (dm/get-prop gradient :end-y) - width (or (dm/get-prop gradient :width) 0) - stops (dm/get-prop gradient :stops)] + (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) @@ -29,10 +28,10 @@ (loop [stops (seq stops) offset (+ offset GRADIENT-BASE-SIZE)] (when-not (empty? stops) (let [stop (first stops) - hex-color (dm/get-prop stop :color) - opacity (dm/get-prop stop :opacity) + hex-color (:color stop) + opacity (:opacity stop) argb (clr/hex->u32argb hex-color opacity) - stop-offset (dm/get-prop stop :offset)] + 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 From 1da623e63f679a7a856da2ef7028145d2eeee456 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Albeza?= Date: Wed, 16 Apr 2025 12:05:06 +0200 Subject: [PATCH 11/11] :lipstick: Change naming and args order of write-gradient-fill! --- frontend/src/app/render_wasm/api.cljs | 4 ++-- frontend/src/app/render_wasm/serializers/fills.cljs | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index 89e96f323..ada20b757 100644 --- a/frontend/src/app/render_wasm/api.cljs +++ b/frontend/src/app/render_wasm/api.cljs @@ -227,7 +227,7 @@ (let [size (sr-fills/gradient-byte-size gradient) offset (mem/alloc-bytes size) heap (mem/get-heap-u32)] - (sr-fills/serialize-gradient-fill gradient opacity heap offset) + (sr-fills/write-gradient-fill! offset heap gradient opacity) (case (:type gradient) :linear (h/call wasm/internal-module "_add_shape_linear_fill") @@ -273,7 +273,7 @@ (let [size (sr-fills/gradient-byte-size gradient) offset (mem/alloc-bytes size) heap (mem/get-heap-u32)] - (sr-fills/serialize-gradient-fill gradient opacity heap offset) + (sr-fills/write-gradient-fill! offset heap gradient opacity) (case (:type gradient) :linear (h/call wasm/internal-module "_add_shape_stroke_linear_fill") diff --git a/frontend/src/app/render_wasm/serializers/fills.cljs b/frontend/src/app/render_wasm/serializers/fills.cljs index 4bdad7323..e841bb0eb 100644 --- a/frontend/src/app/render_wasm/serializers/fills.cljs +++ b/frontend/src/app/render_wasm/serializers/fills.cljs @@ -10,8 +10,8 @@ (let [stops (:stops gradient)] (+ GRADIENT-BASE-SIZE (* (count stops) GRADIENT-STOP-SIZE)))) -(defn serialize-gradient-fill - [gradient opacity heap offset] +(defn write-gradient-fill! + [offset heap gradient opacity] (let [dview (js/DataView. (.-buffer heap)) start-x (:start-x gradient) start-y (:start-y gradient) @@ -26,7 +26,8 @@ (.setFloat32 dview (+ offset 16) opacity true) (.setFloat32 dview (+ offset 20) width true) (loop [stops (seq stops) offset (+ offset GRADIENT-BASE-SIZE)] - (when-not (empty? stops) + (if (empty? stops) + offset (let [stop (first stops) hex-color (:color stop) opacity (:opacity stop)