mirror of
https://github.com/penpot/penpot.git
synced 2025-01-06 14:50:20 -05:00
Merge pull request #5392 from penpot/ladybenko-9266-linear-gradients
🎉 Render linear gradient fills
This commit is contained in:
commit
61bf4e145e
7 changed files with 184 additions and 76 deletions
|
@ -8,6 +8,7 @@
|
|||
"A WASM based render API"
|
||||
(:require
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.math :as mth]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.config :as cf]
|
||||
[app.render-wasm.helpers :as h]
|
||||
|
@ -34,6 +35,14 @@
|
|||
(h/call internal-module "_render_without_cache")
|
||||
(set! 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 cancel-render
|
||||
[]
|
||||
(when internal-frame-id
|
||||
|
@ -92,14 +101,22 @@
|
|||
[fills]
|
||||
(h/call internal-module "_clear_shape_fills")
|
||||
(run! (fn [fill]
|
||||
(let [opacity (:fill-opacity fill)
|
||||
color (:fill-color fill)]
|
||||
(let [opacity (or (:fill-opacity fill) 1.0)
|
||||
color (:fill-color fill)
|
||||
gradient (:fill-color-gradient fill)]
|
||||
(when ^boolean color
|
||||
(let [rgb (js/parseInt (subs color 1) 16)
|
||||
r (bit-shift-right rgb 16)
|
||||
g (bit-and (bit-shift-right rgb 8) 255)
|
||||
b (bit-and rgb 255)]
|
||||
(h/call internal-module "_add_shape_solid_fill" r g b opacity)))))
|
||||
(let [rgba (rgba-from-hex color opacity)]
|
||||
(h/call internal-module "_add_shape_solid_fill" rgba)))
|
||||
(when (and (some? gradient) (= (:type gradient) :linear))
|
||||
(h/call internal-module "_add_shape_linear_fill"
|
||||
(:start-x gradient)
|
||||
(:start-y gradient)
|
||||
(:end-x gradient)
|
||||
(:end-y gradient)
|
||||
opacity)
|
||||
(run! (fn [stop]
|
||||
(let [rgba (rgba-from-hex (:color stop) (:opacity stop))]
|
||||
(h/call internal-module "_add_shape_fill_stop" rgba (:offset stop)))) (:stops gradient)))))
|
||||
fills))
|
||||
|
||||
(defn- translate-blend-mode
|
||||
|
|
|
@ -150,12 +150,40 @@ pub extern "C" fn clear_shape_children() {
|
|||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn add_shape_solid_fill(r: u8, g: u8, b: u8, a: f32) {
|
||||
pub extern "C" fn add_shape_solid_fill(raw_color: u32) {
|
||||
let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer");
|
||||
if let Some(shape) = state.current_shape() {
|
||||
let alpha: u8 = (a * 0xff as f32).floor() as u8;
|
||||
let color = skia::Color::from_argb(alpha, r, g, b);
|
||||
shape.add_fill(shapes::Fill::from(color));
|
||||
let color = skia::Color::new(raw_color);
|
||||
shape.add_fill(shapes::Fill::Solid(color));
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn add_shape_linear_fill(
|
||||
start_x: f32,
|
||||
start_y: f32,
|
||||
end_x: f32,
|
||||
end_y: f32,
|
||||
opacity: f32,
|
||||
) {
|
||||
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_linear_gradient(
|
||||
(start_x, start_y),
|
||||
(end_x, end_y),
|
||||
opacity,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn add_shape_fill_stop(raw_color: u32, offset: f32) {
|
||||
let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer");
|
||||
if let Some(shape) = state.current_shape() {
|
||||
let color = skia::Color::new(raw_color);
|
||||
shape
|
||||
.add_gradient_stop(color, offset)
|
||||
.expect("got no fill or an invalid one");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
use skia_safe as skia;
|
||||
|
||||
pub type Rect = skia::Rect;
|
||||
pub type Matrix = skia::Matrix;
|
||||
|
|
|
@ -212,7 +212,7 @@ impl RenderState {
|
|||
for fill in shape.fills().rev() {
|
||||
self.drawing_surface
|
||||
.canvas()
|
||||
.draw_rect(shape.selrect, &fill.to_paint());
|
||||
.draw_rect(shape.selrect, &fill.to_paint(&shape.selrect));
|
||||
}
|
||||
|
||||
let mut paint = skia::Paint::default();
|
||||
|
|
|
@ -2,21 +2,17 @@ use crate::math;
|
|||
use skia_safe as skia;
|
||||
use uuid::Uuid;
|
||||
|
||||
mod blend;
|
||||
mod fills;
|
||||
pub use blend::*;
|
||||
pub use fills::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Kind {
|
||||
None,
|
||||
Text,
|
||||
Path,
|
||||
SVGRaw,
|
||||
Image,
|
||||
Circle,
|
||||
Rect,
|
||||
Bool,
|
||||
Group,
|
||||
Frame,
|
||||
}
|
||||
|
||||
type Color = skia::Color;
|
||||
pub type Color = skia::Color;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Matrix {
|
||||
|
@ -41,60 +37,8 @@ impl Matrix {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Fill {
|
||||
Solid(Color), // TODO: add more fills here
|
||||
}
|
||||
|
||||
impl From<Color> for Fill {
|
||||
fn from(value: Color) -> Self {
|
||||
Self::Solid(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl Fill {
|
||||
pub fn to_paint(&self) -> skia::Paint {
|
||||
match self {
|
||||
Self::Solid(color) => {
|
||||
let mut p = skia::Paint::default();
|
||||
p.set_color(*color);
|
||||
p.set_style(skia::PaintStyle::Fill);
|
||||
p.set_anti_alias(true);
|
||||
p.set_blend_mode(skia::BlendMode::SrcOver);
|
||||
p
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
pub struct BlendMode(skia::BlendMode);
|
||||
|
||||
impl Default for BlendMode {
|
||||
fn default() -> Self {
|
||||
BlendMode(skia::BlendMode::SrcOver)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i32> for BlendMode {
|
||||
fn from(value: i32) -> Self {
|
||||
if value <= skia::BlendMode::Luminosity as i32 {
|
||||
unsafe { Self(std::mem::transmute(value)) }
|
||||
} else {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<skia::BlendMode> for BlendMode {
|
||||
fn into(self) -> skia::BlendMode {
|
||||
match self {
|
||||
Self(skia_blend) => skia_blend,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[allow(dead_code)]
|
||||
pub struct Shape {
|
||||
pub id: Uuid,
|
||||
pub children: Vec<Uuid>,
|
||||
|
@ -146,6 +90,18 @@ impl Shape {
|
|||
self.fills.clear();
|
||||
}
|
||||
|
||||
pub fn add_gradient_stop(&mut self, color: skia::Color, offset: f32) -> Result<(), String> {
|
||||
let fill = self.fills.last_mut().ok_or("Shape has no fills")?;
|
||||
let gradient = match fill {
|
||||
Fill::LinearGradient(g) => Ok(g),
|
||||
_ => Err("Active fill is not a gradient"),
|
||||
}?;
|
||||
|
||||
gradient.add_stop(color, offset);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_blend_mode(&mut self, mode: BlendMode) {
|
||||
self.blend_mode = mode;
|
||||
}
|
||||
|
|
28
render-wasm/src/shapes/blend.rs
Normal file
28
render-wasm/src/shapes/blend.rs
Normal file
|
@ -0,0 +1,28 @@
|
|||
use skia_safe as skia;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
pub struct BlendMode(skia::BlendMode);
|
||||
|
||||
impl Default for BlendMode {
|
||||
fn default() -> Self {
|
||||
BlendMode(skia::BlendMode::SrcOver)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i32> for BlendMode {
|
||||
fn from(value: i32) -> Self {
|
||||
if value <= skia::BlendMode::Luminosity as i32 {
|
||||
unsafe { Self(std::mem::transmute(value)) }
|
||||
} else {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<skia::BlendMode> for BlendMode {
|
||||
fn into(self) -> skia::BlendMode {
|
||||
match self {
|
||||
Self(skia_blend) => skia_blend,
|
||||
}
|
||||
}
|
||||
}
|
80
render-wasm/src/shapes/fills.rs
Normal file
80
render-wasm/src/shapes/fills.rs
Normal file
|
@ -0,0 +1,80 @@
|
|||
use skia_safe as skia;
|
||||
|
||||
use super::Color;
|
||||
use crate::math;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Gradient {
|
||||
colors: Vec<Color>,
|
||||
offsets: Vec<f32>,
|
||||
opacity: f32,
|
||||
start: (f32, f32),
|
||||
end: (f32, f32),
|
||||
}
|
||||
|
||||
impl Gradient {
|
||||
pub fn add_stop(&mut self, color: Color, offset: f32) {
|
||||
self.colors.push(color);
|
||||
self.offsets.push(offset);
|
||||
}
|
||||
|
||||
fn to_shader(&self, rect: &math::Rect) -> skia::Shader {
|
||||
let start = (
|
||||
rect.left + self.start.0 * rect.width(),
|
||||
rect.top + self.start.1 * rect.height(),
|
||||
);
|
||||
let end = (
|
||||
rect.left + self.end.0 * rect.width(),
|
||||
rect.top + self.end.1 * rect.height(),
|
||||
);
|
||||
let shader = skia::shader::Shader::linear_gradient(
|
||||
(start, end),
|
||||
self.colors.as_slice(),
|
||||
self.offsets.as_slice(),
|
||||
skia::TileMode::Clamp,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
shader
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Fill {
|
||||
Solid(Color),
|
||||
LinearGradient(Gradient),
|
||||
}
|
||||
|
||||
impl Fill {
|
||||
pub fn new_linear_gradient(start: (f32, f32), end: (f32, f32), opacity: f32) -> Self {
|
||||
Self::LinearGradient(Gradient {
|
||||
start,
|
||||
end,
|
||||
opacity,
|
||||
colors: vec![],
|
||||
offsets: vec![],
|
||||
})
|
||||
}
|
||||
|
||||
pub fn to_paint(&self, rect: &math::Rect) -> skia::Paint {
|
||||
match self {
|
||||
Self::Solid(color) => {
|
||||
let mut p = skia::Paint::default();
|
||||
p.set_color(*color);
|
||||
p.set_style(skia::PaintStyle::Fill);
|
||||
p.set_anti_alias(true);
|
||||
p.set_blend_mode(skia::BlendMode::SrcOver);
|
||||
p
|
||||
}
|
||||
Self::LinearGradient(gradient) => {
|
||||
let mut p = skia::Paint::default();
|
||||
p.set_shader(gradient.to_shader(&rect));
|
||||
p.set_alpha((gradient.opacity * 255.) as u8);
|
||||
p.set_style(skia::PaintStyle::Fill);
|
||||
p.set_blend_mode(skia::BlendMode::SrcOver);
|
||||
p
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue