mirror of
https://github.com/penpot/penpot.git
synced 2025-07-26 03:07:17 +02:00
Merge pull request #5388 from penpot/azazeln28-feat-start-drawing-paths
🎉 Start drawing paths
This commit is contained in:
commit
0eedc036be
12 changed files with 307 additions and 34 deletions
|
@ -43,7 +43,7 @@
|
||||||
|
|
||||||
[:div {:class (stl/css :debug-panel-inner)}
|
[:div {:class (stl/css :debug-panel-inner)}
|
||||||
(for [option (sort-by d/name dbg/options)]
|
(for [option (sort-by d/name dbg/options)]
|
||||||
[:div {:class (stl/css :checkbox-wrapper)}
|
[:div {:key (d/name option) :class (stl/css :checkbox-wrapper)}
|
||||||
[:span {:class (stl/css-case :checkbox-icon true :global/checked (dbg/enabled? option))
|
[:span {:class (stl/css-case :checkbox-icon true :global/checked (dbg/enabled? option))
|
||||||
:on-click #(on-toggle-enabled % option)}
|
:on-click #(on-toggle-enabled % option)}
|
||||||
(when (dbg/enabled? option) i/status-tick)]
|
(when (dbg/enabled? option) i/status-tick)]
|
||||||
|
|
|
@ -9,9 +9,11 @@
|
||||||
(:require
|
(:require
|
||||||
[app.common.data.macros :as dm]
|
[app.common.data.macros :as dm]
|
||||||
[app.common.math :as mth]
|
[app.common.math :as mth]
|
||||||
|
[app.common.svg.path :as path]
|
||||||
[app.common.uuid :as uuid]
|
[app.common.uuid :as uuid]
|
||||||
[app.config :as cf]
|
[app.config :as cf]
|
||||||
[app.render-wasm.helpers :as h]
|
[app.render-wasm.helpers :as h]
|
||||||
|
[app.util.debug :as dbg]
|
||||||
[app.util.functions :as fns]
|
[app.util.functions :as fns]
|
||||||
[app.util.http :as http]
|
[app.util.http :as http]
|
||||||
[app.util.webapi :as wapi]
|
[app.util.webapi :as wapi]
|
||||||
|
@ -185,6 +187,16 @@
|
||||||
(store-image id))))))
|
(store-image id))))))
|
||||||
fills))
|
fills))
|
||||||
|
|
||||||
|
(defn set-shape-path-content
|
||||||
|
[content]
|
||||||
|
(let [buffer (path/content->buffer content)
|
||||||
|
size (.-byteLength buffer)
|
||||||
|
ptr (h/call internal-module "_alloc_bytes" size)
|
||||||
|
heap (gobj/get ^js internal-module "HEAPU8")
|
||||||
|
mem (js/Uint8Array. (.-buffer heap) ptr size)]
|
||||||
|
(.set mem (js/Uint8Array. buffer))
|
||||||
|
(h/call internal-module "_set_shape_path_content")))
|
||||||
|
|
||||||
(defn- translate-blend-mode
|
(defn- translate-blend-mode
|
||||||
[blend-mode]
|
[blend-mode]
|
||||||
(case blend-mode
|
(case blend-mode
|
||||||
|
@ -236,15 +248,18 @@
|
||||||
(loop [index 0 pending []]
|
(loop [index 0 pending []]
|
||||||
(if (< index total-shapes)
|
(if (< index total-shapes)
|
||||||
(let [shape (nth shapes index)
|
(let [shape (nth shapes index)
|
||||||
|
type (dm/get-prop shape :type)
|
||||||
id (dm/get-prop shape :id)
|
id (dm/get-prop shape :id)
|
||||||
selrect (dm/get-prop shape :selrect)
|
selrect (dm/get-prop shape :selrect)
|
||||||
rotation (dm/get-prop shape :rotation)
|
rotation (dm/get-prop shape :rotation)
|
||||||
transform (dm/get-prop shape :transform)
|
transform (dm/get-prop shape :transform)
|
||||||
fills (dm/get-prop shape :fills)
|
fills (if (= type :group)
|
||||||
|
[] (dm/get-prop shape :fills))
|
||||||
children (dm/get-prop shape :shapes)
|
children (dm/get-prop shape :shapes)
|
||||||
blend-mode (dm/get-prop shape :blend-mode)
|
blend-mode (dm/get-prop shape :blend-mode)
|
||||||
opacity (dm/get-prop shape :opacity)
|
opacity (dm/get-prop shape :opacity)
|
||||||
hidden (dm/get-prop shape :hidden)]
|
hidden (dm/get-prop shape :hidden)
|
||||||
|
content (dm/get-prop shape :content)]
|
||||||
|
|
||||||
(use-shape id)
|
(use-shape id)
|
||||||
(set-shape-selrect selrect)
|
(set-shape-selrect selrect)
|
||||||
|
@ -254,6 +269,7 @@
|
||||||
(set-shape-children children)
|
(set-shape-children children)
|
||||||
(set-shape-opacity opacity)
|
(set-shape-opacity opacity)
|
||||||
(set-shape-hidden hidden)
|
(set-shape-hidden hidden)
|
||||||
|
(when (and (some? content) (= type :path)) (set-shape-path-content content))
|
||||||
(let [pending-fills (doall (set-shape-fills fills))]
|
(let [pending-fills (doall (set-shape-fills fills))]
|
||||||
(recur (inc index) (into pending pending-fills))))
|
(recur (inc index) (into pending pending-fills))))
|
||||||
pending))]
|
pending))]
|
||||||
|
@ -279,9 +295,16 @@
|
||||||
[width height]
|
[width height]
|
||||||
(h/call internal-module "_resize_viewbox" width height))
|
(h/call internal-module "_resize_viewbox" width height))
|
||||||
|
|
||||||
|
(defn- debug-flags
|
||||||
|
[]
|
||||||
|
(cond-> 0
|
||||||
|
(dbg/enabled? :wasm-viewbox)
|
||||||
|
(bit-or 2r00000000000000000000000000000001)))
|
||||||
|
|
||||||
(defn assign-canvas
|
(defn assign-canvas
|
||||||
[canvas]
|
[canvas]
|
||||||
(let [gl (unchecked-get internal-module "GL")
|
(let [gl (unchecked-get internal-module "GL")
|
||||||
|
flags (debug-flags)
|
||||||
context (.getContext ^js canvas "webgl2" canvas-options)
|
context (.getContext ^js canvas "webgl2" canvas-options)
|
||||||
|
|
||||||
;; Register the context with emscripten
|
;; Register the context with emscripten
|
||||||
|
@ -290,7 +313,7 @@
|
||||||
|
|
||||||
;; Initialize Wasm Render Engine
|
;; Initialize Wasm Render Engine
|
||||||
(h/call internal-module "_init" (/ (.-width ^js canvas) dpr) (/ (.-height ^js canvas) dpr))
|
(h/call internal-module "_init" (/ (.-width ^js canvas) dpr) (/ (.-height ^js canvas) dpr))
|
||||||
(h/call internal-module "_set_render_options" 0x01 dpr))
|
(h/call internal-module "_set_render_options" flags dpr))
|
||||||
|
|
||||||
(set! (.-width canvas) (* dpr (.-clientWidth ^js canvas)))
|
(set! (.-width canvas) (* dpr (.-clientWidth ^js canvas)))
|
||||||
(set! (.-height canvas) (* dpr (.-clientHeight ^js canvas))))
|
(set! (.-height canvas) (* dpr (.-clientHeight ^js canvas))))
|
||||||
|
|
72
frontend/src/app/render_wasm/path.cljs
Normal file
72
frontend/src/app/render_wasm/path.cljs
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
(ns app.render-wasm.path)
|
||||||
|
|
||||||
|
(def command-size 28)
|
||||||
|
|
||||||
|
#_(defn content->buffer
|
||||||
|
"Converts the path content into binary format."
|
||||||
|
[content]
|
||||||
|
(let [total (count content)
|
||||||
|
buffer (new js/ArrayBuffer (* total command-size))
|
||||||
|
dview (new js/DataView buffer)]
|
||||||
|
(loop [index 0]
|
||||||
|
(when (< index total)
|
||||||
|
(let [segment (nth content index)
|
||||||
|
offset (* index command-size)]
|
||||||
|
(case (:command segment)
|
||||||
|
:move-to
|
||||||
|
(let [{:keys [x y]} (:params segment)]
|
||||||
|
(.setUint16 dview (+ offset 0) 1)
|
||||||
|
(.setFloat32 dview (+ offset 20) x)
|
||||||
|
(.setFloat32 dview (+ offset 24) y))
|
||||||
|
:line-to
|
||||||
|
(let [{:keys [x y]} (:params segment)]
|
||||||
|
(.setUint16 dview (+ offset 0) 2)
|
||||||
|
(.setFloat32 dview (+ offset 20) x)
|
||||||
|
(.setFloat32 dview (+ offset 24) y))
|
||||||
|
:curve-to
|
||||||
|
(let [{:keys [c1x c1y c2x c2y x y]} (:params segment)]
|
||||||
|
(.setUint16 dview (+ offset 0) 3)
|
||||||
|
(.setFloat32 dview (+ offset 4) c1x)
|
||||||
|
(.setFloat32 dview (+ offset 8) c1y)
|
||||||
|
(.setFloat32 dview (+ offset 12) c2x)
|
||||||
|
(.setFloat32 dview (+ offset 16) c2y)
|
||||||
|
(.setFloat32 dview (+ offset 20) x)
|
||||||
|
(.setFloat32 dview (+ offset 24) y))
|
||||||
|
|
||||||
|
:close-path
|
||||||
|
(.setUint16 dview (+ offset 0) 4))
|
||||||
|
(recur (inc index)))))
|
||||||
|
buffer))
|
||||||
|
|
||||||
|
#_(defn buffer->content
|
||||||
|
"Converts the a buffer to a path content vector"
|
||||||
|
[buffer]
|
||||||
|
(assert (instance? js/ArrayBuffer buffer) "expected ArrayBuffer instance")
|
||||||
|
(let [total (/ (.-byteLength buffer) command-size)
|
||||||
|
dview (new js/DataView buffer)]
|
||||||
|
(loop [index 0
|
||||||
|
result []]
|
||||||
|
(if (< index total)
|
||||||
|
(let [offset (* index command-size)
|
||||||
|
type (.getUint16 dview (+ offset 0))
|
||||||
|
command (case type
|
||||||
|
1 :move-to
|
||||||
|
2 :line-to
|
||||||
|
3 :curve-to
|
||||||
|
4 :close-path)
|
||||||
|
params (case type
|
||||||
|
1 {:x (.getFloat32 dview (+ offset 20))
|
||||||
|
:y (.getFloat32 dview (+ offset 24))}
|
||||||
|
2 {:x (.getFloat32 dview (+ offset 20))
|
||||||
|
:y (.getFloat32 dview (+ offset 24))}
|
||||||
|
3 {:c1x (.getFloat32 dview (+ offset 4))
|
||||||
|
:c1y (.getFloat32 dview (+ offset 8))
|
||||||
|
:c2x (.getFloat32 dview (+ offset 12))
|
||||||
|
:c2y (.getFloat32 dview (+ offset 16))
|
||||||
|
:x (.getFloat32 dview (+ offset 20))
|
||||||
|
:y (.getFloat32 dview (+ offset 24))}
|
||||||
|
4 {})]
|
||||||
|
(recur (inc index)
|
||||||
|
(conj result {:command command
|
||||||
|
:params params})))
|
||||||
|
result))))
|
|
@ -8,7 +8,6 @@
|
||||||
(:require
|
(:require
|
||||||
[app.common.transit :as t]
|
[app.common.transit :as t]
|
||||||
[app.common.types.shape :as shape]
|
[app.common.types.shape :as shape]
|
||||||
;; [app.common.svg.path :as path]
|
|
||||||
[app.render-wasm.api :as api]
|
[app.render-wasm.api :as api]
|
||||||
[clojure.core :as c]
|
[clojure.core :as c]
|
||||||
[cuerdas.core :as str]))
|
[cuerdas.core :as str]))
|
||||||
|
@ -120,6 +119,7 @@
|
||||||
:opacity (api/set-shape-opacity v)
|
:opacity (api/set-shape-opacity v)
|
||||||
:hidden (api/set-shape-hidden v)
|
:hidden (api/set-shape-hidden v)
|
||||||
:shapes (api/set-shape-children v)
|
:shapes (api/set-shape-children v)
|
||||||
|
:content (api/set-shape-path-content v)
|
||||||
nil)
|
nil)
|
||||||
;; when something synced with wasm
|
;; when something synced with wasm
|
||||||
;; is modified, we need to request
|
;; is modified, we need to request
|
||||||
|
|
|
@ -92,7 +92,10 @@
|
||||||
:bool-shapes
|
:bool-shapes
|
||||||
|
|
||||||
;; Show some information about the WebGL context.
|
;; Show some information about the WebGL context.
|
||||||
:gl-context})
|
:gl-context
|
||||||
|
|
||||||
|
;; Show viewbox
|
||||||
|
:wasm-viewbox})
|
||||||
|
|
||||||
(defn enable!
|
(defn enable!
|
||||||
[option]
|
[option]
|
||||||
|
|
|
@ -103,16 +103,15 @@ pub extern "C" fn use_shape(a: u32, b: u32, c: u32, d: u32) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn set_shape_selrect(left: f32, top: f32, right: f32, bottom: f32) {
|
pub extern "C" fn set_shape_selrect(left: f32, top: f32, right: f32, bottom: f32) {
|
||||||
let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer");
|
let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer");
|
||||||
|
|
||||||
if let Some(shape) = state.current_shape() {
|
if let Some(shape) = state.current_shape() {
|
||||||
shape.selrect.set_ltrb(left, top, right, bottom);
|
shape.set_selrect(left, top, right, bottom);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn set_shape_rotation(rotation: f32) {
|
pub extern "C" fn set_shape_rotation(rotation: f32) {
|
||||||
let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer");
|
let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer");
|
||||||
if let Some(shape) = state.current_shape() {
|
if let Some(shape) = state.current_shape() {
|
||||||
shape.rotation = rotation;
|
shape.rotation = rotation;
|
||||||
|
@ -120,7 +119,7 @@ pub unsafe extern "C" fn set_shape_rotation(rotation: f32) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn set_shape_transform(a: f32, b: f32, c: f32, d: f32, e: f32, f: f32) {
|
pub extern "C" fn set_shape_transform(a: f32, b: f32, c: f32, d: f32, e: f32, f: f32) {
|
||||||
let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer");
|
let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer");
|
||||||
if let Some(shape) = state.current_shape() {
|
if let Some(shape) = state.current_shape() {
|
||||||
shape.transform.a = a;
|
shape.transform.a = a;
|
||||||
|
@ -271,6 +270,22 @@ pub extern "C" fn set_shape_hidden(hidden: bool) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn set_shape_path_content() {
|
||||||
|
let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer");
|
||||||
|
|
||||||
|
if let Some(shape) = state.current_shape() {
|
||||||
|
let bytes = mem::bytes();
|
||||||
|
let raw_segments = bytes
|
||||||
|
.chunks(size_of::<shapes::RawPathData>())
|
||||||
|
.map(|data| shapes::RawPathData {
|
||||||
|
data: data.try_into().unwrap(),
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
shape.set_path_segments(raw_segments).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
init_gl();
|
init_gl();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
use skia_safe as skia;
|
use skia_safe as skia;
|
||||||
|
|
||||||
pub type Rect = skia::Rect;
|
pub type Rect = skia::Rect;
|
||||||
|
pub type Point = (f32, f32);
|
||||||
|
|
|
@ -7,7 +7,7 @@ pub extern "C" fn alloc_bytes(len: usize) -> *mut u8 {
|
||||||
panic!("Bytes already allocated");
|
panic!("Bytes already allocated");
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut buffer = Box::new(Vec::<u8>::with_capacity(len));
|
let mut buffer = Box::new(vec![0u8; len]);
|
||||||
let ptr = buffer.as_mut_ptr();
|
let ptr = buffer.as_mut_ptr();
|
||||||
|
|
||||||
unsafe { BUFFERU8 = Some(buffer) };
|
unsafe { BUFFERU8 = Some(buffer) };
|
||||||
|
@ -23,3 +23,8 @@ pub fn buffer_ptr() -> *mut u8 {
|
||||||
let buffer = unsafe { BUFFERU8.as_mut() }.expect("uninitializied buffer");
|
let buffer = unsafe { BUFFERU8.as_mut() }.expect("uninitializied buffer");
|
||||||
buffer.as_mut_ptr()
|
buffer.as_mut_ptr()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn bytes() -> Vec<u8> {
|
||||||
|
let buffer = unsafe { BUFFERU8.take() }.expect("uninitialized buffer");
|
||||||
|
*buffer
|
||||||
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ use uuid::Uuid;
|
||||||
|
|
||||||
use crate::debug;
|
use crate::debug;
|
||||||
use crate::math::Rect;
|
use crate::math::Rect;
|
||||||
use crate::shapes::{draw_image_in_container, Fill, Image, Shape};
|
use crate::shapes::{draw_image_in_container, Fill, Image, Kind, Shape};
|
||||||
use crate::view::Viewbox;
|
use crate::view::Viewbox;
|
||||||
|
|
||||||
struct GpuState {
|
struct GpuState {
|
||||||
|
@ -224,7 +224,7 @@ impl RenderState {
|
||||||
self.drawing_surface.canvas().concat(&matrix);
|
self.drawing_surface.canvas().concat(&matrix);
|
||||||
|
|
||||||
for fill in shape.fills().rev() {
|
for fill in shape.fills().rev() {
|
||||||
self.render_fill(fill, shape.selrect);
|
self.render_fill(fill, shape.selrect, &shape.kind);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut paint = skia::Paint::default();
|
let mut paint = skia::Paint::default();
|
||||||
|
@ -281,22 +281,30 @@ impl RenderState {
|
||||||
self.flush();
|
self.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_fill(&mut self, fill: &Fill, selrect: Rect) {
|
fn render_fill(&mut self, fill: &Fill, selrect: Rect, kind: &Kind) {
|
||||||
if let Fill::Image(image_fill) = fill {
|
match (fill, kind) {
|
||||||
let image = self.images.get(&image_fill.id());
|
(Fill::Image(image_fill), kind) => {
|
||||||
if let Some(image) = image {
|
let image = self.images.get(&image_fill.id());
|
||||||
draw_image_in_container(
|
if let Some(image) = image {
|
||||||
&self.drawing_surface.canvas(),
|
draw_image_in_container(
|
||||||
&image,
|
&self.drawing_surface.canvas(),
|
||||||
image_fill.size(),
|
&image,
|
||||||
selrect,
|
image_fill.size(),
|
||||||
&fill.to_paint(&selrect),
|
kind,
|
||||||
);
|
&fill.to_paint(&selrect),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(_, Kind::Rect(rect)) => {
|
||||||
|
self.drawing_surface
|
||||||
|
.canvas()
|
||||||
|
.draw_rect(rect, &fill.to_paint(&selrect));
|
||||||
|
}
|
||||||
|
(_, Kind::Path(path)) => {
|
||||||
|
self.drawing_surface
|
||||||
|
.canvas()
|
||||||
|
.draw_path(&path.to_skia_path(), &fill.to_paint(&selrect));
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
self.drawing_surface
|
|
||||||
.canvas()
|
|
||||||
.draw_rect(selrect, &fill.to_paint(&selrect));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,13 +5,16 @@ use uuid::Uuid;
|
||||||
mod blend;
|
mod blend;
|
||||||
mod fills;
|
mod fills;
|
||||||
mod images;
|
mod images;
|
||||||
|
mod paths;
|
||||||
pub use blend::*;
|
pub use blend::*;
|
||||||
pub use fills::*;
|
pub use fills::*;
|
||||||
pub use images::*;
|
pub use images::*;
|
||||||
|
pub use paths::*;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum Kind {
|
pub enum Kind {
|
||||||
Rect,
|
Rect(math::Rect),
|
||||||
|
Path(Path),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Color = skia::Color;
|
pub type Color = skia::Color;
|
||||||
|
@ -59,7 +62,7 @@ impl Shape {
|
||||||
Self {
|
Self {
|
||||||
id,
|
id,
|
||||||
children: Vec::<Uuid>::new(),
|
children: Vec::<Uuid>::new(),
|
||||||
kind: Kind::Rect,
|
kind: Kind::Rect(math::Rect::new_empty()),
|
||||||
selrect: math::Rect::new_empty(),
|
selrect: math::Rect::new_empty(),
|
||||||
transform: Matrix::identity(),
|
transform: Matrix::identity(),
|
||||||
rotation: 0.,
|
rotation: 0.,
|
||||||
|
@ -70,6 +73,13 @@ impl Shape {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_selrect(&mut self, left: f32, top: f32, right: f32, bottom: f32) {
|
||||||
|
self.selrect.set_ltrb(left, top, right, bottom);
|
||||||
|
if let Kind::Rect(_) = self.kind {
|
||||||
|
self.kind = Kind::Rect(self.selrect.to_owned());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn translation(&self) -> (f32, f32) {
|
pub fn translation(&self) -> (f32, f32) {
|
||||||
(self.transform.e, self.transform.f)
|
(self.transform.e, self.transform.f)
|
||||||
}
|
}
|
||||||
|
@ -108,6 +118,12 @@ impl Shape {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_path_segments(&mut self, buffer: Vec<RawPathData>) -> Result<(), String> {
|
||||||
|
let p = Path::try_from(buffer)?;
|
||||||
|
self.kind = Kind::Path(p);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_blend_mode(&mut self, mode: BlendMode) {
|
pub fn set_blend_mode(&mut self, mode: BlendMode) {
|
||||||
self.blend_mode = mode;
|
self.blend_mode = mode;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,17 +3,24 @@ use skia_safe as skia;
|
||||||
|
|
||||||
pub type Image = skia::Image;
|
pub type Image = skia::Image;
|
||||||
|
|
||||||
|
use crate::shapes::Kind;
|
||||||
|
|
||||||
pub fn draw_image_in_container(
|
pub fn draw_image_in_container(
|
||||||
canvas: &skia::Canvas,
|
canvas: &skia::Canvas,
|
||||||
image: &Image,
|
image: &Image,
|
||||||
size: (i32, i32),
|
size: (i32, i32),
|
||||||
container: skia::Rect,
|
kind: &Kind,
|
||||||
paint: &skia::Paint,
|
paint: &skia::Paint,
|
||||||
) {
|
) {
|
||||||
let width = size.0 as f32;
|
let width = size.0 as f32;
|
||||||
let height = size.1 as f32;
|
let height = size.1 as f32;
|
||||||
let image_aspect_ratio = width / height;
|
let image_aspect_ratio = width / height;
|
||||||
|
|
||||||
|
let container = match kind {
|
||||||
|
Kind::Rect(r) => r.to_owned(),
|
||||||
|
Kind::Path(p) => p.to_skia_path().bounds().to_owned(),
|
||||||
|
};
|
||||||
|
|
||||||
// Container size
|
// Container size
|
||||||
let container_width = container.width();
|
let container_width = container.width();
|
||||||
let container_height = container.height();
|
let container_height = container.height();
|
||||||
|
@ -42,7 +49,14 @@ pub fn draw_image_in_container(
|
||||||
canvas.save();
|
canvas.save();
|
||||||
|
|
||||||
// Set the clipping rectangle to the container bounds
|
// Set the clipping rectangle to the container bounds
|
||||||
canvas.clip_rect(container, skia::ClipOp::Intersect, true);
|
match kind {
|
||||||
|
Kind::Rect(_) => {
|
||||||
|
canvas.clip_rect(container, skia::ClipOp::Intersect, true);
|
||||||
|
}
|
||||||
|
Kind::Path(p) => {
|
||||||
|
canvas.clip_path(&p.to_skia_path(), skia::ClipOp::Intersect, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Draw the image with the calculated destination rectangle
|
// Draw the image with the calculated destination rectangle
|
||||||
canvas.draw_image_rect(image, None, dest_rect, &paint);
|
canvas.draw_image_rect(image, None, dest_rect, &paint);
|
||||||
|
|
116
render-wasm/src/shapes/paths.rs
Normal file
116
render-wasm/src/shapes/paths.rs
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
use skia_safe as skia;
|
||||||
|
use std::array::TryFromSliceError;
|
||||||
|
|
||||||
|
use crate::math::Point;
|
||||||
|
|
||||||
|
fn stringify_slice_err(_: TryFromSliceError) -> String {
|
||||||
|
format!("Error deserializing path")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct RawPathData {
|
||||||
|
pub data: [u8; 28],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RawPathData {
|
||||||
|
fn command(&self) -> Result<u16, String> {
|
||||||
|
let cmd = u16::from_be_bytes(self.data[0..2].try_into().map_err(stringify_slice_err)?);
|
||||||
|
Ok(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn xy(&self) -> Result<Point, String> {
|
||||||
|
let x = f32::from_be_bytes(self.data[20..24].try_into().map_err(stringify_slice_err)?);
|
||||||
|
let y = f32::from_be_bytes(self.data[24..].try_into().map_err(stringify_slice_err)?);
|
||||||
|
Ok((x, y))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn c1(&self) -> Result<Point, String> {
|
||||||
|
let c1_x = f32::from_be_bytes(self.data[4..8].try_into().map_err(stringify_slice_err)?);
|
||||||
|
let c1_y = f32::from_be_bytes(self.data[8..12].try_into().map_err(stringify_slice_err)?);
|
||||||
|
|
||||||
|
Ok((c1_x, c1_y))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn c2(&self) -> Result<Point, String> {
|
||||||
|
let c2_x = f32::from_be_bytes(self.data[12..16].try_into().map_err(stringify_slice_err)?);
|
||||||
|
let c2_y = f32::from_be_bytes(self.data[16..20].try_into().map_err(stringify_slice_err)?);
|
||||||
|
|
||||||
|
Ok((c2_x, c2_y))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const MOVE_TO: u16 = 1;
|
||||||
|
const LINE_TO: u16 = 2;
|
||||||
|
const CURVE_TO: u16 = 3;
|
||||||
|
const CLOSE: u16 = 4;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Copy, Clone)]
|
||||||
|
enum Segment {
|
||||||
|
MoveTo(Point),
|
||||||
|
LineTo(Point),
|
||||||
|
CurveTo((Point, Point, Point)),
|
||||||
|
Close,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<RawPathData> for Segment {
|
||||||
|
type Error = String;
|
||||||
|
fn try_from(value: RawPathData) -> Result<Self, Self::Error> {
|
||||||
|
let cmd = value.command()?;
|
||||||
|
match cmd {
|
||||||
|
MOVE_TO => Ok(Segment::MoveTo(value.xy()?)),
|
||||||
|
LINE_TO => Ok(Segment::LineTo(value.xy()?)),
|
||||||
|
CURVE_TO => Ok(Segment::CurveTo((value.c1()?, value.c2()?, value.xy()?))),
|
||||||
|
CLOSE => Ok(Segment::Close),
|
||||||
|
_ => Err(format!(
|
||||||
|
"Error deserializing path. Unknown command/flags: {:#010x}",
|
||||||
|
cmd
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct Path {
|
||||||
|
segments: Vec<Segment>,
|
||||||
|
skia_path: skia::Path,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Vec<RawPathData>> for Path {
|
||||||
|
type Error = String;
|
||||||
|
|
||||||
|
fn try_from(value: Vec<RawPathData>) -> Result<Self, Self::Error> {
|
||||||
|
let segments = value
|
||||||
|
.into_iter()
|
||||||
|
.map(|raw| Segment::try_from(raw))
|
||||||
|
.collect::<Result<Vec<Segment>, String>>()?;
|
||||||
|
|
||||||
|
let mut skia_path = skia::Path::new();
|
||||||
|
for segment in segments.iter() {
|
||||||
|
match *segment {
|
||||||
|
Segment::MoveTo(xy) => {
|
||||||
|
skia_path.move_to(xy);
|
||||||
|
}
|
||||||
|
Segment::LineTo(xy) => {
|
||||||
|
skia_path.line_to(xy);
|
||||||
|
}
|
||||||
|
Segment::CurveTo((c1, c2, xy)) => {
|
||||||
|
skia_path.cubic_to(c1, c2, xy);
|
||||||
|
}
|
||||||
|
Segment::Close => {
|
||||||
|
skia_path.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Path {
|
||||||
|
segments,
|
||||||
|
skia_path,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Path {
|
||||||
|
pub fn to_skia_path(&self) -> skia::Path {
|
||||||
|
self.skia_path.snapshot()
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue