diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index 47f2ed0706..62139a7e64 100644 --- a/frontend/src/app/render_wasm/api.cljs +++ b/frontend/src/app/render_wasm/api.cljs @@ -214,35 +214,26 @@ (let [opacity (or (:fill-opacity fill) 1.0) color (:fill-color fill) gradient (:fill-color-gradient fill) - image (:fill-image fill)] + image (:fill-image fill) + offset (mem/alloc-bytes sr-fills/FILL-BYTE-SIZE) + heap (mem/get-heap-u32)] (cond (some? color) - (let [rgba (sr-clr/hex->u32argb color opacity)] - (h/call wasm/internal-module "_add_shape_solid_fill" rgba)) + (let [argb (sr-clr/hex->u32argb color opacity)] + (sr-fills/write-solid-fill! offset heap argb) + (h/call wasm/internal-module "_add_shape_fill")) (some? gradient) - (let [size sr-fills/GRADIENT-BYTE-SIZE - offset (mem/alloc-bytes size) - heap (mem/get-heap-u32)] + (do (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"))) + (h/call wasm/internal-module "_add_shape_fill")) (some? image) (let [id (dm/get-prop image :id) buffer (uuid/get-u32 id) cached-image? (h/call wasm/internal-module "_is_image_cached" (aget buffer 0) (aget buffer 1) (aget buffer 2) (aget buffer 3))] - (h/call wasm/internal-module "_add_shape_image_fill" - (aget buffer 0) - (aget buffer 1) - (aget buffer 2) - (aget buffer 3) - opacity - (dm/get-prop image :width) - (dm/get-prop image :height)) + (sr-fills/write-image-fill! offset heap id opacity (dm/get-prop image :width) (dm/get-prop image :height)) + (h/call wasm/internal-module "_add_shape_fill") (when (== cached-image? 0) (store-image id)))))) fills)) @@ -259,7 +250,9 @@ align (:stroke-alignment stroke) style (-> stroke :stroke-style sr/translate-stroke-style) cap-start (-> stroke :stroke-cap-start sr/translate-stroke-cap) - cap-end (-> stroke :stroke-cap-end sr/translate-stroke-cap)] + cap-end (-> stroke :stroke-cap-end sr/translate-stroke-cap) + offset (mem/alloc-bytes sr-fills/FILL-BYTE-SIZE) + heap (mem/get-heap-u32)] (case align :inner (h/call wasm/internal-module "_add_shape_inner_stroke" width style cap-start cap-end) :outer (h/call wasm/internal-module "_add_shape_outer_stroke" width style cap-start cap-end) @@ -267,34 +260,23 @@ (cond (some? gradient) - (let [size sr-fills/GRADIENT-BYTE-SIZE - offset (mem/alloc-bytes size) - heap (mem/get-heap-u32)] + (let [_ nil] (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"))) + (h/call wasm/internal-module "_add_shape_stroke_fill")) (some? image) (let [id (dm/get-prop image :id) buffer (uuid/get-u32 id) cached-image? (h/call wasm/internal-module "_is_image_cached" (aget buffer 0) (aget buffer 1) (aget buffer 2) (aget buffer 3))] - (h/call wasm/internal-module "_add_shape_image_stroke" - (aget buffer 0) - (aget buffer 1) - (aget buffer 2) - (aget buffer 3) - opacity - (dm/get-prop image :width) - (dm/get-prop image :height)) + (sr-fills/write-image-fill! offset heap id opacity (dm/get-prop image :width) (dm/get-prop image :height)) + (h/call wasm/internal-module "_add_shape_stroke_fill") (when (== cached-image? 0) (store-image id))) (some? color) - (let [rgba (sr-clr/hex->u32argb color opacity)] - (h/call wasm/internal-module "_add_shape_stroke_solid_fill" rgba))))) + (let [argb (sr-clr/hex->u32argb color opacity)] + (sr-fills/write-solid-fill! offset heap argb) + (h/call wasm/internal-module "_add_shape_stroke_fill"))))) strokes)) (defn set-shape-path-attrs diff --git a/frontend/src/app/render_wasm/serializers/fills.cljs b/frontend/src/app/render_wasm/serializers/fills.cljs index f4f395b66e..4b21af4f4b 100644 --- a/frontend/src/app/render_wasm/serializers/fills.cljs +++ b/frontend/src/app/render_wasm/serializers/fills.cljs @@ -1,5 +1,6 @@ (ns app.render-wasm.serializers.fills (:require + [app.common.uuid :as uuid] [app.render-wasm.serializers.color :as clr])) (def ^:private GRADIENT-STOP-SIZE 8) @@ -10,30 +11,61 @@ (def GRADIENT-BYTE-SIZE (+ GRADIENT-BASE-SIZE (* MAX-GRADIENT-STOPS GRADIENT-STOP-SIZE))) +(def SOLID-BYTE-SIZE 4) +(def IMAGE-BYTE-SIZE 28) + +;; FIXME: get it from the wasm module +(def FILL-BYTE-SIZE (+ 4 (max GRADIENT-BYTE-SIZE IMAGE-BYTE-SIZE SOLID-BYTE-SIZE))) + +(defn write-solid-fill! + [offset heap-u32 argb] + (let [dview (js/DataView. (.-buffer heap-u32))] + (.setUint8 dview offset 0x00 true) + (.setUint32 dview (+ offset 4) argb true) + (+ offset FILL-BYTE-SIZE))) + +(defn write-image-fill! + [offset heap-u32 id opacity width height] + (let [dview (js/DataView. (.-buffer heap-u32)) + uuid-buffer (uuid/get-u32 id)] + (.setUint8 dview offset 0x03 true) + (.setUint32 dview (+ offset 4) (aget uuid-buffer 0) true) + (.setUint32 dview (+ offset 8) (aget uuid-buffer 1) true) + (.setUint32 dview (+ offset 12) (aget uuid-buffer 2) true) + (.setUint32 dview (+ offset 16) (aget uuid-buffer 3) true) + (.setFloat32 dview (+ offset 20) opacity true) + (.setInt32 dview (+ offset 24) width true) + (.setInt32 dview (+ offset 28) height true) + (+ offset FILL-BYTE-SIZE))) + + + (defn write-gradient-fill! - [offset heap gradient opacity] - (let [dview (js/DataView. (.-buffer heap)) + [offset heap-u32 gradient opacity] + (let [dview (js/DataView. (.-buffer heap-u32)) 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 (take MAX-GRADIENT-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) - (.setUint32 dview (+ offset 24) (count stops) true) - (loop [stops (seq stops) offset (+ offset GRADIENT-BASE-SIZE)] + stops (take MAX-GRADIENT-STOPS (:stops gradient)) + type (if (= (:type gradient) :linear) 0x01 0x02)] + (.setUint8 dview offset type true) + (.setFloat32 dview (+ offset 4) start-x true) + (.setFloat32 dview (+ offset 8) start-y true) + (.setFloat32 dview (+ offset 12) end-x true) + (.setFloat32 dview (+ offset 16) end-y true) + (.setFloat32 dview (+ offset 20) opacity true) + (.setFloat32 dview (+ offset 24) width true) + (.setUint8 dview (+ offset 28) (count stops) true) + (loop [stops (seq stops) loop-offset (+ offset 32)] (if (empty? stops) - offset + (+ offset FILL-BYTE-SIZE) (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 + (.setUint32 dview loop-offset argb true) + (.setFloat32 dview (+ loop-offset 4) stop-offset true) + (recur (rest stops) (+ loop-offset GRADIENT-STOP-SIZE))))))) \ No newline at end of file diff --git a/render-wasm/docs/serialization.md b/render-wasm/docs/serialization.md index 7318c62a25..36cbc5833c 100644 --- a/render-wasm/docs/serialization.md +++ b/render-wasm/docs/serialization.md @@ -67,21 +67,60 @@ Paths are made of segments of **28 bytes** each. The layout (assuming positions **Flags** is not being used at the moment. -## Gradient stops +## Fills -Gradient stops are serialized in a `Uint8Array`, each stop taking **5 bytes**. +All fills take `160` bytes, but depending on the fill type, not all bytes are actually used. + +### Solid color fills + +| Offset | Length (bytes) | Data Type | Field | +| ------ | -------------- | --------- | ---------- | +| 0 | 1 | `0x00` | Fill type | +| 1 | 3 | ? | Reserved | +| 4 | 4 | `u32` | ARGB color | + +### Image fills + +| Offset | Length (bytes) | Data Type | Field | +| ------ | -------------- | --------- | --------- | +| 0 | 1 | `0x03` | Fill type | +| 1 | 3 | ? | Reserved | +| 4 | 4 | `u32` | `a` (ID) | +| 8 | 4 | `u32` | `b` (ID) | +| 12 | 4 | `u32` | `c` (ID) | +| 16 | 4 | `u32` | `d` (ID) | +| 20 | 4 | `f32` | Opacity | +| 24 | 4 | `width` | Opacity | +| 29 | 4 | `height` | Opacity | + +### Gradient fills + +| Offset | Length (bytes) | Data Type | Field | +| ------ | -------------- | ----------- | ----------- | +| 0 | 1 | `0x03` | Fill type\* | +| 1 | 3 | ? | Reserved | +| 4 | 4 | `f32` | Start `x` | +| 8 | 4 | `f32` | Start `y` | +| 12 | 4 | `f32` | End `x` | +| 16 | 4 | `f32` | End `y` | +| 20 | 4 | `f32` | Opacity | +| 24 | 4 | `f32` | Width\*\* | +| 28 | 4 | `u8` | Stop count | +| 29 | 3 | ? | Reserved | +| 32 | 128 | _See below_ | Stop data | + +\*: **Fill type** is `0x01` for linear gradients and `0x02` for radial gradients. + +\*\*: **Width** is unused in linear gradients. + +#### Gradient stop data + +Gradient stops are serialized as a sequence of `16` chunks with the following layout: | Offset | Length (bytes) | Data Type | Field | | ------ | -------------- | --------- | ----------- | -| 0 | 1 | `u8` | Red | -| 1 | 1 | `u8` | Green | -| 2 | 1 | `u8` | Blue | -| 3 | 1 | `u8` | Alpha | -| 4 | 1 | `u8` | Stop Offset | - -**Red**, **Green**, **Blue** and **Alpha** are the RGBA components of the stop. - -**Stop offset** is the offset, being integer values ranging from `0` to `100` (both inclusive). +| 0 | 4 | `u32` | ARGB Color | +| 4 | 4 | `f32` | Stop offset | ## Stroke Caps @@ -154,11 +193,11 @@ Shadow styles are serialized as `u8`: ### Grid Direction -| Value | Field | -| ----- | ------------- | -| 0 | Row | -| 1 | Column | -| \_ | error | +| Value | Field | +| ----- | ------ | +| 0 | Row | +| 1 | Column | +| \_ | error | ### Align Items @@ -265,8 +304,6 @@ Shadow styles are serialized as `u8`: | 3 | Fixed | | \_ | error | - - ## Font ### Style diff --git a/render-wasm/src/shapes.rs b/render-wasm/src/shapes.rs index 5e27a14570..24f585966f 100644 --- a/render-wasm/src/shapes.rs +++ b/render-wasm/src/shapes.rs @@ -914,8 +914,11 @@ mod tests { let mut shape = any_shape(); assert_eq!(shape.fills.len(), 0); - shape.add_fill(Fill::Solid(Color::TRANSPARENT)); - assert_eq!(shape.fills.get(0), Some(&Fill::Solid(Color::TRANSPARENT))) + shape.add_fill(Fill::Solid(SolidColor(Color::TRANSPARENT))); + assert_eq!( + shape.fills.get(0), + Some(&Fill::Solid(SolidColor(Color::TRANSPARENT))) + ) } #[test] diff --git a/render-wasm/src/shapes/fills.rs b/render-wasm/src/shapes/fills.rs index f85ba75068..0263bdd94f 100644 --- a/render-wasm/src/shapes/fills.rs +++ b/render-wasm/src/shapes/fills.rs @@ -1,118 +1,39 @@ use skia_safe::{self as skia, Rect}; -use super::Color; +pub use super::Color; use crate::uuid::Uuid; -const MAX_GRADIENT_STOPS: usize = 16; -const BASE_GRADIENT_DATA_SIZE: usize = 28; -const RAW_GRADIENT_DATA_SIZE: usize = - BASE_GRADIENT_DATA_SIZE + RAW_STOP_DATA_SIZE * MAX_GRADIENT_STOPS; - -#[derive(Debug)] -#[repr(C)] -pub struct RawGradientData { - start_x: f32, - start_y: f32, - end_x: f32, - end_y: f32, - opacity: f32, - width: f32, - stop_count: u32, - stops: [RawStopData; MAX_GRADIENT_STOPS], -} - -impl From<[u8; RAW_GRADIENT_DATA_SIZE]> for RawGradientData { - fn from(bytes: [u8; RAW_GRADIENT_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]]), - stop_count: u32::from_le_bytes([bytes[24], bytes[25], bytes[26], bytes[27]]), - // FIXME: 2025-04-22: use `array_chunks` once the next release is out - // and we update our devenv. - // See https://github.com/rust-lang/rust/issues/74985 - stops: bytes[28..] - .chunks_exact(RAW_STOP_DATA_SIZE) - .map(|chunk| RawStopData::try_from(chunk).unwrap()) - .collect::>() - .try_into() - .unwrap(), - } - } -} - -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: u32, - offset: f32, -} - -impl RawStopData { - pub fn color(&self) -> skia::Color { - skia::Color::from(self.color) - } - - pub fn offset(&self) -> f32 { - self.offset - } -} - -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]]), - } - } -} - -// FIXME: We won't need this once we use `array_chunks`. See comment above. -impl TryFrom<&[u8]> for RawStopData { - type Error = String; - - fn try_from(bytes: &[u8]) -> Result { - let data: [u8; RAW_STOP_DATA_SIZE] = bytes - .try_into() - .map_err(|_| "Invalid stop data".to_string())?; - Ok(RawStopData::from(data)) - } -} - #[derive(Debug, Clone, PartialEq)] pub struct Gradient { - colors: Vec, - offsets: Vec, - opacity: f32, start: (f32, f32), end: (f32, f32), + opacity: u8, width: f32, + colors: Vec, + offsets: Vec, } impl Gradient { + pub fn new( + start: (f32, f32), + end: (f32, f32), + opacity: u8, + width: f32, + stops: &[(Color, f32)], + ) -> Self { + let mut gradient = Gradient { + start, + end, + opacity, + colors: vec![], + offsets: vec![], + width, + }; + + gradient.add_stops(stops); + gradient + } + fn add_stops(&mut self, stops: &[(Color, f32)]) { let colors = stops.iter().map(|(color, _)| *color); let offsets = stops.iter().map(|(_, offset)| *offset); @@ -173,52 +94,24 @@ impl Gradient { } } -impl From for Gradient { - fn from(raw_gradient: RawGradientData) -> Self { - let stops = raw_gradient - .stops - .iter() - .take(raw_gradient.stop_count as usize) - .map(|stop| (stop.color(), stop.offset())) - .collect::>(); - - let mut gradient = Gradient { - start: raw_gradient.start(), - end: raw_gradient.end(), - opacity: raw_gradient.opacity(), - colors: vec![], - offsets: vec![], - width: raw_gradient.width(), - }; - - gradient.add_stops(&stops); - - gradient - } -} - -impl TryFrom<&[u8]> for Gradient { - type Error = String; - - fn try_from(bytes: &[u8]) -> Result { - let raw_gradient_bytes: [u8; RAW_GRADIENT_DATA_SIZE] = bytes[0..RAW_GRADIENT_DATA_SIZE] - .try_into() - .map_err(|_| "Invalid gradient data".to_string())?; - let gradient = RawGradientData::from(raw_gradient_bytes).into(); - - Ok(gradient) - } -} - #[derive(Debug, Clone, PartialEq)] pub struct ImageFill { id: Uuid, opacity: u8, - height: i32, width: i32, + height: i32, } impl ImageFill { + pub fn new(id: Uuid, opacity: u8, width: i32, height: i32) -> Self { + Self { + id, + opacity, + width, + height, + } + } + pub fn size(&self) -> (i32, i32) { (self.width, self.height) } @@ -228,27 +121,21 @@ impl ImageFill { } } +#[derive(Debug, Clone, PartialEq, Copy)] +pub struct SolidColor(pub Color); + #[derive(Debug, Clone, PartialEq)] pub enum Fill { - Solid(Color), + Solid(SolidColor), LinearGradient(Gradient), RadialGradient(Gradient), Image(ImageFill), } impl Fill { - pub fn new_image_fill(id: Uuid, opacity: u8, (width, height): (i32, i32)) -> Self { - Self::Image(ImageFill { - id, - opacity, - height, - width, - }) - } - pub fn to_paint(&self, rect: &Rect, anti_alias: bool) -> skia::Paint { match self { - Self::Solid(color) => { + Self::Solid(SolidColor(color)) => { let mut p = skia::Paint::default(); p.set_color(*color); p.set_style(skia::PaintStyle::Fill); @@ -259,7 +146,7 @@ impl Fill { Self::LinearGradient(gradient) => { let mut p = skia::Paint::default(); p.set_shader(gradient.to_linear_shader(rect)); - p.set_alpha((gradient.opacity * 255.) as u8); + p.set_alpha(gradient.opacity); p.set_style(skia::PaintStyle::Fill); p.set_anti_alias(anti_alias); p.set_blend_mode(skia::BlendMode::SrcOver); @@ -268,7 +155,7 @@ impl Fill { Self::RadialGradient(gradient) => { let mut p = skia::Paint::default(); p.set_shader(gradient.to_radial_shader(rect)); - p.set_alpha((gradient.opacity * 255.) as u8); + p.set_alpha(gradient.opacity); p.set_style(skia::PaintStyle::Fill); p.set_anti_alias(anti_alias); p.set_blend_mode(skia::BlendMode::SrcOver); diff --git a/render-wasm/src/shapes/strokes.rs b/render-wasm/src/shapes/strokes.rs index c1372d224c..ffa1e40621 100644 --- a/render-wasm/src/shapes/strokes.rs +++ b/render-wasm/src/shapes/strokes.rs @@ -1,4 +1,4 @@ -use crate::shapes::fills::Fill; +use crate::shapes::fills::{Fill, SolidColor}; use skia_safe::{self as skia, Rect}; use std::collections::HashMap; @@ -78,10 +78,9 @@ impl Stroke { } pub fn new_center_stroke(width: f32, style: u8, cap_start: u8, cap_end: u8) -> Self { - let transparent = skia::Color::from_argb(0, 0, 0, 0); Stroke { - fill: Fill::Solid(transparent), - width: width, + fill: Fill::Solid(SolidColor(skia::Color::TRANSPARENT)), + width, style: StrokeStyle::from(style), cap_end: StrokeCap::from(cap_end), cap_start: StrokeCap::from(cap_start), @@ -90,10 +89,9 @@ impl Stroke { } pub fn new_inner_stroke(width: f32, style: u8, cap_start: u8, cap_end: u8) -> Self { - let transparent = skia::Color::from_argb(0, 0, 0, 0); Stroke { - fill: Fill::Solid(transparent), - width: width, + fill: Fill::Solid(SolidColor(skia::Color::TRANSPARENT)), + width, style: StrokeStyle::from(style), cap_end: StrokeCap::from(cap_end), cap_start: StrokeCap::from(cap_start), @@ -102,10 +100,9 @@ impl Stroke { } pub fn new_outer_stroke(width: f32, style: u8, cap_start: u8, cap_end: u8) -> Self { - let transparent = skia::Color::from_argb(0, 0, 0, 0); Stroke { - fill: Fill::Solid(transparent), - width: width, + fill: Fill::Solid(SolidColor(skia::Color::TRANSPARENT)), + width, style: StrokeStyle::from(style), cap_end: StrokeCap::from(cap_end), cap_start: StrokeCap::from(cap_start), diff --git a/render-wasm/src/wasm/fills.rs b/render-wasm/src/wasm/fills.rs index a6c47fdf1c..7be256d776 100644 --- a/render-wasm/src/wasm/fills.rs +++ b/render-wasm/src/wasm/fills.rs @@ -1,54 +1,64 @@ -use skia_safe as skia; +mod gradient; +mod image; +mod solid; 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)); - }); +const RAW_FILL_DATA_SIZE: usize = std::mem::size_of::(); + +#[repr(C)] +#[repr(align(4))] +#[repr(u8)] +#[derive(Debug, PartialEq, Clone, Copy)] +#[allow(dead_code)] +pub enum RawFillData { + Solid(solid::RawSolidData) = 0x00, + Linear(gradient::RawGradientData) = 0x01, + Radial(gradient::RawGradientData) = 0x02, + Image(image::RawImageFillData) = 0x03, +} + +impl From for shapes::Fill { + fn from(fill_data: RawFillData) -> Self { + match fill_data { + RawFillData::Solid(solid_fill_data) => shapes::Fill::Solid(solid_fill_data.into()), + RawFillData::Linear(linear_fill_data) => { + shapes::Fill::LinearGradient(linear_fill_data.into()) + } + RawFillData::Radial(radial_fill_data) => { + shapes::Fill::RadialGradient(radial_fill_data.into()) + } + RawFillData::Image(image_fill_data) => shapes::Fill::Image(image_fill_data.into()), + } + } +} + +impl From<[u8; RAW_FILL_DATA_SIZE]> for RawFillData { + fn from(bytes: [u8; RAW_FILL_DATA_SIZE]) -> Self { + unsafe { std::mem::transmute(bytes) } + } +} + +impl TryFrom<&[u8]> for RawFillData { + type Error = String; + fn try_from(bytes: &[u8]) -> Result { + let data: [u8; RAW_FILL_DATA_SIZE] = bytes + .get(0..RAW_FILL_DATA_SIZE) + .and_then(|slice| slice.try_into().ok()) + .ok_or("Invalid fill data".to_string())?; + Ok(RawFillData::from(data)) + } } #[no_mangle] -pub extern "C" fn add_shape_linear_fill() { +pub extern "C" fn add_shape_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), - )); + let raw_fill = RawFillData::try_from(&bytes[..]).expect("Invalid fill data"); + shape.add_fill(raw_fill.into()); }); } @@ -58,3 +68,32 @@ pub extern "C" fn clear_shape_fills() { shape.clear_fills(); }); } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_raw_fill_data_layout() { + assert_eq!( + std::mem::size_of::(), + 4 + std::mem::size_of::() + ); + assert_eq!(std::mem::align_of::(), 4); + } + + #[test] + fn test_raw_fill_data_from_bytes_to_solid_fill() { + let mut bytes = vec![0x00; std::mem::size_of::()]; + bytes[0] = 0x00; + bytes[4..8].copy_from_slice(&0xfffabada_u32.to_le_bytes()); + + let raw_fill = RawFillData::try_from(&bytes[..]); + + assert!(raw_fill.is_ok()); + assert_eq!( + raw_fill.unwrap(), + RawFillData::Solid(solid::RawSolidData { color: 0xfffabada }) + ); + } +} diff --git a/render-wasm/src/wasm/fills/gradient.rs b/render-wasm/src/wasm/fills/gradient.rs new file mode 100644 index 0000000000..dcb2a81fa9 --- /dev/null +++ b/render-wasm/src/wasm/fills/gradient.rs @@ -0,0 +1,63 @@ +use crate::shapes::{Color, Gradient}; + +const MAX_GRADIENT_STOPS: usize = 16; + +#[derive(Debug, PartialEq, Clone, Copy)] +#[repr(C)] +#[repr(align(4))] +pub struct RawGradientData { + start_x: f32, + start_y: f32, + end_x: f32, + end_y: f32, + opacity: f32, + width: f32, + stop_count: u8, + stops: [RawStopData; MAX_GRADIENT_STOPS], +} + +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) + } +} + +#[derive(Debug, PartialEq, Clone, Copy)] +#[repr(C)] +struct RawStopData { + color: u32, + offset: f32, +} + +impl RawStopData { + pub fn color(&self) -> Color { + Color::from(self.color) + } + + pub fn offset(&self) -> f32 { + self.offset + } +} + +impl From for Gradient { + fn from(raw_gradient: RawGradientData) -> Self { + let stops = raw_gradient + .stops + .iter() + .take(raw_gradient.stop_count as usize) + .map(|stop| (stop.color(), stop.offset())) + .collect::>(); + + Gradient::new( + raw_gradient.start(), + raw_gradient.end(), + (raw_gradient.opacity * 255.) as u8, + raw_gradient.width, + &stops, + ) + } +} diff --git a/render-wasm/src/wasm/fills/image.rs b/render-wasm/src/wasm/fills/image.rs new file mode 100644 index 0000000000..b669ad2b32 --- /dev/null +++ b/render-wasm/src/wasm/fills/image.rs @@ -0,0 +1,23 @@ +use crate::{shapes::ImageFill, utils::uuid_from_u32_quartet}; + +#[derive(Debug, Clone, Copy, PartialEq)] +#[repr(C)] +#[repr(align(4))] +pub struct RawImageFillData { + a: u32, + b: u32, + c: u32, + d: u32, + opacity: f32, + width: i32, + height: i32, +} + +impl From for ImageFill { + fn from(value: RawImageFillData) -> Self { + let id = uuid_from_u32_quartet(value.a, value.b, value.c, value.d); + let opacity = (value.opacity * 255.).floor() as u8; + + Self::new(id, opacity, value.width, value.height) + } +} diff --git a/render-wasm/src/wasm/fills/solid.rs b/render-wasm/src/wasm/fills/solid.rs new file mode 100644 index 0000000000..164f4282e5 --- /dev/null +++ b/render-wasm/src/wasm/fills/solid.rs @@ -0,0 +1,14 @@ +use crate::shapes::{Color, SolidColor}; + +#[repr(C)] +#[repr(align(4))] +#[derive(Debug, PartialEq, Clone, Copy)] +pub struct RawSolidData { + pub color: u32, +} + +impl From for SolidColor { + fn from(value: RawSolidData) -> Self { + Self(Color::new(value.color)) + } +} diff --git a/render-wasm/src/wasm/strokes.rs b/render-wasm/src/wasm/strokes.rs index 5aea834cfb..4f9905daac 100644 --- a/render-wasm/src/wasm/strokes.rs +++ b/render-wasm/src/wasm/strokes.rs @@ -1,8 +1,5 @@ -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; @@ -34,58 +31,13 @@ pub extern "C" fn add_shape_outer_stroke(width: f32, style: u8, cap_start: u8, c } #[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() { +pub extern "C" fn add_shape_stroke_fill() { with_current_shape!(state, |shape: &mut Shape| { let bytes = mem::bytes(); - let gradient = shapes::Gradient::try_from(&bytes[..]).expect("Invalid gradient data"); - + let raw_fill = super::fills::RawFillData::try_from(&bytes[..]).expect("Invalid fill 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"); + .set_stroke_fill(raw_fill.into()) + .expect("could not add stroke fill"); }); }