0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-01-06 14:50:20 -05:00

Merge pull request #5410 from penpot/superalex-radial-gradients-wasm-render

🎉 Support for radial gradients with wasm render
This commit is contained in:
Belén Albeza 2024-12-10 14:18:09 +01:00 committed by GitHub
commit 33e70a4108
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 106 additions and 16 deletions

View file

@ -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)]

View file

@ -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");

View file

@ -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);

View file

@ -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"),
}?;

View file

@ -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);