diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index f8b3339a5..610ce8951 100644 --- a/frontend/src/app/render_wasm/api.cljs +++ b/frontend/src/app/render_wasm/api.cljs @@ -155,19 +155,27 @@ (let [rgba (rgba-from-hex color opacity)] (h/call internal-module "_add_shape_solid_fill" rgba)) - (and (some? gradient) (= (:type gradient) :linear)) + (some? gradient) (let [stops (:stops gradient) n-stops (count stops) mem-size (* 5 n-stops) stops-ptr (h/call internal-module "_alloc_bytes" mem-size) heap (gobj/get ^js internal-module "HEAPU8") mem (js/Uint8Array. (.-buffer heap) stops-ptr mem-size)] - (h/call internal-module "_add_shape_linear_fill" - (:start-x gradient) - (:start-y gradient) - (:end-x gradient) - (:end-y gradient) - opacity) + (if (= (:type gradient) :linear) + (h/call internal-module "_add_shape_linear_fill" + (:start-x gradient) + (:start-y gradient) + (:end-x gradient) + (:end-y gradient) + opacity) + (h/call 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)] diff --git a/render-wasm/src/main.rs b/render-wasm/src/main.rs index 53ce2c541..6a55357df 100644 --- a/render-wasm/src/main.rs +++ b/render-wasm/src/main.rs @@ -183,6 +183,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, +) { + let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer"); + if let Some(shape) = state.current_shape() { + shape.add_fill(shapes::Fill::new_radial_gradient( + (start_x, start_y), + (end_x, end_y), + opacity, + width, + )) + } +} + #[no_mangle] pub extern "C" fn add_shape_fill_stops(ptr: *mut shapes::RawStopData, n_stops: u32) { let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer"); diff --git a/render-wasm/src/render.rs b/render-wasm/src/render.rs index a7668a98a..a3fa7fe4b 100644 --- a/render-wasm/src/render.rs +++ b/render-wasm/src/render.rs @@ -198,13 +198,11 @@ impl RenderState { } pub fn render_single_shape(&mut self, shape: &Shape) { - // Check transform-matrix code from common/src/app/common/geom/shapes/transforms.cljc - let mut matrix = skia::Matrix::new_identity(); + let mut transform = skia::Matrix::new_identity(); let (translate_x, translate_y) = shape.translation(); let (scale_x, scale_y) = shape.scale(); let (skew_x, skew_y) = shape.skew(); - - matrix.set_all( + transform.set_all( scale_x, skew_x, translate_x, @@ -216,10 +214,12 @@ impl RenderState { 1., ); - let mut center = shape.selrect.center(); - matrix.post_translate(center); - center.negate(); + // Check transform-matrix code from common/src/app/common/geom/shapes/transforms.cljc + let center = shape.selrect.center(); + let mut matrix = skia::Matrix::new_identity(); matrix.pre_translate(center); + matrix.pre_concat(&transform); + matrix.pre_translate(-center); self.drawing_surface.canvas().concat(&matrix); diff --git a/render-wasm/src/shapes.rs b/render-wasm/src/shapes.rs index 5770e4a13..ad708a32d 100644 --- a/render-wasm/src/shapes.rs +++ b/render-wasm/src/shapes.rs @@ -110,6 +110,7 @@ impl Shape { 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"), }?; diff --git a/render-wasm/src/shapes/fills.rs b/render-wasm/src/shapes/fills.rs index 095b80bee..dd69102e2 100644 --- a/render-wasm/src/shapes/fills.rs +++ b/render-wasm/src/shapes/fills.rs @@ -28,6 +28,7 @@ pub struct Gradient { opacity: f32, start: (f32, f32), end: (f32, f32), + width: f32, } impl Gradient { @@ -36,7 +37,7 @@ impl Gradient { self.offsets.push(offset); } - fn to_shader(&self, rect: &math::Rect) -> skia::Shader { + fn to_linear_shader(&self, rect: &math::Rect) -> skia::Shader { let start = ( rect.left + self.start.0 * rect.width(), rect.top + self.start.1 * rect.height(), @@ -56,6 +57,41 @@ impl Gradient { .unwrap(); shader } + + fn to_radial_shader(&self, rect: &math::Rect) -> skia::Shader { + let center = skia::Point::new( + rect.left + self.start.0 * rect.width(), + rect.top + self.start.1 * rect.height(), + ); + let end = skia::Point::new( + rect.left + self.end.0 * rect.width(), + rect.top + self.end.1 * rect.height(), + ); + + let direction = end - center; + let distance = (direction.x.powi(2) + direction.y.powi(2)).sqrt(); + let angle = direction.y.atan2(direction.x).to_degrees(); + + // Based on the code from frontend/src/app/main/ui/shapes/gradients.cljs + let mut transform = skia::Matrix::new_identity(); + transform.pre_translate((center.x, center.y)); + transform.pre_rotate(angle + 90., skia::Point::new(0., 0.)); + // We need an extra transform, because in skia radial gradients are circular and we need them to be ellipses if they must adapt to the shape + transform.pre_scale((self.width * rect.width() / rect.height(), 1.), None); + transform.pre_translate((-center.x, -center.y)); + + let shader = skia::shader::Shader::radial_gradient( + center, + distance, + self.colors.as_slice(), + self.offsets.as_slice(), + skia::TileMode::Clamp, + None, + Some(&transform), + ) + .unwrap(); + shader + } } #[derive(Debug, Clone, PartialEq)] @@ -80,6 +116,7 @@ impl ImageFill { pub enum Fill { Solid(Color), LinearGradient(Gradient), + RadialGradient(Gradient), Image(ImageFill), } @@ -91,6 +128,22 @@ impl Fill { opacity, colors: vec![], offsets: vec![], + width: 0., + }) + } + pub fn new_radial_gradient( + start: (f32, f32), + end: (f32, f32), + opacity: f32, + width: f32, + ) -> Self { + Self::RadialGradient(Gradient { + start, + end, + opacity, + colors: vec![], + offsets: vec![], + width, }) } @@ -115,7 +168,15 @@ impl Fill { } Self::LinearGradient(gradient) => { let mut p = skia::Paint::default(); - p.set_shader(gradient.to_shader(&rect)); + p.set_shader(gradient.to_linear_shader(&rect)); + p.set_alpha((gradient.opacity * 255.) as u8); + p.set_style(skia::PaintStyle::Fill); + p.set_blend_mode(skia::BlendMode::SrcOver); + p + } + 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_style(skia::PaintStyle::Fill); p.set_blend_mode(skia::BlendMode::SrcOver);