mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-17 07:18:04 +08:00
Integrate Vello for vector rendering (#1802)
* Start integrating vello into render pipeline Cache vello render creation Implement viewport navigation Close vello path Add transform parameter to vello render pass * Fix render node types * Fix a bunch of bugs in the path translation * Avoid panic on empty document * Fix rendering of holes * Implement image rendering * Implement graph recompilation afer editor api change * Implement preferences toggle for using vello as the renderer * Make surface creation optional * Feature gate vello usages * Implement skeleton for radial gradient * Rename vello preference * Fix some gradients * Only update monitor nodes on graph recompile * Fix warnings + remove dead code * Update everything except for thumbnails after a node graph evaluation * Fix missing click targets for Image frames * Improve perfamance by removing unecessary widget updates * Fix node graph paning * Fix thumbnail loading * Implement proper hash for vector modification * Fix test and warnings * Code review * Fix dep * Remove warning --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
@@ -10,7 +10,7 @@ use core::future::Future;
|
||||
use core::hash::{Hash, Hasher};
|
||||
use core::pin::Pin;
|
||||
use core::ptr::addr_of;
|
||||
use glam::DAffine2;
|
||||
use glam::{DAffine2, UVec2};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
@@ -26,6 +26,7 @@ impl core::fmt::Display for SurfaceId {
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct SurfaceFrame {
|
||||
pub surface_id: SurfaceId,
|
||||
pub resolution: UVec2,
|
||||
pub transform: DAffine2,
|
||||
}
|
||||
|
||||
@@ -51,11 +52,23 @@ unsafe impl StaticType for SurfaceFrame {
|
||||
type Static = SurfaceFrame;
|
||||
}
|
||||
|
||||
impl<S> From<SurfaceHandleFrame<S>> for SurfaceFrame {
|
||||
pub trait Size {
|
||||
fn size(&self) -> UVec2;
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
impl Size for web_sys::HtmlCanvasElement {
|
||||
fn size(&self) -> UVec2 {
|
||||
UVec2::new(self.width(), self.height())
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Size> From<SurfaceHandleFrame<S>> for SurfaceFrame {
|
||||
fn from(x: SurfaceHandleFrame<S>) -> Self {
|
||||
Self {
|
||||
surface_id: x.surface_handle.surface_id,
|
||||
transform: x.transform,
|
||||
resolution: x.surface_handle.surface.size(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,6 +83,12 @@ pub struct SurfaceHandle<Surface> {
|
||||
// #[cfg(target_arch = "wasm32")]
|
||||
// unsafe impl<T: dyn_any::WasmNotSync> Sync for SurfaceHandle<T> {}
|
||||
|
||||
impl<S: Size> Size for SurfaceHandle<S> {
|
||||
fn size(&self) -> UVec2 {
|
||||
self.surface.size()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: 'static> StaticType for SurfaceHandle<T> {
|
||||
type Static = SurfaceHandle<T>;
|
||||
}
|
||||
@@ -162,8 +181,9 @@ impl<T: NodeGraphUpdateSender> NodeGraphUpdateSender for std::sync::Mutex<T> {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait GetImaginatePreferences {
|
||||
fn get_host_name(&self) -> &str;
|
||||
pub trait GetEditorPreferences {
|
||||
fn hostname(&self) -> &str;
|
||||
fn use_vello(&self) -> bool;
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
@@ -196,10 +216,14 @@ impl NodeGraphUpdateSender for Logger {
|
||||
|
||||
struct DummyPreferences;
|
||||
|
||||
impl GetImaginatePreferences for DummyPreferences {
|
||||
fn get_host_name(&self) -> &str {
|
||||
impl GetEditorPreferences for DummyPreferences {
|
||||
fn hostname(&self) -> &str {
|
||||
"dummy_endpoint"
|
||||
}
|
||||
|
||||
fn use_vello(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EditorApi<Io> {
|
||||
@@ -208,8 +232,8 @@ pub struct EditorApi<Io> {
|
||||
/// Gives access to APIs like a rendering surface (native window handle or HTML5 canvas) and WGPU (which becomes WebGPU on web).
|
||||
pub application_io: Option<Arc<Io>>,
|
||||
pub node_graph_message_sender: Box<dyn NodeGraphUpdateSender + Send + Sync>,
|
||||
/// Imaginate preferences made available to the graph through the [`WasmEditorApi`].
|
||||
pub imaginate_preferences: Box<dyn GetImaginatePreferences + Send + Sync>,
|
||||
/// Editor preferences made available to the graph through the [`WasmEditorApi`].
|
||||
pub editor_preferences: Box<dyn GetEditorPreferences + Send + Sync>,
|
||||
}
|
||||
|
||||
impl<Io> Eq for EditorApi<Io> {}
|
||||
@@ -220,7 +244,7 @@ impl<Io: Default> Default for EditorApi<Io> {
|
||||
font_cache: FontCache::default(),
|
||||
application_io: None,
|
||||
node_graph_message_sender: Box::new(Logger),
|
||||
imaginate_preferences: Box::new(DummyPreferences),
|
||||
editor_preferences: Box::new(DummyPreferences),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -230,7 +254,7 @@ impl<Io> Hash for EditorApi<Io> {
|
||||
self.font_cache.hash(state);
|
||||
self.application_io.as_ref().map_or(0, |io| io.as_ref() as *const _ as usize).hash(state);
|
||||
(self.node_graph_message_sender.as_ref() as *const dyn NodeGraphUpdateSender).hash(state);
|
||||
(self.imaginate_preferences.as_ref() as *const dyn GetImaginatePreferences).hash(state);
|
||||
(self.editor_preferences.as_ref() as *const dyn GetEditorPreferences).hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,7 +263,7 @@ impl<Io> PartialEq for EditorApi<Io> {
|
||||
self.font_cache == other.font_cache
|
||||
&& self.application_io.as_ref().map_or(0, |io| addr_of!(io) as usize) == other.application_io.as_ref().map_or(0, |io| addr_of!(io) as usize)
|
||||
&& std::ptr::eq(self.node_graph_message_sender.as_ref() as *const _, other.node_graph_message_sender.as_ref() as *const _)
|
||||
&& std::ptr::eq(self.imaginate_preferences.as_ref() as *const _, other.imaginate_preferences.as_ref() as *const _)
|
||||
&& std::ptr::eq(self.editor_preferences.as_ref() as *const _, other.editor_preferences.as_ref() as *const _)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use crate::application_io::SurfaceHandleFrame;
|
||||
use crate::raster::{BlendMode, ImageFrame};
|
||||
use crate::renderer::GraphicElementRendered;
|
||||
use crate::transform::Footprint;
|
||||
use crate::vector::VectorData;
|
||||
use crate::{Color, Node, SurfaceFrame};
|
||||
@@ -9,7 +8,7 @@ use dyn_any::{DynAny, StaticType};
|
||||
use node_macro::node_fn;
|
||||
|
||||
use core::ops::{Deref, DerefMut};
|
||||
use glam::{DAffine2, DVec2, IVec2, UVec2};
|
||||
use glam::{DAffine2, IVec2, UVec2};
|
||||
use web_sys::HtmlCanvasElement;
|
||||
|
||||
pub mod renderer;
|
||||
@@ -202,19 +201,7 @@ async fn add_artboard<Data: Into<Artboard> + Send>(footprint: Footprint, artboar
|
||||
}
|
||||
|
||||
impl From<ImageFrame<Color>> for GraphicElement {
|
||||
fn from(mut image_frame: ImageFrame<Color>) -> Self {
|
||||
use base64::Engine;
|
||||
|
||||
let image = &image_frame.image;
|
||||
if !image.data.is_empty() {
|
||||
let output = image.to_png();
|
||||
let preamble = "data:image/png;base64,";
|
||||
let mut base64_string = String::with_capacity(preamble.len() + output.len() * 4);
|
||||
base64_string.push_str(preamble);
|
||||
base64::engine::general_purpose::STANDARD.encode_string(output, &mut base64_string);
|
||||
image_frame.image.base64_string = Some(base64_string);
|
||||
}
|
||||
|
||||
fn from(image_frame: ImageFrame<Color>) -> Self {
|
||||
GraphicElement::ImageFrame(image_frame)
|
||||
}
|
||||
}
|
||||
@@ -237,14 +224,28 @@ impl From<alloc::sync::Arc<SurfaceHandleFrame<HtmlCanvasElement>>> for GraphicEl
|
||||
fn from(surface: alloc::sync::Arc<SurfaceHandleFrame<HtmlCanvasElement>>) -> Self {
|
||||
let surface_id = surface.surface_handle.surface_id;
|
||||
let transform = surface.transform;
|
||||
GraphicElement::Surface(SurfaceFrame { surface_id, transform })
|
||||
GraphicElement::Surface(SurfaceFrame {
|
||||
surface_id,
|
||||
transform,
|
||||
resolution: UVec2 {
|
||||
x: surface.surface_handle.surface.width(),
|
||||
y: surface.surface_handle.surface.height(),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
impl From<SurfaceHandleFrame<HtmlCanvasElement>> for GraphicElement {
|
||||
fn from(surface: SurfaceHandleFrame<HtmlCanvasElement>) -> Self {
|
||||
let surface_id = surface.surface_handle.surface_id;
|
||||
let transform = surface.transform;
|
||||
GraphicElement::Surface(SurfaceFrame { surface_id, transform })
|
||||
GraphicElement::Surface(SurfaceFrame {
|
||||
surface_id,
|
||||
transform,
|
||||
resolution: UVec2 {
|
||||
x: surface.surface_handle.surface.width(),
|
||||
y: surface.surface_handle.surface.height(),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,21 +288,4 @@ impl GraphicGroup {
|
||||
transform: DAffine2::IDENTITY,
|
||||
alpha_blending: AlphaBlending::new(),
|
||||
};
|
||||
|
||||
pub fn to_usvg_tree(&self, resolution: UVec2, viewbox: [DVec2; 2]) -> usvg::Tree {
|
||||
let mut root_node = usvg::Group::default();
|
||||
let tree = usvg::Tree {
|
||||
size: usvg::Size::from_wh(resolution.x as f32, resolution.y as f32).unwrap(),
|
||||
view_box: usvg::ViewBox {
|
||||
rect: usvg::NonZeroRect::from_ltrb(viewbox[0].x as f32, viewbox[0].y as f32, viewbox[1].x as f32, viewbox[1].y as f32).unwrap(),
|
||||
aspect: usvg::AspectRatio::default(),
|
||||
},
|
||||
root: root_node.clone(),
|
||||
};
|
||||
|
||||
for element in self.iter() {
|
||||
root_node.children.push(element.to_usvg_node());
|
||||
}
|
||||
tree
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ use crate::raster::bbox::Bbox;
|
||||
use crate::raster::{BlendMode, Image, ImageFrame};
|
||||
use crate::transform::Transform;
|
||||
use crate::uuid::generate_uuid;
|
||||
use crate::vector::style::{Fill, Stroke, ViewMode};
|
||||
use crate::vector::PointId;
|
||||
use crate::SurfaceFrame;
|
||||
use crate::{vector::VectorData, Artboard, Color, GraphicElement, GraphicGroup};
|
||||
@@ -13,6 +14,8 @@ use bezier_rs::Subpath;
|
||||
|
||||
use base64::Engine;
|
||||
use glam::{DAffine2, DVec2};
|
||||
#[cfg(feature = "vello")]
|
||||
use vello::*;
|
||||
|
||||
/// Represents a clickable target for the layer
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -166,7 +169,7 @@ pub enum ImageRenderMode {
|
||||
/// Static state used whilst rendering
|
||||
#[derive(Default)]
|
||||
pub struct RenderParams {
|
||||
pub view_mode: crate::vector::style::ViewMode,
|
||||
pub view_mode: ViewMode,
|
||||
pub image_render_mode: ImageRenderMode,
|
||||
pub culling_bounds: Option<[DVec2; 2]>,
|
||||
pub thumbnail: bool,
|
||||
@@ -177,7 +180,7 @@ pub struct RenderParams {
|
||||
}
|
||||
|
||||
impl RenderParams {
|
||||
pub fn new(view_mode: crate::vector::style::ViewMode, image_render_mode: ImageRenderMode, culling_bounds: Option<[DVec2; 2]>, thumbnail: bool, hide_artboards: bool, for_export: bool) -> Self {
|
||||
pub fn new(view_mode: ViewMode, image_render_mode: ImageRenderMode, culling_bounds: Option<[DVec2; 2]>, thumbnail: bool, hide_artboards: bool, for_export: bool) -> Self {
|
||||
Self {
|
||||
view_mode,
|
||||
image_render_mode,
|
||||
@@ -203,7 +206,7 @@ pub fn format_transform_matrix(transform: DAffine2) -> String {
|
||||
result
|
||||
}
|
||||
|
||||
fn to_transform(transform: DAffine2) -> usvg::Transform {
|
||||
pub fn to_transform(transform: DAffine2) -> usvg::Transform {
|
||||
let cols = transform.to_cols_array();
|
||||
usvg::Transform::from_row(cols[0] as f32, cols[1] as f32, cols[2] as f32, cols[3] as f32, cols[4] as f32, cols[5] as f32)
|
||||
}
|
||||
@@ -212,33 +215,14 @@ pub trait GraphicElementRendered {
|
||||
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams);
|
||||
fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]>;
|
||||
fn add_click_targets(&self, click_targets: &mut Vec<ClickTarget>);
|
||||
fn to_usvg_node(&self) -> usvg::Node {
|
||||
let mut render = SvgRender::new();
|
||||
let render_params = RenderParams::new(crate::vector::style::ViewMode::Normal, ImageRenderMode::Base64, None, false, false, false);
|
||||
self.render_svg(&mut render, &render_params);
|
||||
render.format_svg(DVec2::ZERO, DVec2::ONE);
|
||||
let svg = render.svg.to_svg_string();
|
||||
|
||||
let opt = usvg::Options::default();
|
||||
|
||||
let tree = usvg::Tree::from_str(&svg, &opt).expect("Failed to parse SVG");
|
||||
usvg::Node::Group(Box::new(tree.root.clone()))
|
||||
}
|
||||
|
||||
fn to_usvg_tree(&self, resolution: glam::UVec2, viewbox: [DVec2; 2]) -> usvg::Tree {
|
||||
let root = match self.to_usvg_node() {
|
||||
usvg::Node::Group(root_node) => *root_node,
|
||||
_ => usvg::Group::default(),
|
||||
};
|
||||
usvg::Tree {
|
||||
size: usvg::Size::from_wh(resolution.x as f32, resolution.y as f32).unwrap(),
|
||||
view_box: usvg::ViewBox {
|
||||
rect: usvg::NonZeroRect::from_ltrb(viewbox[0].x as f32, viewbox[0].y as f32, viewbox[1].x as f32, viewbox[1].y as f32).unwrap(),
|
||||
aspect: usvg::AspectRatio::default(),
|
||||
},
|
||||
root,
|
||||
}
|
||||
#[cfg(feature = "vello")]
|
||||
fn to_vello_scene(&self, transform: DAffine2) -> Scene {
|
||||
let mut scene = vello::Scene::new();
|
||||
self.render_to_vello(&mut scene, transform);
|
||||
scene
|
||||
}
|
||||
#[cfg(feature = "vello")]
|
||||
fn render_to_vello(&self, _scene: &mut Scene, _transform: DAffine2) {}
|
||||
|
||||
fn contains_artboard(&self) -> bool {
|
||||
false
|
||||
@@ -283,12 +267,22 @@ impl GraphicElementRendered for GraphicGroup {
|
||||
}
|
||||
}
|
||||
|
||||
fn to_usvg_node(&self) -> usvg::Node {
|
||||
let mut root_node = usvg::Group::default();
|
||||
#[cfg(feature = "vello")]
|
||||
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2) {
|
||||
let kurbo_transform = kurbo::Affine::new((transform * self.transform).to_cols_array());
|
||||
// TODO: We make the bounding box bigger to accommodate for the stroke width. This should be done in a better way.
|
||||
let Some(bounds) = self.bounding_box(DAffine2::from_scale(DVec2::splat(1.05))) else { return };
|
||||
let blending = vello::peniko::BlendMode::new(self.alpha_blending.blend_mode.into(), vello::peniko::Compose::SrcOver);
|
||||
scene.push_layer(
|
||||
blending,
|
||||
self.alpha_blending.opacity,
|
||||
kurbo_transform,
|
||||
&vello::kurbo::Rect::new(bounds[0].x, bounds[0].y, bounds[1].x, bounds[1].y),
|
||||
);
|
||||
for element in self.iter() {
|
||||
root_node.children.push(element.to_usvg_node());
|
||||
element.render_to_vello(scene, transform * self.transform);
|
||||
}
|
||||
usvg::Node::Group(Box::new(root_node))
|
||||
scene.pop_layer();
|
||||
}
|
||||
|
||||
fn contains_artboard(&self) -> bool {
|
||||
@@ -335,8 +329,8 @@ impl GraphicElementRendered for VectorData {
|
||||
}
|
||||
|
||||
fn add_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
|
||||
let stroke_width = self.style.stroke().as_ref().map_or(0., crate::vector::style::Stroke::weight);
|
||||
let filled = self.style.fill() != &crate::vector::style::Fill::None;
|
||||
let stroke_width = self.style.stroke().as_ref().map_or(0., Stroke::weight);
|
||||
let filled = self.style.fill() != &Fill::None;
|
||||
let fill = |mut subpath: bezier_rs::Subpath<_>| {
|
||||
if filled {
|
||||
subpath.set_closed(true);
|
||||
@@ -346,38 +340,81 @@ impl GraphicElementRendered for VectorData {
|
||||
click_targets.extend(self.stroke_bezier_paths().map(fill).map(|subpath| ClickTarget { stroke_width, subpath }));
|
||||
}
|
||||
|
||||
fn to_usvg_node(&self) -> usvg::Node {
|
||||
use bezier_rs::BezierHandles;
|
||||
use usvg::tiny_skia_path::PathBuilder;
|
||||
let mut builder = PathBuilder::new();
|
||||
let vector_data = self;
|
||||
#[cfg(feature = "vello")]
|
||||
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2) {
|
||||
use crate::vector::style::GradientType;
|
||||
use vello::peniko;
|
||||
|
||||
let transform = to_transform(vector_data.transform);
|
||||
for subpath in vector_data.stroke_bezier_paths() {
|
||||
let start = vector_data.transform.transform_point2(subpath[0].anchor);
|
||||
builder.move_to(start.x as f32, start.y as f32);
|
||||
for bezier in subpath.iter() {
|
||||
bezier.apply_transformation(|pos| vector_data.transform.transform_point2(pos));
|
||||
let end = bezier.end;
|
||||
match bezier.handles {
|
||||
BezierHandles::Linear => builder.line_to(end.x as f32, end.y as f32),
|
||||
BezierHandles::Quadratic { handle } => builder.quad_to(handle.x as f32, handle.y as f32, end.x as f32, end.y as f32),
|
||||
BezierHandles::Cubic { handle_start, handle_end } => {
|
||||
builder.cubic_to(handle_start.x as f32, handle_start.y as f32, handle_end.x as f32, handle_end.y as f32, end.x as f32, end.y as f32)
|
||||
}
|
||||
}
|
||||
}
|
||||
if subpath.closed {
|
||||
builder.close()
|
||||
}
|
||||
let kurbo_transform = kurbo::Affine::new(transform.to_cols_array());
|
||||
let to_point = |p: DVec2| kurbo::Point::new(p.x, p.y);
|
||||
let mut path = kurbo::BezPath::new();
|
||||
for (_, subpath) in self.region_bezier_paths() {
|
||||
subpath.to_vello_path(self.transform, &mut path);
|
||||
}
|
||||
|
||||
match self.style.fill() {
|
||||
Fill::Solid(color) => {
|
||||
let fill = peniko::Brush::Solid(peniko::Color::rgba(color.r() as f64, color.g() as f64, color.b() as f64, color.a() as f64));
|
||||
scene.fill(peniko::Fill::NonZero, kurbo_transform, &fill, None, &path);
|
||||
}
|
||||
Fill::Gradient(gradient) => {
|
||||
let mut stops = peniko::ColorStops::new();
|
||||
for &(offset, color) in &gradient.stops.0 {
|
||||
stops.push(peniko::ColorStop {
|
||||
offset: offset as f32,
|
||||
color: peniko::Color::rgba(color.r() as f64, color.g() as f64, color.b() as f64, color.a() as f64),
|
||||
});
|
||||
}
|
||||
// Compute bounding box of the shape to determine the gradient start and end points
|
||||
let bounds = self.bounding_box().unwrap_or_default();
|
||||
let lerp_bounds = |p: DVec2| bounds[0] + (bounds[1] - bounds[0]) * p;
|
||||
let start = lerp_bounds(gradient.start);
|
||||
let end = lerp_bounds(gradient.end);
|
||||
|
||||
let transform = self.transform * gradient.transform;
|
||||
let start = transform.transform_point2(start);
|
||||
let end = transform.transform_point2(end);
|
||||
let fill = peniko::Brush::Gradient(peniko::Gradient {
|
||||
kind: match gradient.gradient_type {
|
||||
GradientType::Linear => peniko::GradientKind::Linear {
|
||||
start: to_point(start),
|
||||
end: to_point(end),
|
||||
},
|
||||
GradientType::Radial => {
|
||||
let radius = start.distance(end);
|
||||
peniko::GradientKind::Radial {
|
||||
start_center: to_point(start),
|
||||
start_radius: 0.,
|
||||
end_center: to_point(end),
|
||||
end_radius: radius as f32,
|
||||
}
|
||||
}
|
||||
},
|
||||
stops,
|
||||
..Default::default()
|
||||
});
|
||||
scene.fill(peniko::Fill::NonZero, kurbo_transform, &fill, None, &path);
|
||||
}
|
||||
Fill::None => (),
|
||||
};
|
||||
|
||||
let mut path = kurbo::BezPath::new();
|
||||
for (_, subpath) in self.region_bezier_paths() {
|
||||
subpath.to_vello_path(self.transform, &mut path);
|
||||
}
|
||||
|
||||
if let Some(stroke) = self.style.stroke() {
|
||||
let color = match stroke.color {
|
||||
Some(color) => peniko::Color::rgba(color.r() as f64, color.g() as f64, color.b() as f64, color.a() as f64),
|
||||
None => peniko::Color::TRANSPARENT,
|
||||
};
|
||||
let stroke = kurbo::Stroke {
|
||||
width: stroke.weight,
|
||||
miter_limit: stroke.line_join_miter_limit,
|
||||
..Default::default()
|
||||
};
|
||||
scene.stroke(&stroke, kurbo_transform, color, None, &path);
|
||||
}
|
||||
let path = builder.finish().unwrap();
|
||||
let mut path = usvg::Path::new(path.into());
|
||||
path.abs_transform = transform;
|
||||
// TODO: use proper style
|
||||
path.fill = None;
|
||||
path.stroke = Some(usvg::Stroke::default());
|
||||
usvg::Node::Path(Box::new(path))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -458,6 +495,28 @@ impl GraphicElementRendered for Artboard {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "vello")]
|
||||
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2) {
|
||||
use vello::peniko;
|
||||
|
||||
// Render background
|
||||
let color = peniko::Color::rgba(self.background.r() as f64, self.background.g() as f64, self.background.b() as f64, self.background.a() as f64);
|
||||
let rect = kurbo::Rect::new(self.location.x as f64, self.location.y as f64, self.dimensions.x as f64, self.dimensions.y as f64);
|
||||
let blend_mode = peniko::BlendMode::new(peniko::Mix::Clip, peniko::Compose::SrcOver);
|
||||
|
||||
scene.push_layer(peniko::Mix::Normal, 1., kurbo::Affine::new(transform.to_cols_array()), &rect);
|
||||
scene.fill(peniko::Fill::NonZero, kurbo::Affine::new(transform.to_cols_array()), color, None, &rect);
|
||||
scene.pop_layer();
|
||||
|
||||
if self.clip {
|
||||
scene.push_layer(blend_mode, 1., kurbo::Affine::new(transform.to_cols_array()), &rect);
|
||||
}
|
||||
self.graphic_group.render_to_vello(scene, transform * self.transform());
|
||||
if self.clip {
|
||||
scene.pop_layer();
|
||||
}
|
||||
}
|
||||
|
||||
fn add_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
|
||||
let mut subpath = Subpath::new_rect(DVec2::ZERO, self.dimensions.as_dvec2());
|
||||
subpath.apply_transform(self.graphic_group.transform.inverse());
|
||||
@@ -486,6 +545,13 @@ impl GraphicElementRendered for crate::ArtboardGroup {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "vello")]
|
||||
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2) {
|
||||
for artboard in &self.artboards {
|
||||
artboard.render_to_vello(scene, transform)
|
||||
}
|
||||
}
|
||||
|
||||
fn contains_artboard(&self) -> bool {
|
||||
!self.artboards.is_empty()
|
||||
}
|
||||
@@ -511,6 +577,11 @@ impl GraphicElementRendered for SurfaceFrame {
|
||||
render.svg.push(canvas.into())
|
||||
}
|
||||
|
||||
#[cfg(feature = "vello")]
|
||||
fn render_to_vello(&self, _scene: &mut Scene, _transform: DAffine2) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
|
||||
let bbox = Bbox::from_transform(transform);
|
||||
let aabb = bbox.to_axis_aligned_bbox();
|
||||
@@ -567,24 +638,24 @@ impl GraphicElementRendered for ImageFrame<Color> {
|
||||
click_targets.push(ClickTarget { subpath, stroke_width: 0. });
|
||||
}
|
||||
|
||||
fn to_usvg_node(&self) -> usvg::Node {
|
||||
let image_frame = self;
|
||||
if image_frame.image.width * image_frame.image.height == 0 {
|
||||
return usvg::Node::Group(Box::default());
|
||||
#[cfg(feature = "vello")]
|
||||
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2) {
|
||||
use vello::peniko;
|
||||
|
||||
let image = &self.image;
|
||||
if image.data.is_empty() {
|
||||
return;
|
||||
}
|
||||
let png = image_frame.image.to_png();
|
||||
usvg::Node::Image(Box::new(usvg::Image {
|
||||
id: String::new(),
|
||||
abs_transform: to_transform(image_frame.transform),
|
||||
visibility: usvg::Visibility::Visible,
|
||||
view_box: usvg::ViewBox {
|
||||
rect: usvg::NonZeroRect::from_xywh(0., 0., 1., 1.).unwrap(),
|
||||
aspect: usvg::AspectRatio::default(),
|
||||
},
|
||||
rendering_mode: usvg::ImageRendering::OptimizeSpeed,
|
||||
kind: usvg::ImageKind::PNG(png.into()),
|
||||
bounding_box: None,
|
||||
}))
|
||||
let image = vello::peniko::Image {
|
||||
data: image.to_flat_u8().0.into(),
|
||||
width: image.width,
|
||||
height: image.height,
|
||||
format: peniko::Format::Rgba8,
|
||||
extend: peniko::Extend::Repeat,
|
||||
};
|
||||
let transform = transform * self.transform * DAffine2::from_scale(1. / DVec2::new(image.width as f64, image.height as f64));
|
||||
|
||||
scene.draw_image(&image, vello::kurbo::Affine::new(transform.to_cols_array()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -616,12 +687,13 @@ impl GraphicElementRendered for GraphicElement {
|
||||
}
|
||||
}
|
||||
|
||||
fn to_usvg_node(&self) -> usvg::Node {
|
||||
#[cfg(feature = "vello")]
|
||||
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2) {
|
||||
match self {
|
||||
GraphicElement::VectorData(vector_data) => vector_data.to_usvg_node(),
|
||||
GraphicElement::ImageFrame(image_frame) => image_frame.to_usvg_node(),
|
||||
GraphicElement::GraphicGroup(graphic_group) => graphic_group.to_usvg_node(),
|
||||
GraphicElement::Surface(surface) => surface.to_usvg_node(),
|
||||
GraphicElement::VectorData(vector_data) => vector_data.render_to_vello(scene, transform),
|
||||
GraphicElement::ImageFrame(image_frame) => image_frame.render_to_vello(scene, transform),
|
||||
GraphicElement::GraphicGroup(graphic_group) => graphic_group.render_to_vello(scene, transform),
|
||||
GraphicElement::Surface(surface) => surface.render_to_vello(scene, transform),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -659,32 +731,6 @@ impl<T: Primitive> GraphicElementRendered for T {
|
||||
}
|
||||
|
||||
fn add_click_targets(&self, _click_targets: &mut Vec<ClickTarget>) {}
|
||||
|
||||
fn to_usvg_node(&self) -> usvg::Node {
|
||||
let text = self;
|
||||
usvg::Node::Text(Box::new(usvg::Text {
|
||||
id: String::new(),
|
||||
abs_transform: usvg::Transform::identity(),
|
||||
rendering_mode: usvg::TextRendering::OptimizeSpeed,
|
||||
writing_mode: usvg::WritingMode::LeftToRight,
|
||||
chunks: vec![usvg::TextChunk {
|
||||
text: text.to_string(),
|
||||
x: None,
|
||||
y: None,
|
||||
anchor: usvg::TextAnchor::Start,
|
||||
spans: vec![],
|
||||
text_flow: usvg::TextFlow::Linear,
|
||||
}],
|
||||
dx: Vec::new(),
|
||||
dy: Vec::new(),
|
||||
rotate: Vec::new(),
|
||||
bounding_box: None,
|
||||
abs_bounding_box: None,
|
||||
stroke_bounding_box: None,
|
||||
abs_stroke_bounding_box: None,
|
||||
flattened: None,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl GraphicElementRendered for Option<Color> {
|
||||
|
||||
@@ -233,6 +233,37 @@ impl core::fmt::Display for BlendMode {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "vello")]
|
||||
impl From<BlendMode> for vello::peniko::Mix {
|
||||
fn from(val: BlendMode) -> Self {
|
||||
match val {
|
||||
// Normal group
|
||||
BlendMode::Normal => vello::peniko::Mix::Normal,
|
||||
// Darken group
|
||||
BlendMode::Darken => vello::peniko::Mix::Darken,
|
||||
BlendMode::Multiply => vello::peniko::Mix::Multiply,
|
||||
BlendMode::ColorBurn => vello::peniko::Mix::ColorBurn,
|
||||
// Lighten group
|
||||
BlendMode::Lighten => vello::peniko::Mix::Lighten,
|
||||
BlendMode::Screen => vello::peniko::Mix::Screen,
|
||||
BlendMode::ColorDodge => vello::peniko::Mix::ColorDodge,
|
||||
// Contrast group
|
||||
BlendMode::Overlay => vello::peniko::Mix::Overlay,
|
||||
BlendMode::SoftLight => vello::peniko::Mix::SoftLight,
|
||||
BlendMode::HardLight => vello::peniko::Mix::HardLight,
|
||||
// Inversion group
|
||||
BlendMode::Difference => vello::peniko::Mix::Difference,
|
||||
BlendMode::Exclusion => vello::peniko::Mix::Exclusion,
|
||||
// Component group
|
||||
BlendMode::Hue => vello::peniko::Mix::Hue,
|
||||
BlendMode::Saturation => vello::peniko::Mix::Saturation,
|
||||
BlendMode::Color => vello::peniko::Mix::Color,
|
||||
BlendMode::Luminosity => vello::peniko::Mix::Luminosity,
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct LuminanceNode<LuminanceCalculation> {
|
||||
luminance_calc: LuminanceCalculation,
|
||||
|
||||
@@ -277,7 +277,7 @@ impl ManipulatorPointId {
|
||||
}
|
||||
|
||||
/// The type of handle found on a bézier curve.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, DynAny)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, DynAny)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum HandleType {
|
||||
/// The first handle on a cubic bézier or the only handle on a quadratic bézier.
|
||||
@@ -287,7 +287,7 @@ pub enum HandleType {
|
||||
}
|
||||
|
||||
/// Represents a primary or end handle found in a particular segment.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, DynAny)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, DynAny)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct HandleId {
|
||||
pub ty: HandleType,
|
||||
|
||||
@@ -9,7 +9,7 @@ use std::collections::HashMap;
|
||||
macro_rules! create_ids {
|
||||
($($id:ident),*) => {
|
||||
$(
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, DynAny)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Ord, Eq, Hash, DynAny)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
/// A strongly typed ID
|
||||
pub struct $id(u64);
|
||||
|
||||
@@ -16,6 +16,20 @@ pub struct PointModification {
|
||||
delta: HashMap<PointId, DVec2>,
|
||||
}
|
||||
|
||||
impl Hash for PointModification {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.add.hash(state);
|
||||
|
||||
let mut remove = self.remove.iter().collect::<Vec<_>>();
|
||||
remove.sort_unstable();
|
||||
remove.hash(state);
|
||||
|
||||
let mut delta = self.delta.iter().map(|(&a, &b)| (a, [b.x.to_bits(), b.y.to_bits()])).collect::<Vec<_>>();
|
||||
delta.sort_unstable();
|
||||
delta.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl PointModification {
|
||||
/// Apply this modification to the specified [`PointDomain`].
|
||||
pub fn apply(&self, point_domain: &mut PointDomain, segment_domain: &mut SegmentDomain) {
|
||||
@@ -90,6 +104,36 @@ pub struct SegmentModification {
|
||||
stroke: HashMap<SegmentId, StrokeId>,
|
||||
}
|
||||
|
||||
impl Hash for SegmentModification {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.add.hash(state);
|
||||
|
||||
let mut remove = self.remove.iter().collect::<Vec<_>>();
|
||||
remove.sort_unstable();
|
||||
remove.hash(state);
|
||||
|
||||
let mut start_point = self.start_point.iter().map(|(&a, &b)| (a, b)).collect::<Vec<_>>();
|
||||
start_point.sort_unstable();
|
||||
start_point.hash(state);
|
||||
|
||||
let mut end_point = self.end_point.iter().map(|(&a, &b)| (a, b)).collect::<Vec<_>>();
|
||||
end_point.sort_unstable();
|
||||
end_point.hash(state);
|
||||
|
||||
let mut handle_primary = self.handle_primary.iter().map(|(&a, &b)| (a, b.map(|b| [b.x.to_bits(), b.y.to_bits()]))).collect::<Vec<_>>();
|
||||
handle_primary.sort_unstable();
|
||||
handle_primary.hash(state);
|
||||
|
||||
let mut handle_end = self.handle_end.iter().map(|(&a, &b)| (a, b.map(|b| [b.x.to_bits(), b.y.to_bits()]))).collect::<Vec<_>>();
|
||||
handle_end.sort_unstable();
|
||||
handle_end.hash(state);
|
||||
|
||||
let mut stroke = self.stroke.iter().map(|(&a, &b)| (a, b)).collect::<Vec<_>>();
|
||||
stroke.sort_unstable();
|
||||
stroke.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl SegmentModification {
|
||||
/// Apply this modification to the specified [`SegmentDomain`].
|
||||
pub fn apply(&self, segment_domain: &mut SegmentDomain, point_domain: &PointDomain) {
|
||||
@@ -245,6 +289,24 @@ pub struct RegionModification {
|
||||
fill: HashMap<RegionId, FillId>,
|
||||
}
|
||||
|
||||
impl Hash for RegionModification {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.add.hash(state);
|
||||
|
||||
let mut remove = self.remove.iter().collect::<Vec<_>>();
|
||||
remove.sort_unstable();
|
||||
remove.hash(state);
|
||||
|
||||
let mut segment_range = self.segment_range.iter().map(|(&a, b)| (a, (*b.start(), *b.end()))).collect::<Vec<_>>();
|
||||
segment_range.sort_unstable();
|
||||
segment_range.hash(state);
|
||||
|
||||
let mut fill = self.fill.iter().map(|(&a, &b)| (a, b)).collect::<Vec<_>>();
|
||||
fill.sort_unstable();
|
||||
fill.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl RegionModification {
|
||||
/// Apply this modification to the specified [`RegionDomain`].
|
||||
pub fn apply(&self, region_domain: &mut RegionDomain) {
|
||||
@@ -398,8 +460,19 @@ impl VectorModification {
|
||||
|
||||
impl core::hash::Hash for VectorModification {
|
||||
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||
// TODO: properly implement (hashing a hashset is difficult because ordering is unstable)
|
||||
PointId::generate().hash(state);
|
||||
self.points.hash(state);
|
||||
|
||||
self.segments.hash(state);
|
||||
|
||||
self.regions.hash(state);
|
||||
|
||||
let mut add_g1_continuous = self.add_g1_continuous.iter().copied().collect::<Vec<_>>();
|
||||
add_g1_continuous.sort_unstable();
|
||||
add_g1_continuous.hash(state);
|
||||
|
||||
let mut remove_g1_continuous = self.remove_g1_continuous.iter().copied().collect::<Vec<_>>();
|
||||
remove_g1_continuous.sort_unstable();
|
||||
remove_g1_continuous.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user