Instance tables refactor part 1: wrap graphical data in the new Instances<T> struct (#2230)

* Port VectorData to Instances<VectorData>

* Port ImageFrame<P> and TextureFrame to Instances<ImageFrame<P>> and Instances<TextureFrame>

* Avoid mutation with the TransformMut trait

* Port GraphicGroup to Instances<GraphicGroup>

* It compiles!

* Organize debugging

* Document upgrading

* Fix Brush node

* Restore TransformMut in lieu of TransformSet trait

* Fix tests

* Final code review
This commit is contained in:
Keavon Chambers
2025-01-28 23:51:12 -08:00
committed by GitHub
parent 408f9bffa1
commit eb0ff20d3c
43 changed files with 1855 additions and 1221 deletions

View File

@@ -1,3 +1,4 @@
use crate::instances::Instances;
use crate::text::FontCache;
use crate::transform::{Footprint, Transform, TransformMut};
use crate::vector::style::ViewMode;
@@ -64,6 +65,8 @@ impl Size for web_sys::HtmlCanvasElement {
}
}
pub type TextureFrameTable = Instances<TextureFrame>;
#[derive(Debug, Clone)]
pub struct TextureFrame {
#[cfg(feature = "wgpu")]

View File

@@ -1,14 +1,17 @@
use crate::application_io::TextureFrame;
use crate::raster::{BlendMode, ImageFrame};
use crate::application_io::{TextureFrame, TextureFrameTable};
use crate::instances::Instances;
use crate::raster::image::{ImageFrame, ImageFrameTable};
use crate::raster::BlendMode;
use crate::transform::{ApplyTransform, Footprint, Transform, TransformMut};
use crate::uuid::NodeId;
use crate::vector::VectorData;
use crate::vector::{VectorData, VectorDataTable};
use crate::Color;
use dyn_any::DynAny;
use core::ops::{Deref, DerefMut};
use glam::{DAffine2, IVec2};
use std::hash::Hash;
pub mod renderer;
@@ -38,6 +41,25 @@ impl AlphaBlending {
}
}
// TODO: Eventually remove this migration document upgrade code
pub fn migrate_graphic_group<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<GraphicGroupTable, D::Error> {
use serde::Deserialize;
#[derive(serde::Serialize, serde::Deserialize)]
#[serde(untagged)]
enum EitherFormat {
GraphicGroup(GraphicGroup),
GraphicGroupTable(GraphicGroupTable),
}
Ok(match EitherFormat::deserialize(deserializer)? {
EitherFormat::GraphicGroup(graphic_group) => GraphicGroupTable::new(graphic_group),
EitherFormat::GraphicGroupTable(graphic_group_table) => graphic_group_table,
})
}
pub type GraphicGroupTable = Instances<GraphicGroup>;
/// A list of [`GraphicElement`]s
#[derive(Clone, Debug, PartialEq, DynAny, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
@@ -56,12 +78,6 @@ impl core::hash::Hash for GraphicGroup {
}
impl GraphicGroup {
pub const EMPTY: Self = Self {
elements: Vec::new(),
transform: DAffine2::IDENTITY,
alpha_blending: AlphaBlending::new(),
};
pub fn new(elements: Vec<GraphicElement>) -> Self {
Self {
elements: elements.into_iter().map(|element| (element, None)).collect(),
@@ -71,127 +87,161 @@ impl GraphicGroup {
}
}
impl From<GraphicGroup> for GraphicGroupTable {
fn from(graphic_group: GraphicGroup) -> Self {
Self::new(graphic_group)
}
}
impl From<VectorData> for GraphicGroupTable {
fn from(vector_data: VectorData) -> Self {
Self::new(GraphicGroup::new(vec![GraphicElement::VectorData(VectorDataTable::new(vector_data))]))
}
}
impl From<VectorDataTable> for GraphicGroupTable {
fn from(vector_data: VectorDataTable) -> Self {
Self::new(GraphicGroup::new(vec![GraphicElement::VectorData(vector_data)]))
}
}
impl From<ImageFrame<Color>> for GraphicGroupTable {
fn from(image_frame: ImageFrame<Color>) -> Self {
Self::new(GraphicGroup::new(vec![GraphicElement::RasterFrame(RasterFrame::ImageFrame(ImageFrameTable::new(image_frame)))]))
}
}
impl From<ImageFrameTable<Color>> for GraphicGroupTable {
fn from(image_frame: ImageFrameTable<Color>) -> Self {
Self::new(GraphicGroup::new(vec![GraphicElement::RasterFrame(RasterFrame::ImageFrame(image_frame))]))
}
}
impl From<TextureFrame> for GraphicGroupTable {
fn from(texture_frame: TextureFrame) -> Self {
Self::new(GraphicGroup::new(vec![GraphicElement::RasterFrame(RasterFrame::TextureFrame(TextureFrameTable::new(texture_frame)))]))
}
}
impl From<TextureFrameTable> for GraphicGroupTable {
fn from(texture_frame: TextureFrameTable) -> Self {
Self::new(GraphicGroup::new(vec![GraphicElement::RasterFrame(RasterFrame::TextureFrame(texture_frame))]))
}
}
/// The possible forms of graphical content held in a Vec by the `elements` field of [`GraphicElement`].
/// Can be another recursively nested [`GraphicGroup`], a [`VectorData`] shape, an [`ImageFrame`], or an [`Artboard`].
#[derive(Clone, Debug, Hash, PartialEq, DynAny)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum GraphicElement {
/// Equivalent to the SVG <g> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/g
GraphicGroup(GraphicGroup),
GraphicGroup(GraphicGroupTable),
/// A vector shape, equivalent to the SVG <path> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path
VectorData(Box<VectorData>),
Raster(Raster),
VectorData(VectorDataTable),
RasterFrame(RasterFrame),
}
// TODO: Can this be removed? It doesn't necessarily make that much sense to have a default when, instead, the entire GraphicElement just shouldn't exist if there's no specific content to assign it.
impl Default for GraphicElement {
fn default() -> Self {
Self::VectorData(Box::new(VectorData::empty()))
Self::VectorData(VectorDataTable::default())
}
}
impl GraphicElement {
pub fn as_group(&self) -> Option<&GraphicGroup> {
pub fn as_group(&self) -> Option<&GraphicGroupTable> {
match self {
GraphicElement::GraphicGroup(group) => Some(group),
_ => None,
}
}
pub fn as_group_mut(&mut self) -> Option<&mut GraphicGroup> {
pub fn as_group_mut(&mut self) -> Option<&mut GraphicGroupTable> {
match self {
GraphicElement::GraphicGroup(group) => Some(group),
_ => None,
}
}
pub fn as_vector_data(&self) -> Option<&VectorData> {
pub fn as_vector_data(&self) -> Option<&VectorDataTable> {
match self {
GraphicElement::VectorData(data) => Some(data),
_ => None,
}
}
pub fn as_vector_data_mut(&mut self) -> Option<&mut VectorData> {
pub fn as_vector_data_mut(&mut self) -> Option<&mut VectorDataTable> {
match self {
GraphicElement::VectorData(data) => Some(data),
_ => None,
}
}
pub fn as_raster(&self) -> Option<&Raster> {
pub fn as_raster(&self) -> Option<&RasterFrame> {
match self {
GraphicElement::Raster(raster) => Some(raster),
GraphicElement::RasterFrame(raster) => Some(raster),
_ => None,
}
}
pub fn as_raster_mut(&mut self) -> Option<&mut Raster> {
pub fn as_raster_mut(&mut self) -> Option<&mut RasterFrame> {
match self {
GraphicElement::Raster(raster) => Some(raster),
GraphicElement::RasterFrame(raster) => Some(raster),
_ => None,
}
}
}
#[derive(Clone, Debug, Hash, PartialEq, DynAny)]
pub enum Raster {
/// A bitmap image with a finite position and extent, equivalent to the SVG <image> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/image
ImageFrame(ImageFrame<Color>),
Texture(TextureFrame),
pub enum RasterFrame {
/// A CPU-based bitmap image with a finite position and extent, equivalent to the SVG <image> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/image
ImageFrame(ImageFrameTable<Color>),
/// A GPU texture with a finite position and extent
TextureFrame(TextureFrameTable),
}
impl<'de> serde::Deserialize<'de> for Raster {
impl<'de> serde::Deserialize<'de> for RasterFrame {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let frame = ImageFrame::deserialize(deserializer)?;
Ok(Raster::ImageFrame(frame))
Ok(RasterFrame::ImageFrame(ImageFrameTable::new(ImageFrame::deserialize(deserializer)?)))
}
}
impl serde::Serialize for Raster {
impl serde::Serialize for RasterFrame {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self {
Raster::ImageFrame(_) => self.serialize(serializer),
Raster::Texture(_) => todo!(),
RasterFrame::ImageFrame(_) => self.serialize(serializer),
RasterFrame::TextureFrame(_) => todo!(),
}
}
}
impl Transform for Raster {
impl Transform for RasterFrame {
fn transform(&self) -> DAffine2 {
match self {
Raster::ImageFrame(frame) => frame.transform(),
Raster::Texture(frame) => frame.transform(),
RasterFrame::ImageFrame(frame) => frame.transform(),
RasterFrame::TextureFrame(frame) => frame.transform(),
}
}
fn local_pivot(&self, pivot: glam::DVec2) -> glam::DVec2 {
match self {
Raster::ImageFrame(frame) => frame.local_pivot(pivot),
Raster::Texture(frame) => frame.local_pivot(pivot),
RasterFrame::ImageFrame(frame) => frame.local_pivot(pivot),
RasterFrame::TextureFrame(frame) => frame.local_pivot(pivot),
}
}
}
impl TransformMut for Raster {
impl TransformMut for RasterFrame {
fn transform_mut(&mut self) -> &mut DAffine2 {
match self {
Raster::ImageFrame(frame) => frame.transform_mut(),
Raster::Texture(frame) => frame.transform_mut(),
RasterFrame::ImageFrame(frame) => frame.transform_mut(),
RasterFrame::TextureFrame(frame) => frame.transform_mut(),
}
}
}
/// Some [`ArtboardData`] with some optional clipping bounds that can be exported.
/// Similar to an Inkscape page: https://media.inkscape.org/media/doc/release_notes/1.2/Inkscape_1.2.html#Page_tool
#[derive(Clone, Debug, Hash, PartialEq, DynAny)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Artboard {
pub graphic_group: GraphicGroup,
pub graphic_group: GraphicGroupTable,
pub label: String,
pub location: IVec2,
pub dimensions: IVec2,
@@ -202,7 +252,7 @@ pub struct Artboard {
impl Artboard {
pub fn new(location: IVec2, dimensions: IVec2) -> Self {
Self {
graphic_group: GraphicGroup::EMPTY,
graphic_group: GraphicGroupTable::default(),
label: String::from("Artboard"),
location: location.min(location + dimensions),
dimensions: dimensions.abs(),
@@ -220,8 +270,6 @@ pub struct ArtboardGroup {
}
impl ArtboardGroup {
pub const EMPTY: Self = Self { artboards: Vec::new() };
pub fn new() -> Self {
Default::default()
}
@@ -239,19 +287,22 @@ async fn layer<F: 'n + Send + Copy>(
)]
footprint: F,
#[implementations(
() -> GraphicGroup,
Footprint -> GraphicGroup,
() -> GraphicGroupTable,
Footprint -> GraphicGroupTable,
)]
stack: impl Node<F, Output = GraphicGroup>,
stack: impl Node<F, Output = GraphicGroupTable>,
#[implementations(
() -> GraphicElement,
Footprint -> GraphicElement,
)]
element: impl Node<F, Output = GraphicElement>,
node_path: Vec<NodeId>,
) -> GraphicGroup {
) -> GraphicGroupTable {
let mut element = element.eval(footprint).await;
let mut stack = stack.eval(footprint).await;
let stack = stack.eval(footprint).await;
let stack = stack.one_item();
let mut stack = stack.clone();
if stack.transform.matrix2.determinant() != 0. {
*element.transform_mut() = stack.transform.inverse() * element.transform();
} else {
@@ -262,7 +313,8 @@ async fn layer<F: 'n + Send + Copy>(
// Get the penultimate element of the node path, or None if the path is too short
let encapsulating_node_id = node_path.get(node_path.len().wrapping_sub(2)).copied();
stack.push((element, encapsulating_node_id));
stack
GraphicGroupTable::new(stack)
}
#[node_macro::node(category("Debug"))]
@@ -276,14 +328,14 @@ async fn to_element<F: 'n + Send, Data: Into<GraphicElement> + 'n>(
)]
footprint: F,
#[implementations(
() -> GraphicGroup,
() -> VectorData,
() -> ImageFrame<Color>,
() -> TextureFrame,
Footprint -> GraphicGroup,
Footprint -> VectorData,
Footprint -> ImageFrame<Color>,
Footprint -> TextureFrame,
() -> GraphicGroupTable,
() -> VectorDataTable,
() -> ImageFrameTable<Color>,
() -> TextureFrameTable,
Footprint -> GraphicGroupTable,
Footprint -> VectorDataTable,
Footprint -> ImageFrameTable<Color>,
Footprint -> TextureFrameTable,
)]
data: impl Node<F, Output = Data>,
) -> GraphicElement {
@@ -291,7 +343,7 @@ async fn to_element<F: 'n + Send, Data: Into<GraphicElement> + 'n>(
}
#[node_macro::node(category("General"))]
async fn to_group<F: 'n + Send, Data: Into<GraphicGroup> + 'n>(
async fn to_group<F: 'n + Send, Data: Into<GraphicGroupTable> + 'n>(
#[implementations(
(),
(),
@@ -301,17 +353,17 @@ async fn to_group<F: 'n + Send, Data: Into<GraphicGroup> + 'n>(
)]
footprint: F,
#[implementations(
() -> GraphicGroup,
() -> VectorData,
() -> ImageFrame<Color>,
() -> TextureFrame,
Footprint -> GraphicGroup,
Footprint -> VectorData,
Footprint -> ImageFrame<Color>,
Footprint -> TextureFrame,
() -> GraphicGroupTable,
() -> VectorDataTable,
() -> ImageFrameTable<Color>,
() -> TextureFrameTable,
Footprint -> GraphicGroupTable,
Footprint -> VectorDataTable,
Footprint -> ImageFrameTable<Color>,
Footprint -> TextureFrameTable,
)]
element: impl Node<F, Output = Data>,
) -> GraphicGroup {
) -> GraphicGroupTable {
element.eval(footprint).await.into()
}
@@ -323,20 +375,28 @@ async fn flatten_group<F: 'n + Send>(
)]
footprint: F,
#[implementations(
() -> GraphicGroup,
Footprint -> GraphicGroup,
() -> GraphicGroupTable,
Footprint -> GraphicGroupTable,
)]
group: impl Node<F, Output = GraphicGroup>,
group: impl Node<F, Output = GraphicGroupTable>,
fully_flatten: bool,
) -> GraphicGroup {
) -> GraphicGroupTable {
let nested_group = group.eval(footprint).await;
let mut flat_group = GraphicGroup::EMPTY;
let nested_group = nested_group.one_item();
let nested_group = nested_group.clone();
let mut flat_group = GraphicGroup::default();
fn flatten_group(result_group: &mut GraphicGroup, current_group: GraphicGroup, fully_flatten: bool) {
let mut collection_group = GraphicGroup::EMPTY;
let mut collection_group = GraphicGroup::default();
for (element, reference) in current_group.elements {
if let GraphicElement::GraphicGroup(mut nested_group) = element {
nested_group.transform *= current_group.transform;
let mut sub_group = GraphicGroup::EMPTY;
if let GraphicElement::GraphicGroup(nested_group) = element {
let nested_group = nested_group.one_item();
let mut nested_group = nested_group.clone();
*nested_group.transform_mut() = nested_group.transform() * current_group.transform;
let mut sub_group = GraphicGroup::default();
if fully_flatten {
flatten_group(&mut sub_group, nested_group, fully_flatten);
} else {
@@ -353,12 +413,14 @@ async fn flatten_group<F: 'n + Send>(
result_group.append(&mut collection_group.elements);
}
flatten_group(&mut flat_group, nested_group, fully_flatten);
flat_group
GraphicGroupTable::new(flat_group)
}
#[node_macro::node(category(""))]
async fn to_artboard<F: 'n + Send + ApplyTransform, Data: Into<GraphicGroup> + 'n>(
async fn to_artboard<F: 'n + Send + ApplyTransform, Data: Into<GraphicGroupTable> + 'n>(
#[implementations(
(),
(),
@@ -368,13 +430,13 @@ async fn to_artboard<F: 'n + Send + ApplyTransform, Data: Into<GraphicGroup> + '
)]
mut footprint: F,
#[implementations(
() -> GraphicGroup,
() -> VectorData,
() -> ImageFrame<Color>,
() -> GraphicGroupTable,
() -> VectorDataTable,
() -> ImageFrameTable<Color>,
() -> TextureFrame,
Footprint -> GraphicGroup,
Footprint -> VectorData,
Footprint -> ImageFrame<Color>,
Footprint -> GraphicGroupTable,
Footprint -> VectorDataTable,
Footprint -> ImageFrameTable<Color>,
Footprint -> TextureFrame,
)]
contents: impl Node<F, Output = Data>,
@@ -426,23 +488,47 @@ async fn append_artboard<F: 'n + Send + Copy>(
artboards
}
// TODO: Remove this one
impl From<ImageFrame<Color>> for GraphicElement {
fn from(image_frame: ImageFrame<Color>) -> Self {
GraphicElement::Raster(Raster::ImageFrame(image_frame))
GraphicElement::RasterFrame(RasterFrame::ImageFrame(ImageFrameTable::new(image_frame)))
}
}
impl From<ImageFrameTable<Color>> for GraphicElement {
fn from(image_frame: ImageFrameTable<Color>) -> Self {
GraphicElement::RasterFrame(RasterFrame::ImageFrame(image_frame))
}
}
// TODO: Remove this one
impl From<TextureFrame> for GraphicElement {
fn from(texture: TextureFrame) -> Self {
GraphicElement::Raster(Raster::Texture(texture))
GraphicElement::RasterFrame(RasterFrame::TextureFrame(TextureFrameTable::new(texture)))
}
}
impl From<TextureFrameTable> for GraphicElement {
fn from(texture: TextureFrameTable) -> Self {
GraphicElement::RasterFrame(RasterFrame::TextureFrame(texture))
}
}
// TODO: Remove this one
impl From<VectorData> for GraphicElement {
fn from(vector_data: VectorData) -> Self {
GraphicElement::VectorData(Box::new(vector_data))
GraphicElement::VectorData(VectorDataTable::new(vector_data))
}
}
impl From<VectorDataTable> for GraphicElement {
fn from(vector_data: VectorDataTable) -> Self {
GraphicElement::VectorData(vector_data)
}
}
// TODO: Remove this one
impl From<GraphicGroup> for GraphicElement {
fn from(graphic_group: GraphicGroup) -> Self {
GraphicElement::GraphicGroup(GraphicGroupTable::new(graphic_group))
}
}
impl From<GraphicGroupTable> for GraphicElement {
fn from(graphic_group: GraphicGroupTable) -> Self {
GraphicElement::GraphicGroup(graphic_group)
}
}
@@ -464,8 +550,8 @@ impl DerefMut for GraphicGroup {
/// as that would conflict with the implementation for `Self`
trait ToGraphicElement: Into<GraphicElement> {}
impl ToGraphicElement for VectorData {}
impl ToGraphicElement for ImageFrame<Color> {}
impl ToGraphicElement for VectorDataTable {}
impl ToGraphicElement for ImageFrameTable<Color> {}
impl ToGraphicElement for TextureFrame {}
impl<T> From<T> for GraphicGroup

View File

@@ -3,13 +3,13 @@ mod rect;
pub use quad::Quad;
pub use rect::Rect;
use crate::raster::{BlendMode, Image, ImageFrame};
use crate::raster::image::ImageFrameTable;
use crate::raster::{BlendMode, Image};
use crate::transform::{Footprint, Transform};
use crate::uuid::{generate_uuid, NodeId};
use crate::vector::style::{Fill, Stroke, ViewMode};
use crate::vector::PointId;
use crate::Raster;
use crate::{vector::VectorData, Artboard, Color, GraphicElement, GraphicGroup};
use crate::vector::{PointId, VectorDataTable};
use crate::{Artboard, ArtboardGroup, Color, GraphicElement, GraphicGroup, GraphicGroupTable, RasterFrame};
use bezier_rs::Subpath;
use dyn_any::DynAny;
@@ -217,7 +217,7 @@ pub enum ImageRenderMode {
#[derive(Clone, Debug, Default)]
pub struct RenderContext {
#[cfg(feature = "wgpu")]
pub ressource_overrides: std::collections::HashMap<u64, alloc::sync::Arc<wgpu::Texture>>,
pub resource_overrides: std::collections::HashMap<u64, alloc::sync::Arc<wgpu::Texture>>,
}
/// Static state used whilst rendering
@@ -406,60 +406,117 @@ impl GraphicElementRendered for GraphicGroup {
}
}
fn to_graphic_element(&self) -> GraphicElement {
GraphicElement::GraphicGroup(GraphicGroupTable::new(self.clone()))
}
}
impl GraphicElementRendered for GraphicGroupTable {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
for instance in self.instances() {
instance.render_svg(render, render_params);
}
}
fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
self.instances().flat_map(|instance| instance.bounding_box(transform)).reduce(Quad::combine_bounds)
}
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) {
let instance = self.one_item();
instance.collect_metadata(metadata, footprint, element_id);
}
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
for instance in self.instances() {
instance.add_upstream_click_targets(click_targets);
}
}
#[cfg(feature = "vello")]
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext) {
for instance in self.instances() {
instance.render_to_vello(scene, transform, context);
}
}
fn contains_artboard(&self) -> bool {
self.instances().any(|instance| instance.contains_artboard())
}
fn new_ids_from_hash(&mut self, _reference: Option<NodeId>) {
for instance in self.instances_mut() {
instance.new_ids_from_hash(None);
}
}
fn to_graphic_element(&self) -> GraphicElement {
GraphicElement::GraphicGroup(self.clone())
}
}
impl GraphicElementRendered for VectorData {
impl GraphicElementRendered for VectorDataTable {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
let multiplied_transform = render.transform * self.transform;
let set_stroke_transform = self.style.stroke().map(|stroke| stroke.transform).filter(|transform| transform.matrix2.determinant() != 0.);
let applied_stroke_transform = set_stroke_transform.unwrap_or(self.transform);
let element_transform = set_stroke_transform.map(|stroke_transform| multiplied_transform * stroke_transform.inverse());
let element_transform = element_transform.unwrap_or(DAffine2::IDENTITY);
let layer_bounds = self.bounding_box().unwrap_or_default();
let transformed_bounds = self.bounding_box_with_transform(applied_stroke_transform).unwrap_or_default();
for instance in self.instances() {
let multiplied_transform = render.transform * instance.transform;
let set_stroke_transform = instance.style.stroke().map(|stroke| stroke.transform).filter(|transform| transform.matrix2.determinant() != 0.);
let applied_stroke_transform = set_stroke_transform.unwrap_or(instance.transform);
let element_transform = set_stroke_transform.map(|stroke_transform| multiplied_transform * stroke_transform.inverse());
let element_transform = element_transform.unwrap_or(DAffine2::IDENTITY);
let layer_bounds = instance.bounding_box().unwrap_or_default();
let transformed_bounds = instance.bounding_box_with_transform(applied_stroke_transform).unwrap_or_default();
let mut path = String::new();
for subpath in self.stroke_bezier_paths() {
let _ = subpath.subpath_to_svg(&mut path, applied_stroke_transform);
let mut path = String::new();
for subpath in instance.stroke_bezier_paths() {
let _ = subpath.subpath_to_svg(&mut path, applied_stroke_transform);
}
render.leaf_tag("path", |attributes| {
attributes.push("d", path);
let matrix = format_transform_matrix(element_transform);
attributes.push("transform", matrix);
let defs = &mut attributes.0.svg_defs;
let fill_and_stroke = instance
.style
.render(render_params.view_mode, defs, element_transform, applied_stroke_transform, layer_bounds, transformed_bounds);
attributes.push_val(fill_and_stroke);
if instance.alpha_blending.opacity < 1. {
attributes.push("opacity", instance.alpha_blending.opacity.to_string());
}
if instance.alpha_blending.blend_mode != BlendMode::default() {
attributes.push("style", instance.alpha_blending.blend_mode.render());
}
});
}
render.leaf_tag("path", |attributes| {
attributes.push("d", path);
let matrix = format_transform_matrix(element_transform);
attributes.push("transform", matrix);
let defs = &mut attributes.0.svg_defs;
let fill_and_stroke = self
.style
.render(render_params.view_mode, defs, element_transform, applied_stroke_transform, layer_bounds, transformed_bounds);
attributes.push_val(fill_and_stroke);
if self.alpha_blending.opacity < 1. {
attributes.push("opacity", self.alpha_blending.opacity.to_string());
}
if self.alpha_blending.blend_mode != BlendMode::default() {
attributes.push("style", self.alpha_blending.blend_mode.render());
}
});
}
fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
let stroke_width = self.style.stroke().map(|s| s.weight()).unwrap_or_default();
let miter_limit = self.style.stroke().map(|s| s.line_join_miter_limit).unwrap_or(1.);
let scale = transform.decompose_scale();
// We use the full line width here to account for different styles of line caps
let offset = DVec2::splat(stroke_width * scale.x.max(scale.y) * miter_limit);
self.bounding_box_with_transform(transform * self.transform).map(|[a, b]| [a - offset, b + offset])
self.instances()
.flat_map(|instance| {
let stroke_width = instance.style.stroke().map(|s| s.weight()).unwrap_or_default();
let miter_limit = instance.style.stroke().map(|s| s.line_join_miter_limit).unwrap_or(1.);
let scale = transform.decompose_scale();
// We use the full line width here to account for different styles of line caps
let offset = DVec2::splat(stroke_width * scale.x.max(scale.y) * miter_limit);
instance.bounding_box_with_transform(transform * instance.transform).map(|[a, b]| [a - offset, b + offset])
})
.reduce(Quad::combine_bounds)
}
fn collect_metadata(&self, metadata: &mut RenderMetadata, mut footprint: Footprint, element_id: Option<NodeId>) {
let instance = self.one_item();
if let Some(element_id) = element_id {
let stroke_width = self.style.stroke().as_ref().map_or(0., Stroke::weight);
let filled = self.style.fill() != &Fill::None;
let stroke_width = instance.style.stroke().as_ref().map_or(0., Stroke::weight);
let filled = instance.style.fill() != &Fill::None;
let fill = |mut subpath: bezier_rs::Subpath<_>| {
if filled {
subpath.set_closed(true);
@@ -467,7 +524,7 @@ impl GraphicElementRendered for VectorData {
subpath
};
let click_targets = self
let click_targets = instance
.stroke_bezier_paths()
.map(fill)
.map(|subpath| ClickTarget::new(subpath, stroke_width))
@@ -476,145 +533,155 @@ impl GraphicElementRendered for VectorData {
metadata.click_targets.insert(element_id, click_targets);
}
if let Some(upstream_graphic_group) = &self.upstream_graphic_group {
footprint.transform *= self.transform;
if let Some(upstream_graphic_group) = &instance.upstream_graphic_group {
footprint.transform *= instance.transform;
upstream_graphic_group.collect_metadata(metadata, footprint, None);
}
}
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
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);
}
subpath
};
click_targets.extend(self.stroke_bezier_paths().map(fill).map(|subpath| ClickTarget::new(subpath, stroke_width)));
for instance in self.instances() {
let stroke_width = instance.style.stroke().as_ref().map_or(0., Stroke::weight);
let filled = instance.style.fill() != &Fill::None;
let fill = |mut subpath: bezier_rs::Subpath<_>| {
if filled {
subpath.set_closed(true);
}
subpath
};
click_targets.extend(instance.stroke_bezier_paths().map(fill).map(|subpath| ClickTarget::new(subpath, stroke_width)));
}
}
#[cfg(feature = "vello")]
fn render_to_vello(&self, scene: &mut Scene, parent_transform: DAffine2, _: &mut RenderContext) {
use crate::vector::style::GradientType;
use vello::peniko;
let mut layer = false;
let multiplied_transform = parent_transform * self.transform;
let set_stroke_transform = self.style.stroke().map(|stroke| stroke.transform).filter(|transform| transform.matrix2.determinant() != 0.);
let applied_stroke_transform = set_stroke_transform.unwrap_or(multiplied_transform);
let element_transform = set_stroke_transform.map(|stroke_transform| multiplied_transform * stroke_transform.inverse());
let element_transform = element_transform.unwrap_or(DAffine2::IDENTITY);
let layer_bounds = self.bounding_box().unwrap_or_default();
for instance in self.instances() {
let mut layer = false;
if self.alpha_blending.opacity < 1. || self.alpha_blending.blend_mode != BlendMode::default() {
layer = true;
scene.push_layer(
peniko::BlendMode::new(self.alpha_blending.blend_mode.into(), peniko::Compose::SrcOver),
self.alpha_blending.opacity,
kurbo::Affine::new(multiplied_transform.to_cols_array()),
&kurbo::Rect::new(layer_bounds[0].x, layer_bounds[0].y, layer_bounds[1].x, layer_bounds[1].y),
);
}
let multiplied_transform = parent_transform * instance.transform;
let set_stroke_transform = instance.style.stroke().map(|stroke| stroke.transform).filter(|transform| transform.matrix2.determinant() != 0.);
let applied_stroke_transform = set_stroke_transform.unwrap_or(multiplied_transform);
let element_transform = set_stroke_transform.map(|stroke_transform| multiplied_transform * stroke_transform.inverse());
let element_transform = element_transform.unwrap_or(DAffine2::IDENTITY);
let layer_bounds = instance.bounding_box().unwrap_or_default();
let to_point = |p: DVec2| kurbo::Point::new(p.x, p.y);
let mut path = kurbo::BezPath::new();
for subpath in self.stroke_bezier_paths() {
subpath.to_vello_path(applied_stroke_transform, &mut path);
}
match self.style.fill() {
Fill::Solid(color) => {
let fill = peniko::Brush::Solid(peniko::Color::new([color.r(), color.g(), color.b(), color.a()]));
scene.fill(peniko::Fill::NonZero, kurbo::Affine::new(element_transform.to_cols_array()), &fill, None, &path);
if instance.alpha_blending.opacity < 1. || instance.alpha_blending.blend_mode != BlendMode::default() {
layer = true;
scene.push_layer(
peniko::BlendMode::new(instance.alpha_blending.blend_mode.into(), peniko::Compose::SrcOver),
instance.alpha_blending.opacity,
kurbo::Affine::new(multiplied_transform.to_cols_array()),
&kurbo::Rect::new(layer_bounds[0].x, layer_bounds[0].y, layer_bounds[1].x, layer_bounds[1].y),
);
}
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::DynamicColor::from_alpha_color(peniko::Color::new([color.r(), color.g(), color.b(), color.a()])),
});
let to_point = |p: DVec2| kurbo::Point::new(p.x, p.y);
let mut path = kurbo::BezPath::new();
for subpath in instance.stroke_bezier_paths() {
subpath.to_vello_path(applied_stroke_transform, &mut path);
}
match instance.style.fill() {
Fill::Solid(color) => {
let fill = peniko::Brush::Solid(peniko::Color::new([color.r(), color.g(), color.b(), color.a()]));
scene.fill(peniko::Fill::NonZero, kurbo::Affine::new(element_transform.to_cols_array()), &fill, None, &path);
}
// Compute bounding box of the shape to determine the gradient start and end points
let bounds = self.nonzero_bounding_box();
let bound_transform = DAffine2::from_scale_angle_translation(bounds[1] - bounds[0], 0., bounds[0]);
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::DynamicColor::from_alpha_color(peniko::Color::new([color.r(), color.g(), color.b(), color.a()])),
});
}
// Compute bounding box of the shape to determine the gradient start and end points
let bounds = instance.nonzero_bounding_box();
let bound_transform = DAffine2::from_scale_angle_translation(bounds[1] - bounds[0], 0., bounds[0]);
let inverse_parent_transform = (parent_transform.matrix2.determinant() != 0.).then(|| parent_transform.inverse()).unwrap_or_default();
let mod_points = inverse_parent_transform * multiplied_transform * bound_transform;
let inverse_parent_transform = (parent_transform.matrix2.determinant() != 0.).then(|| parent_transform.inverse()).unwrap_or_default();
let mod_points = inverse_parent_transform * multiplied_transform * bound_transform;
let start = mod_points.transform_point2(gradient.start);
let end = mod_points.transform_point2(gradient.end);
let start = mod_points.transform_point2(gradient.start);
let end = mod_points.transform_point2(gradient.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(start),
end_radius: radius as f32,
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(start),
end_radius: radius as f32,
}
}
}
},
stops,
..Default::default()
});
// Vello does `elment_transform * brush_transform` internally. We don't want elment_transform to have any impact so we need to left multiply by the inverse.
// This makes the final internal brush transform equal to `parent_transform`, allowing you to strech a gradient by transforming the parent folder.
let inverse_element_transform = (element_transform.matrix2.determinant() != 0.).then(|| element_transform.inverse()).unwrap_or_default();
let brush_transform = kurbo::Affine::new((inverse_element_transform * parent_transform).to_cols_array());
scene.fill(peniko::Fill::NonZero, kurbo::Affine::new(element_transform.to_cols_array()), &fill, Some(brush_transform), &path);
}
Fill::None => (),
};
},
stops,
..Default::default()
});
// Vello does `element_transform * brush_transform` internally. We don't want element_transform to have any impact so we need to left multiply by the inverse.
// This makes the final internal brush transform equal to `parent_transform`, allowing you to stretch a gradient by transforming the parent folder.
let inverse_element_transform = (element_transform.matrix2.determinant() != 0.).then(|| element_transform.inverse()).unwrap_or_default();
let brush_transform = kurbo::Affine::new((inverse_element_transform * parent_transform).to_cols_array());
scene.fill(peniko::Fill::NonZero, kurbo::Affine::new(element_transform.to_cols_array()), &fill, Some(brush_transform), &path);
}
Fill::None => (),
};
if let Some(stroke) = self.style.stroke() {
let color = match stroke.color {
Some(color) => peniko::Color::new([color.r(), color.g(), color.b(), color.a()]),
None => peniko::Color::TRANSPARENT,
};
use crate::vector::style::{LineCap, LineJoin};
use vello::kurbo::{Cap, Join};
let cap = match stroke.line_cap {
LineCap::Butt => Cap::Butt,
LineCap::Round => Cap::Round,
LineCap::Square => Cap::Square,
};
let join = match stroke.line_join {
LineJoin::Miter => Join::Miter,
LineJoin::Bevel => Join::Bevel,
LineJoin::Round => Join::Round,
};
let stroke = kurbo::Stroke {
width: stroke.weight,
miter_limit: stroke.line_join_miter_limit,
join,
start_cap: cap,
end_cap: cap,
dash_pattern: stroke.dash_lengths.into(),
dash_offset: stroke.dash_offset,
};
if stroke.width > 0. {
scene.stroke(&stroke, kurbo::Affine::new(element_transform.to_cols_array()), color, None, &path);
if let Some(stroke) = instance.style.stroke() {
let color = match stroke.color {
Some(color) => peniko::Color::new([color.r(), color.g(), color.b(), color.a()]),
None => peniko::Color::TRANSPARENT,
};
use crate::vector::style::{LineCap, LineJoin};
use vello::kurbo::{Cap, Join};
let cap = match stroke.line_cap {
LineCap::Butt => Cap::Butt,
LineCap::Round => Cap::Round,
LineCap::Square => Cap::Square,
};
let join = match stroke.line_join {
LineJoin::Miter => Join::Miter,
LineJoin::Bevel => Join::Bevel,
LineJoin::Round => Join::Round,
};
let stroke = kurbo::Stroke {
width: stroke.weight,
miter_limit: stroke.line_join_miter_limit,
join,
start_cap: cap,
end_cap: cap,
dash_pattern: stroke.dash_lengths.into(),
dash_offset: stroke.dash_offset,
};
if stroke.width > 0. {
scene.stroke(&stroke, kurbo::Affine::new(element_transform.to_cols_array()), color, None, &path);
}
}
if layer {
scene.pop_layer();
}
}
if layer {
scene.pop_layer();
}
}
fn new_ids_from_hash(&mut self, reference: Option<NodeId>) {
self.vector_new_ids_from_hash(reference.map(|id| id.0).unwrap_or_default());
for instance in self.instances_mut() {
instance.vector_new_ids_from_hash(reference.map(|id| id.0).unwrap_or_default());
}
}
fn to_graphic_element(&self) -> GraphicElement {
GraphicElement::VectorData(Box::new(self.clone()))
let instance = self.one_item();
GraphicElement::VectorData(VectorDataTable::new(instance.clone()))
}
}
@@ -690,8 +757,8 @@ impl GraphicElementRendered for Artboard {
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
let mut subpath = Subpath::new_rect(DVec2::ZERO, self.dimensions.as_dvec2());
if self.graphic_group.transform.matrix2.determinant() != 0. {
subpath.apply_transform(self.graphic_group.transform.inverse());
if self.graphic_group.transform().matrix2.determinant() != 0. {
subpath.apply_transform(self.graphic_group.transform().inverse());
click_targets.push(ClickTarget::new(subpath, 0.));
}
}
@@ -726,7 +793,7 @@ impl GraphicElementRendered for Artboard {
}
}
impl GraphicElementRendered for crate::ArtboardGroup {
impl GraphicElementRendered for ArtboardGroup {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
for (artboard, _) in &self.artboards {
artboard.render_svg(render, render_params);
@@ -761,56 +828,64 @@ impl GraphicElementRendered for crate::ArtboardGroup {
}
}
impl GraphicElementRendered for ImageFrame<Color> {
impl GraphicElementRendered for ImageFrameTable<Color> {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
let transform = self.transform * render.transform;
for instance in self.instances() {
let transform = instance.transform * render.transform;
match render_params.image_render_mode {
ImageRenderMode::Base64 => {
let image = &self.image;
if image.data.is_empty() {
return;
match render_params.image_render_mode {
ImageRenderMode::Base64 => {
let image = &instance.image;
if image.data.is_empty() {
return;
}
let base64_string = image.base64_string.clone().unwrap_or_else(|| {
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);
base64_string
});
render.leaf_tag("image", |attributes| {
attributes.push("width", 1.to_string());
attributes.push("height", 1.to_string());
attributes.push("preserveAspectRatio", "none");
attributes.push("href", base64_string);
let matrix = format_transform_matrix(transform);
if !matrix.is_empty() {
attributes.push("transform", matrix);
}
if instance.alpha_blending.opacity < 1. {
attributes.push("opacity", instance.alpha_blending.opacity.to_string());
}
if instance.alpha_blending.blend_mode != BlendMode::default() {
attributes.push("style", instance.alpha_blending.blend_mode.render());
}
});
}
let base64_string = image.base64_string.clone().unwrap_or_else(|| {
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);
base64_string
});
render.leaf_tag("image", |attributes| {
attributes.push("width", 1.to_string());
attributes.push("height", 1.to_string());
attributes.push("preserveAspectRatio", "none");
attributes.push("href", base64_string);
let matrix = format_transform_matrix(transform);
if !matrix.is_empty() {
attributes.push("transform", matrix);
}
if self.alpha_blending.opacity < 1. {
attributes.push("opacity", self.alpha_blending.opacity.to_string());
}
if self.alpha_blending.blend_mode != BlendMode::default() {
attributes.push("style", self.alpha_blending.blend_mode.render());
}
});
}
}
}
fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
let transform = transform * self.transform;
(transform.matrix2.determinant() != 0.).then(|| (transform * Quad::from_box([DVec2::ZERO, DVec2::ONE])).bounding_box())
self.instances()
.flat_map(|instance| {
let transform = transform * instance.transform;
(transform.matrix2.determinant() != 0.).then(|| (transform * Quad::from_box([DVec2::ZERO, DVec2::ONE])).bounding_box())
})
.reduce(Quad::combine_bounds)
}
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) {
let instance = self.one_item();
let Some(element_id) = element_id else { return };
let subpath = Subpath::new_rect(DVec2::ZERO, DVec2::ONE);
metadata.click_targets.insert(element_id, vec![ClickTarget::new(subpath, 0.)]);
metadata.footprints.insert(element_id, (footprint, self.transform));
metadata.footprints.insert(element_id, (footprint, instance.transform));
}
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
@@ -822,55 +897,61 @@ impl GraphicElementRendered for ImageFrame<Color> {
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, _: &mut RenderContext) {
use vello::peniko;
let image = &self.image;
if image.data.is_empty() {
return;
}
let image = vello::peniko::Image::new(image.to_flat_u8().0.into(), peniko::Format::Rgba8, image.width, image.height).with_extend(peniko::Extend::Repeat);
let transform = transform * self.transform * DAffine2::from_scale(1. / DVec2::new(image.width as f64, image.height as f64));
for instance in self.instances() {
let image = &instance.image;
if image.data.is_empty() {
return;
}
let image = vello::peniko::Image::new(image.to_flat_u8().0.into(), peniko::Format::Rgba8, image.width, image.height).with_extend(peniko::Extend::Repeat);
let transform = transform * instance.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()));
scene.draw_image(&image, vello::kurbo::Affine::new(transform.to_cols_array()));
}
}
}
impl GraphicElementRendered for Raster {
impl GraphicElementRendered for RasterFrame {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
let transform = self.transform() * render.transform;
match render_params.image_render_mode {
ImageRenderMode::Base64 => {
let image = match self {
Raster::ImageFrame(ref image) => image,
Raster::Texture(_) => return,
RasterFrame::ImageFrame(ref image) => image,
RasterFrame::TextureFrame(_) => return,
};
let (image, blending) = (&image.image, image.alpha_blending);
if image.data.is_empty() {
return;
}
let base64_string = image.base64_string.clone().unwrap_or_else(|| {
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);
base64_string
});
render.leaf_tag("image", |attributes| {
attributes.push("width", 1.to_string());
attributes.push("height", 1.to_string());
attributes.push("preserveAspectRatio", "none");
attributes.push("href", base64_string);
let matrix = format_transform_matrix(transform);
if !matrix.is_empty() {
attributes.push("transform", matrix);
for image in image.instances() {
let (image, blending) = (&image.image, image.alpha_blending);
if image.data.is_empty() {
return;
}
if blending.opacity < 1. {
attributes.push("opacity", blending.opacity.to_string());
}
if blending.blend_mode != BlendMode::default() {
attributes.push("style", blending.blend_mode.render());
}
});
let base64_string = image.base64_string.clone().unwrap_or_else(|| {
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);
base64_string
});
render.leaf_tag("image", |attributes| {
attributes.push("width", 1.to_string());
attributes.push("height", 1.to_string());
attributes.push("preserveAspectRatio", "none");
attributes.push("href", base64_string);
let matrix = format_transform_matrix(transform);
if !matrix.is_empty() {
attributes.push("transform", matrix);
}
if blending.opacity < 1. {
attributes.push("opacity", blending.opacity.to_string());
}
if blending.blend_mode != BlendMode::default() {
attributes.push("style", blending.blend_mode.render());
}
});
}
}
}
}
@@ -897,35 +978,46 @@ impl GraphicElementRendered for Raster {
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext) {
use vello::peniko;
let (image, blend_mode) = match self {
Raster::ImageFrame(image_frame) => {
let image = &image_frame.image;
if image.data.is_empty() {
return;
}
let image = vello::peniko::Image::new(image.to_flat_u8().0.into(), peniko::Format::Rgba8, image.width, image.height).with_extend(peniko::Extend::Repeat);
(image, image_frame.alpha_blending)
let mut render_stuff = |image: vello::peniko::Image, blend_mode: crate::AlphaBlending| {
let image_transform = transform * self.transform() * DAffine2::from_scale(1. / DVec2::new(image.width as f64, image.height as f64));
let layer = blend_mode != Default::default();
let Some(bounds) = self.bounding_box(transform) else { return };
let blending = vello::peniko::BlendMode::new(blend_mode.blend_mode.into(), vello::peniko::Compose::SrcOver);
if layer {
let rect = vello::kurbo::Rect::new(bounds[0].x, bounds[0].y, bounds[1].x, bounds[1].y);
scene.push_layer(blending, blend_mode.opacity, kurbo::Affine::IDENTITY, &rect);
}
Raster::Texture(texture) => {
let image = vello::peniko::Image::new(vec![].into(), peniko::Format::Rgba8, texture.texture.width(), texture.texture.height()).with_extend(peniko::Extend::Repeat);
let id = image.data.id();
context.ressource_overrides.insert(id, texture.texture.clone());
(image, texture.alpha_blend)
scene.draw_image(&image, vello::kurbo::Affine::new(image_transform.to_cols_array()));
if layer {
scene.pop_layer()
}
};
let image_transform = transform * self.transform() * DAffine2::from_scale(1. / DVec2::new(image.width as f64, image.height as f64));
let layer = blend_mode != Default::default();
let Some(bounds) = self.bounding_box(transform) else { return };
let blending = vello::peniko::BlendMode::new(blend_mode.blend_mode.into(), vello::peniko::Compose::SrcOver);
match self {
RasterFrame::ImageFrame(image_frame) => {
for image_frame in image_frame.instances() {
let image = &image_frame.image;
if image.data.is_empty() {
return;
}
if layer {
let rect = vello::kurbo::Rect::new(bounds[0].x, bounds[0].y, bounds[1].x, bounds[1].y);
scene.push_layer(blending, blend_mode.opacity, kurbo::Affine::IDENTITY, &rect);
}
scene.draw_image(&image, vello::kurbo::Affine::new(image_transform.to_cols_array()));
if layer {
scene.pop_layer()
let image = vello::peniko::Image::new(image.to_flat_u8().0.into(), peniko::Format::Rgba8, image.width, image.height).with_extend(peniko::Extend::Repeat);
render_stuff(image, image_frame.alpha_blending);
}
}
RasterFrame::TextureFrame(texture) => {
for texture in texture.instances() {
let image = vello::peniko::Image::new(vec![].into(), peniko::Format::Rgba8, texture.texture.width(), texture.texture.height()).with_extend(peniko::Extend::Repeat);
let id = image.data.id();
context.resource_overrides.insert(id, texture.texture.clone());
render_stuff(image, texture.alpha_blend);
}
}
}
}
}
@@ -934,15 +1026,15 @@ impl GraphicElementRendered for GraphicElement {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
match self {
GraphicElement::VectorData(vector_data) => vector_data.render_svg(render, render_params),
GraphicElement::Raster(raster) => raster.render_svg(render, render_params),
GraphicElement::RasterFrame(raster) => raster.render_svg(render, render_params),
GraphicElement::GraphicGroup(graphic_group) => graphic_group.render_svg(render, render_params),
}
}
fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
match self {
GraphicElement::VectorData(vector_data) => GraphicElementRendered::bounding_box(&**vector_data, transform),
GraphicElement::Raster(raster) => raster.bounding_box(transform),
GraphicElement::VectorData(vector_data) => vector_data.bounding_box(transform),
GraphicElement::RasterFrame(raster) => raster.bounding_box(transform),
GraphicElement::GraphicGroup(graphic_group) => graphic_group.bounding_box(transform),
}
}
@@ -954,7 +1046,7 @@ impl GraphicElementRendered for GraphicElement {
match self {
GraphicElement::VectorData(vector_data) => vector_data.collect_metadata(metadata, footprint, element_id),
GraphicElement::Raster(raster) => raster.collect_metadata(metadata, footprint, element_id),
GraphicElement::RasterFrame(raster) => raster.collect_metadata(metadata, footprint, element_id),
GraphicElement::GraphicGroup(graphic_group) => graphic_group.collect_metadata(metadata, footprint, element_id),
}
}
@@ -962,7 +1054,7 @@ impl GraphicElementRendered for GraphicElement {
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
match self {
GraphicElement::VectorData(vector_data) => vector_data.add_upstream_click_targets(click_targets),
GraphicElement::Raster(raster) => raster.add_upstream_click_targets(click_targets),
GraphicElement::RasterFrame(raster) => raster.add_upstream_click_targets(click_targets),
GraphicElement::GraphicGroup(graphic_group) => graphic_group.add_upstream_click_targets(click_targets),
}
}
@@ -972,7 +1064,7 @@ impl GraphicElementRendered for GraphicElement {
match self {
GraphicElement::VectorData(vector_data) => vector_data.render_to_vello(scene, transform, context),
GraphicElement::GraphicGroup(graphic_group) => graphic_group.render_to_vello(scene, transform, context),
GraphicElement::Raster(raster) => raster.render_to_vello(scene, transform, context),
GraphicElement::RasterFrame(raster) => raster.render_to_vello(scene, transform, context),
}
}
@@ -980,7 +1072,7 @@ impl GraphicElementRendered for GraphicElement {
match self {
GraphicElement::VectorData(vector_data) => vector_data.contains_artboard(),
GraphicElement::GraphicGroup(graphic_group) => graphic_group.contains_artboard(),
GraphicElement::Raster(raster) => raster.contains_artboard(),
GraphicElement::RasterFrame(raster) => raster.contains_artboard(),
}
}
@@ -988,7 +1080,7 @@ impl GraphicElementRendered for GraphicElement {
match self {
GraphicElement::VectorData(vector_data) => vector_data.new_ids_from_hash(reference),
GraphicElement::GraphicGroup(graphic_group) => graphic_group.new_ids_from_hash(reference),
GraphicElement::Raster(_) => (),
GraphicElement::RasterFrame(_) => (),
}
}
}

View File

@@ -0,0 +1,87 @@
use crate::vector::InstanceId;
use crate::GraphicElement;
use dyn_any::StaticType;
use std::hash::Hash;
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct Instances<T>
where
T: Into<GraphicElement> + StaticType + 'static,
{
id: Vec<InstanceId>,
instances: Vec<T>,
}
impl<T: Into<GraphicElement> + StaticType + 'static> Instances<T> {
pub fn new(instance: T) -> Self {
Self {
id: vec![InstanceId::generate()],
instances: vec![instance],
}
}
pub fn one_item(&self) -> &T {
self.instances.first().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {} (one_item)", self.instances.len()))
}
pub fn one_item_mut(&mut self) -> &mut T {
let length = self.instances.len();
self.instances.first_mut().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {} (one_item_mut)", length))
}
pub fn instances(&self) -> impl Iterator<Item = &T> {
assert!(self.instances.len() == 1, "ONE INSTANCE EXPECTED, FOUND {} (instances)", self.instances.len());
self.instances.iter()
}
pub fn instances_mut(&mut self) -> impl Iterator<Item = &mut T> {
assert!(self.instances.len() == 1, "ONE INSTANCE EXPECTED, FOUND {} (instances_mut)", self.instances.len());
self.instances.iter_mut()
}
// pub fn id(&self) -> impl Iterator<Item = InstanceId> + '_ {
// self.id.iter().copied()
// }
// pub fn push(&mut self, id: InstanceId, instance: T) {
// self.id.push(id);
// self.instances.push(instance);
// }
// pub fn replace_all(&mut self, id: InstanceId, instance: T) {
// let mut instance = instance;
// for (old_id, old_instance) in self.id.iter_mut().zip(self.instances.iter_mut()) {
// let mut new_id = id;
// std::mem::swap(old_id, &mut new_id);
// std::mem::swap(&mut instance, old_instance);
// }
// }
}
impl<T: Into<GraphicElement> + Default + Hash + StaticType + 'static> Default for Instances<T> {
fn default() -> Self {
Self::new(T::default())
}
}
impl<T: Into<GraphicElement> + Hash + StaticType + 'static> core::hash::Hash for Instances<T> {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.id.hash(state);
for instance in &self.instances {
instance.hash(state);
}
}
}
impl<T: Into<GraphicElement> + PartialEq + StaticType + 'static> PartialEq for Instances<T> {
fn eq(&self, other: &Self) -> bool {
self.id == other.id && self.instances.len() == other.instances.len() && { self.instances.iter().zip(other.instances.iter()).all(|(a, b)| a == b) }
}
}
unsafe impl<T: Into<GraphicElement> + StaticType + 'static> dyn_any::StaticType for Instances<T> {
type Static = Instances<T>;
}

View File

@@ -15,6 +15,7 @@ pub use ctor;
pub mod consts;
pub mod generic;
pub mod instances;
pub mod logic;
pub mod ops;
pub mod structural;

View File

@@ -1,13 +1,14 @@
use crate::transform::Footprint;
use crate::vector::VectorData;
use crate::vector::VectorDataTable;
use glam::{DAffine2, DVec2};
#[node_macro::node(category("Debug"))]
async fn log_to_console<T: core::fmt::Debug, F: Send + 'n>(
#[implementations((), (), (), (), (), (), (), (), Footprint)] footprint: F,
#[implementations(
() -> String, () -> bool, () -> f64, () -> u32, () -> u64, () -> DVec2, () -> VectorData, () -> DAffine2,
Footprint -> String, Footprint -> bool, Footprint -> f64, Footprint -> u32, Footprint -> u64, Footprint -> DVec2, Footprint -> VectorData, Footprint -> DAffine2,
() -> String, () -> bool, () -> f64, () -> u32, () -> u64, () -> DVec2, () -> VectorDataTable, () -> DAffine2,
Footprint -> String, Footprint -> bool, Footprint -> f64, Footprint -> u32, Footprint -> u64, Footprint -> DVec2, Footprint -> VectorDataTable, Footprint -> DAffine2,
)]
value: impl Node<F, Output = T>,
) -> T {
@@ -37,14 +38,14 @@ async fn switch<T, F: Send + 'n>(
condition: bool,
#[expose]
#[implementations(
() -> String, () -> bool, () -> f64, () -> u32, () -> u64, () -> DVec2, () -> VectorData, () -> DAffine2,
Footprint -> String, Footprint -> bool, Footprint -> f64, Footprint -> u32, Footprint -> u64, Footprint -> DVec2, Footprint -> VectorData, Footprint -> DAffine2
() -> String, () -> bool, () -> f64, () -> u32, () -> u64, () -> DVec2, () -> VectorDataTable, () -> DAffine2,
Footprint -> String, Footprint -> bool, Footprint -> f64, Footprint -> u32, Footprint -> u64, Footprint -> DVec2, Footprint -> VectorDataTable, Footprint -> DAffine2
)]
if_true: impl Node<F, Output = T>,
#[expose]
#[implementations(
() -> String, () -> bool, () -> f64, () -> u32, () -> u64, () -> DVec2, () -> VectorData, () -> DAffine2,
Footprint -> String, Footprint -> bool, Footprint -> f64, Footprint -> u32, Footprint -> u64, Footprint -> DVec2, Footprint -> VectorData, Footprint -> DAffine2
() -> String, () -> bool, () -> f64, () -> u32, () -> u64, () -> DVec2, () -> VectorDataTable, () -> DAffine2,
Footprint -> String, Footprint -> bool, Footprint -> f64, Footprint -> u32, Footprint -> u64, Footprint -> DVec2, Footprint -> VectorDataTable, Footprint -> DAffine2
)]
if_false: impl Node<F, Output = T>,
) -> T {

View File

@@ -1,5 +1,5 @@
use crate::raster::image::ImageFrameTable;
use crate::raster::BlendMode;
use crate::raster::ImageFrame;
use crate::registry::types::Percentage;
use crate::vector::style::GradientStops;
use crate::{Color, Node};
@@ -472,7 +472,7 @@ fn unwrap<T: Default>(_: (), #[implementations(Option<f64>, Option<f32>, Option<
/// Meant for debugging purposes, not general use. Clones the input value.
#[node_macro::node(category("Debug"))]
fn clone<'i, T: Clone + 'i>(_: (), #[implementations(&ImageFrame<Color>)] value: &'i T) -> T {
fn clone<'i, T: Clone + 'i>(_: (), #[implementations(&ImageFrameTable<Color>)] value: &'i T) -> T {
value.clone()
}

View File

@@ -1,7 +1,9 @@
pub use self::color::{Color, Luma, SRGBA8};
use crate::vector::VectorData;
use crate::GraphicGroup;
use crate::{registry::types::Percentage, transform::Footprint};
use crate::raster::image::ImageFrameTable;
use crate::registry::types::Percentage;
use crate::transform::Footprint;
use crate::vector::VectorDataTable;
use crate::GraphicGroupTable;
use bytemuck::{Pod, Zeroable};
use core::fmt::Debug;
@@ -283,27 +285,33 @@ impl<T: BitmapMut + Bitmap> BitmapMut for &mut T {
}
#[cfg(feature = "alloc")]
pub use self::image::{Image, ImageFrame};
pub use self::image::Image;
#[cfg(feature = "alloc")]
pub(crate) mod image;
pub mod image;
trait SetBlendMode {
fn set_blend_mode(&mut self, blend_mode: BlendMode);
}
impl SetBlendMode for VectorData {
impl SetBlendMode for VectorDataTable {
fn set_blend_mode(&mut self, blend_mode: BlendMode) {
self.alpha_blending.blend_mode = blend_mode;
for instance in self.instances_mut() {
instance.alpha_blending.blend_mode = blend_mode;
}
}
}
impl SetBlendMode for GraphicGroup {
impl SetBlendMode for GraphicGroupTable {
fn set_blend_mode(&mut self, blend_mode: BlendMode) {
self.alpha_blending.blend_mode = blend_mode;
for instance in self.instances_mut() {
instance.alpha_blending.blend_mode = blend_mode;
}
}
}
impl SetBlendMode for ImageFrame<Color> {
impl SetBlendMode for ImageFrameTable<Color> {
fn set_blend_mode(&mut self, blend_mode: BlendMode) {
self.alpha_blending.blend_mode = blend_mode;
for instance in self.instances_mut() {
instance.alpha_blending.blend_mode = blend_mode;
}
}
}
@@ -317,12 +325,12 @@ async fn blend_mode<F: 'n + Send, T: SetBlendMode>(
)]
footprint: F,
#[implementations(
() -> GraphicGroup,
() -> VectorData,
() -> ImageFrame<Color>,
Footprint -> GraphicGroup,
Footprint -> VectorData,
Footprint -> ImageFrame<Color>,
() -> GraphicGroupTable,
() -> VectorDataTable,
() -> ImageFrameTable<Color>,
Footprint -> GraphicGroupTable,
Footprint -> VectorDataTable,
Footprint -> ImageFrameTable<Color>,
)]
value: impl Node<F, Output = T>,
blend_mode: BlendMode,
@@ -342,12 +350,12 @@ async fn opacity<F: 'n + Send, T: MultiplyAlpha>(
)]
footprint: F,
#[implementations(
() -> GraphicGroup,
() -> VectorData,
() -> ImageFrame<Color>,
Footprint -> GraphicGroup,
Footprint -> VectorData,
Footprint -> ImageFrame<Color>,
() -> GraphicGroupTable,
() -> VectorDataTable,
() -> ImageFrameTable<Color>,
Footprint -> GraphicGroupTable,
Footprint -> VectorDataTable,
Footprint -> ImageFrameTable<Color>,
)]
value: impl Node<F, Output = T>,
#[default(100.)] factor: Percentage,

View File

@@ -1,15 +1,15 @@
#![allow(clippy::too_many_arguments)]
#[cfg(feature = "alloc")]
use super::curve::{Curve, CurveManipulatorGroup, ValueMapperNode};
use crate::raster::curve::{Curve, CurveManipulatorGroup, ValueMapperNode};
#[cfg(feature = "alloc")]
use super::ImageFrame;
use super::{Channel, Color, Pixel};
use crate::raster::image::{ImageFrame, ImageFrameTable};
use crate::raster::{Channel, Color, Pixel};
use crate::registry::types::{Angle, Percentage, SignedPercentage};
use crate::transform::Footprint;
use crate::vector::style::GradientStops;
use crate::vector::VectorData;
use crate::GraphicGroup;
use crate::vector::VectorDataTable;
use crate::{GraphicElement, GraphicGroupTable};
use dyn_any::DynAny;
@@ -294,10 +294,10 @@ async fn luminance<F: 'n + Send, T: Adjust<Color>>(
footprint: F,
#[implementations(
() -> Color,
() -> ImageFrame<Color>,
() -> ImageFrameTable<Color>,
() -> GradientStops,
Footprint -> Color,
Footprint -> ImageFrame<Color>,
Footprint -> ImageFrameTable<Color>,
Footprint -> GradientStops,
)]
input: impl Node<F, Output = T>,
@@ -328,10 +328,10 @@ async fn extract_channel<F: 'n + Send, T: Adjust<Color>>(
footprint: F,
#[implementations(
() -> Color,
() -> ImageFrame<Color>,
() -> ImageFrameTable<Color>,
() -> GradientStops,
Footprint -> Color,
Footprint -> ImageFrame<Color>,
Footprint -> ImageFrameTable<Color>,
Footprint -> GradientStops,
)]
input: impl Node<F, Output = T>,
@@ -361,10 +361,10 @@ async fn make_opaque<F: 'n + Send, T: Adjust<Color>>(
footprint: F,
#[implementations(
() -> Color,
() -> ImageFrame<Color>,
() -> ImageFrameTable<Color>,
() -> GradientStops,
Footprint -> Color,
Footprint -> ImageFrame<Color>,
Footprint -> ImageFrameTable<Color>,
Footprint -> GradientStops,
)]
input: impl Node<F, Output = T>,
@@ -395,10 +395,10 @@ async fn levels<F: 'n + Send, T: Adjust<Color>>(
footprint: F,
#[implementations(
() -> Color,
() -> ImageFrame<Color>,
() -> ImageFrameTable<Color>,
() -> GradientStops,
Footprint -> Color,
Footprint -> ImageFrame<Color>,
Footprint -> ImageFrameTable<Color>,
Footprint -> GradientStops,
)]
image: impl Node<F, Output = T>,
@@ -472,10 +472,10 @@ async fn black_and_white<F: 'n + Send, T: Adjust<Color>>(
footprint: F,
#[implementations(
() -> Color,
() -> ImageFrame<Color>,
() -> ImageFrameTable<Color>,
() -> GradientStops,
Footprint -> Color,
Footprint -> ImageFrame<Color>,
Footprint -> ImageFrameTable<Color>,
Footprint -> GradientStops,
)]
image: impl Node<F, Output = T>,
@@ -554,10 +554,10 @@ async fn hue_saturation<F: 'n + Send, T: Adjust<Color>>(
footprint: F,
#[implementations(
() -> Color,
() -> ImageFrame<Color>,
() -> ImageFrameTable<Color>,
() -> GradientStops,
Footprint -> Color,
Footprint -> ImageFrame<Color>,
Footprint -> ImageFrameTable<Color>,
Footprint -> GradientStops,
)]
input: impl Node<F, Output = T>,
@@ -598,10 +598,10 @@ async fn invert<F: 'n + Send, T: Adjust<Color>>(
footprint: F,
#[implementations(
() -> Color,
() -> ImageFrame<Color>,
() -> ImageFrameTable<Color>,
() -> GradientStops,
Footprint -> Color,
Footprint -> ImageFrame<Color>,
Footprint -> ImageFrameTable<Color>,
Footprint -> GradientStops,
)]
input: impl Node<F, Output = T>,
@@ -630,10 +630,10 @@ async fn threshold<F: 'n + Send, T: Adjust<Color>>(
footprint: F,
#[implementations(
() -> Color,
() -> ImageFrame<Color>,
() -> ImageFrameTable<Color>,
() -> GradientStops,
Footprint -> Color,
Footprint -> ImageFrame<Color>,
Footprint -> ImageFrameTable<Color>,
Footprint -> GradientStops,
)]
image: impl Node<F, Output = T>,
@@ -666,7 +666,6 @@ async fn threshold<F: 'n + Send, T: Adjust<Color>>(
trait Blend<P: Pixel> {
fn blend(&self, under: &Self, blend_fn: impl Fn(P, P) -> P) -> Self;
}
impl Blend<Color> for Color {
fn blend(&self, under: &Self, blend_fn: impl Fn(Color, Color) -> Color) -> Self {
blend_fn(*self, *under)
@@ -681,24 +680,28 @@ impl Blend<Color> for Option<Color> {
}
}
}
impl Blend<Color> for ImageFrame<Color> {
impl Blend<Color> for ImageFrameTable<Color> {
fn blend(&self, under: &Self, blend_fn: impl Fn(Color, Color) -> Color) -> Self {
let data = self.image.data.iter().zip(under.image.data.iter()).map(|(a, b)| blend_fn(*a, *b)).collect();
let mut result = self.clone();
ImageFrame {
image: super::Image {
data,
width: self.image.width,
height: self.image.height,
base64_string: None,
},
transform: self.transform,
alpha_blending: self.alpha_blending,
for (over, under) in result.instances_mut().zip(under.instances()) {
let data = over.image.data.iter().zip(under.image.data.iter()).map(|(a, b)| blend_fn(*a, *b)).collect();
*over = ImageFrame {
image: super::Image {
data,
width: over.image.width,
height: over.image.height,
base64_string: None,
},
transform: over.transform,
alpha_blending: over.alpha_blending,
};
}
result
}
}
impl Blend<Color> for GradientStops {
fn blend(&self, under: &Self, blend_fn: impl Fn(Color, Color) -> Color) -> Self {
let mut combined_stops = self.0.iter().map(|(position, _)| position).chain(under.0.iter().map(|(position, _)| position)).collect::<Vec<_>>();
@@ -730,20 +733,20 @@ async fn blend<F: 'n + Send + Copy, T: Blend<Color> + Send>(
footprint: F,
#[implementations(
() -> Color,
() -> ImageFrame<Color>,
() -> ImageFrameTable<Color>,
() -> GradientStops,
Footprint -> Color,
Footprint -> ImageFrame<Color>,
Footprint -> ImageFrameTable<Color>,
Footprint -> GradientStops,
)]
over: impl Node<F, Output = T>,
#[expose]
#[implementations(
() -> Color,
() -> ImageFrame<Color>,
() -> ImageFrameTable<Color>,
() -> GradientStops,
Footprint -> Color,
Footprint -> ImageFrame<Color>,
Footprint -> ImageFrameTable<Color>,
Footprint -> GradientStops,
)]
under: impl Node<F, Output = T>,
@@ -753,7 +756,7 @@ async fn blend<F: 'n + Send + Copy, T: Blend<Color> + Send>(
let over = over.eval(footprint).await;
let under = under.eval(footprint).await;
Blend::blend(&over, &under, |a, b| blend_colors(a, b, blend_mode, opacity / 100.))
over.blend(&under, |a, b| blend_colors(a, b, blend_mode, opacity / 100.))
}
#[node_macro::node(category(""))]
@@ -800,8 +803,8 @@ pub fn apply_blend_mode(foreground: Color, background: Color, blend_mode: BlendM
}
}
trait Adjust<C> {
fn adjust(&mut self, map_fn: impl Fn(&C) -> C);
trait Adjust<P> {
fn adjust(&mut self, map_fn: impl Fn(&P) -> P);
}
impl Adjust<Color> for Color {
fn adjust(&mut self, map_fn: impl Fn(&Color) -> Color) {
@@ -822,10 +825,17 @@ impl Adjust<Color> for GradientStops {
}
}
}
impl<C: Pixel> Adjust<C> for ImageFrame<C> {
fn adjust(&mut self, map_fn: impl Fn(&C) -> C) {
for c in self.image.data.iter_mut() {
*c = map_fn(c);
impl<P: Pixel> Adjust<P> for ImageFrameTable<P>
where
P: dyn_any::StaticType,
P::Static: Pixel,
GraphicElement: From<ImageFrame<P>>,
{
fn adjust(&mut self, map_fn: impl Fn(&P) -> P) {
for instance in self.instances_mut() {
for c in instance.image.data.iter_mut() {
*c = map_fn(c);
}
}
}
}
@@ -857,10 +867,10 @@ async fn gradient_map<F: 'n + Send, T: Adjust<Color>>(
footprint: F,
#[implementations(
() -> Color,
() -> ImageFrame<Color>,
() -> ImageFrameTable<Color>,
() -> GradientStops,
Footprint -> Color,
Footprint -> ImageFrame<Color>,
Footprint -> ImageFrameTable<Color>,
Footprint -> GradientStops,
)]
image: impl Node<F, Output = T>,
@@ -896,10 +906,10 @@ async fn vibrance<F: 'n + Send, T: Adjust<Color>>(
footprint: F,
#[implementations(
() -> Color,
() -> ImageFrame<Color>,
() -> ImageFrameTable<Color>,
() -> GradientStops,
Footprint -> Color,
Footprint -> ImageFrame<Color>,
Footprint -> ImageFrameTable<Color>,
Footprint -> GradientStops,
)]
image: impl Node<F, Output = T>,
@@ -1196,10 +1206,10 @@ async fn channel_mixer<F: 'n + Send, T: Adjust<Color>>(
footprint: F,
#[implementations(
() -> Color,
() -> ImageFrame<Color>,
() -> ImageFrameTable<Color>,
() -> GradientStops,
Footprint -> Color,
Footprint -> ImageFrame<Color>,
Footprint -> ImageFrameTable<Color>,
Footprint -> GradientStops,
)]
image: impl Node<F, Output = T>,
@@ -1357,10 +1367,10 @@ async fn selective_color<F: 'n + Send, T: Adjust<Color>>(
footprint: F,
#[implementations(
() -> Color,
() -> ImageFrame<Color>,
() -> ImageFrameTable<Color>,
() -> GradientStops,
Footprint -> Color,
Footprint -> ImageFrame<Color>,
Footprint -> ImageFrameTable<Color>,
Footprint -> GradientStops,
)]
image: impl Node<F, Output = T>,
@@ -1490,19 +1500,30 @@ impl MultiplyAlpha for Color {
*self = Color::from_rgbaf32_unchecked(self.r(), self.g(), self.b(), (self.a() * factor as f32).clamp(0., 1.))
}
}
impl MultiplyAlpha for VectorData {
impl MultiplyAlpha for VectorDataTable {
fn multiply_alpha(&mut self, factor: f64) {
self.alpha_blending.opacity *= factor as f32;
for instance in self.instances_mut() {
instance.alpha_blending.opacity *= factor as f32;
}
}
}
impl MultiplyAlpha for GraphicGroup {
impl MultiplyAlpha for GraphicGroupTable {
fn multiply_alpha(&mut self, factor: f64) {
self.alpha_blending.opacity *= factor as f32;
for instance in self.instances_mut() {
instance.alpha_blending.opacity *= factor as f32;
}
}
}
impl<P: Pixel> MultiplyAlpha for ImageFrame<P> {
impl<P: Pixel> MultiplyAlpha for ImageFrameTable<P>
where
P: dyn_any::StaticType,
P::Static: Pixel,
GraphicElement: From<ImageFrame<P>>,
{
fn multiply_alpha(&mut self, factor: f64) {
self.alpha_blending.opacity *= factor as f32;
for instance in self.instances_mut() {
instance.alpha_blending.opacity *= factor as f32;
}
}
}
@@ -1523,10 +1544,10 @@ async fn posterize<F: 'n + Send, T: Adjust<Color>>(
footprint: F,
#[implementations(
() -> Color,
() -> ImageFrame<Color>,
() -> ImageFrameTable<Color>,
() -> GradientStops,
Footprint -> Color,
Footprint -> ImageFrame<Color>,
Footprint -> ImageFrameTable<Color>,
Footprint -> GradientStops,
)]
input: impl Node<F, Output = T>,
@@ -1566,10 +1587,10 @@ async fn exposure<F: 'n + Send, T: Adjust<Color>>(
footprint: F,
#[implementations(
() -> Color,
() -> ImageFrame<Color>,
() -> ImageFrameTable<Color>,
() -> GradientStops,
Footprint -> Color,
Footprint -> ImageFrame<Color>,
Footprint -> ImageFrameTable<Color>,
Footprint -> GradientStops,
)]
input: impl Node<F, Output = T>,
@@ -1649,10 +1670,10 @@ async fn color_overlay<F: 'n + Send, T: Adjust<Color>>(
footprint: F,
#[implementations(
() -> Color,
() -> ImageFrame<Color>,
() -> ImageFrameTable<Color>,
() -> GradientStops,
Footprint -> Color,
Footprint -> ImageFrame<Color>,
Footprint -> ImageFrameTable<Color>,
Footprint -> GradientStops,
)]
image: impl Node<F, Output = T>,
@@ -1675,33 +1696,34 @@ async fn color_overlay<F: 'n + Send, T: Adjust<Color>>(
input
}
#[cfg(feature = "alloc")]
pub use index_node::IndexNode;
// #[cfg(feature = "alloc")]
// pub use index_node::IndexNode;
#[cfg(feature = "alloc")]
mod index_node {
use crate::raster::{Color, ImageFrame};
// #[cfg(feature = "alloc")]
// mod index_node {
// use crate::raster::{Color, ImageFrame};
#[node_macro::node(category(""))]
pub fn index<T: Default + Clone>(
_: (),
#[implementations(Vec<ImageFrame<Color>>, Vec<Color>)]
#[widget(ParsedWidgetOverride::Hidden)]
input: Vec<T>,
index: u32,
) -> T {
if (index as usize) < input.len() {
input[index as usize].clone()
} else {
warn!("The number of segments is {} but the requested segment is {}!", input.len(), index);
Default::default()
}
}
}
// #[node_macro::node(category(""))]
// pub fn index<T: Default + Clone>(
// _: (),
// #[implementations(Vec<ImageFrame<Color>>, Vec<Color>)]
// #[widget(ParsedWidgetOverride::Hidden)]
// input: Vec<T>,
// index: u32,
// ) -> T {
// if (index as usize) < input.len() {
// input[index as usize].clone()
// } else {
// warn!("The number of segments is {} but the requested segment is {}!", input.len(), index);
// Default::default()
// }
// }
// }
#[cfg(test)]
mod test {
use crate::raster::{BlendMode, Image, ImageFrame};
use crate::raster::image::{ImageFrame, ImageFrameTable};
use crate::raster::{BlendMode, Image};
use crate::{Color, Node};
use std::pin::Pin;
@@ -1730,7 +1752,8 @@ mod test {
// 100% of the output should come from the multiplied value
let opacity = 100_f64;
let result = super::color_overlay((), &FutureWrapperNode(image), overlay_color, BlendMode::Multiply, opacity).await;
let result = super::color_overlay((), &FutureWrapperNode(ImageFrameTable::new(image.clone())), overlay_color, BlendMode::Multiply, opacity).await;
let result = result.one_item();
// The output should just be the original green and alpha channels (as we multiply them by 1 and other channels by 0)
assert_eq!(result.image.data[0], Color::from_rgbaf32_unchecked(0., image_color.g(), 0., image_color.a()));

View File

@@ -5,8 +5,8 @@ use std::sync::Mutex;
use dyn_any::DynAny;
use crate::raster::image::ImageFrame;
use crate::raster::Image;
use crate::raster::ImageFrame;
use crate::vector::brush_stroke::BrushStroke;
use crate::vector::brush_stroke::BrushStyle;
use crate::Color;

View File

@@ -1,6 +1,7 @@
use super::discrete_srgb::float_to_srgb_u8;
use super::Color;
use crate::AlphaBlending;
use crate::instances::Instances;
use crate::{AlphaBlending, GraphicElement};
use alloc::vec::Vec;
use core::hash::{Hash, Hasher};
use dyn_any::StaticType;
@@ -216,7 +217,26 @@ impl<P: Pixel> IntoIterator for Image<P> {
}
}
#[derive(Clone, Debug, PartialEq, Default, specta::Type)]
// TODO: Eventually remove this migration document upgrade code
pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<ImageFrameTable<Color>, D::Error> {
use serde::Deserialize;
#[derive(serde::Serialize, serde::Deserialize)]
#[serde(untagged)]
enum EitherFormat {
ImageFrame(ImageFrame<Color>),
ImageFrameTable(ImageFrameTable<Color>),
}
Ok(match EitherFormat::deserialize(deserializer)? {
EitherFormat::ImageFrame(image_frame) => ImageFrameTable::<Color>::new(image_frame),
EitherFormat::ImageFrameTable(image_frame_table) => image_frame_table,
})
}
pub type ImageFrameTable<P> = Instances<ImageFrame<P>>;
#[derive(Clone, Debug, PartialEq, specta::Type)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ImageFrame<P: Pixel> {
pub image: Image<P>,
@@ -233,6 +253,17 @@ pub struct ImageFrame<P: Pixel> {
pub alpha_blending: AlphaBlending,
}
impl<P: Pixel> Default for ImageFrame<P> {
fn default() -> Self {
Self {
image: Image::empty(),
alpha_blending: AlphaBlending::new(),
// Different from DAffine2::default() which is IDENTITY
transform: DAffine2::ZERO,
}
}
}
impl<P: Debug + Copy + Pixel> Sample for ImageFrame<P> {
type Pixel = P;
@@ -248,6 +279,22 @@ impl<P: Debug + Copy + Pixel> Sample for ImageFrame<P> {
}
}
impl<P: Debug + Copy + Pixel + dyn_any::StaticType> Sample for ImageFrameTable<P>
where
GraphicElement: From<ImageFrame<P>>,
P::Static: Pixel,
{
type Pixel = P;
// TODO: Improve sampling logic
#[inline(always)]
fn sample(&self, pos: DVec2, area: DVec2) -> Option<Self::Pixel> {
let image = self.one_item();
Sample::sample(image, pos, area)
}
}
impl<P: Copy + Pixel> Bitmap for ImageFrame<P> {
type Pixel = P;
@@ -264,12 +311,50 @@ impl<P: Copy + Pixel> Bitmap for ImageFrame<P> {
}
}
impl<P: Copy + Pixel + dyn_any::StaticType> Bitmap for ImageFrameTable<P>
where
P::Static: Pixel,
GraphicElement: From<ImageFrame<P>>,
{
type Pixel = P;
fn width(&self) -> u32 {
let image = self.one_item();
image.width()
}
fn height(&self) -> u32 {
let image = self.one_item();
image.height()
}
fn get_pixel(&self, x: u32, y: u32) -> Option<Self::Pixel> {
let image = self.one_item();
image.get_pixel(x, y)
}
}
impl<P: Copy + Pixel> BitmapMut for ImageFrame<P> {
fn get_pixel_mut(&mut self, x: u32, y: u32) -> Option<&mut Self::Pixel> {
self.image.get_pixel_mut(x, y)
}
}
impl<P: Copy + Pixel + dyn_any::StaticType> BitmapMut for ImageFrameTable<P>
where
GraphicElement: From<ImageFrame<P>>,
P::Static: Pixel,
{
fn get_pixel_mut(&mut self, x: u32, y: u32) -> Option<&mut Self::Pixel> {
let image = self.one_item_mut();
BitmapMut::get_pixel_mut(image, x, y)
}
}
unsafe impl<P: dyn_any::StaticTypeSized + Pixel> StaticType for ImageFrame<P>
where
P::Static: Pixel,
@@ -278,22 +363,6 @@ where
}
impl<P: Copy + Pixel> ImageFrame<P> {
pub const fn empty() -> Self {
Self {
image: Image::empty(),
transform: DAffine2::ZERO,
alpha_blending: AlphaBlending::new(),
}
}
pub const fn identity() -> Self {
Self {
image: Image::empty(),
transform: DAffine2::IDENTITY,
alpha_blending: AlphaBlending::new(),
}
}
pub fn get_mut(&mut self, x: usize, y: usize) -> &mut P {
&mut self.image.data[y * (self.image.width as usize) + x]
}

View File

@@ -1,8 +1,9 @@
use crate::application_io::TextureFrame;
use crate::application_io::{TextureFrame, TextureFrameTable};
use crate::raster::bbox::AxisAlignedBbox;
use crate::raster::{ImageFrame, Pixel};
use crate::vector::VectorData;
use crate::{Artboard, ArtboardGroup, Color, GraphicElement, GraphicGroup};
use crate::raster::image::{ImageFrame, ImageFrameTable};
use crate::raster::Pixel;
use crate::vector::{VectorData, VectorDataTable};
use crate::{Artboard, ArtboardGroup, Color, GraphicElement, GraphicGroup, GraphicGroupTable};
use glam::{DAffine2, DVec2};
@@ -19,12 +20,6 @@ pub trait Transform {
}
}
impl<T: Transform> Transform for &T {
fn transform(&self) -> DAffine2 {
(*self).transform()
}
}
pub trait TransformMut: Transform {
fn transform_mut(&mut self) -> &mut DAffine2;
fn translate(&mut self, offset: DVec2) {
@@ -32,6 +27,14 @@ pub trait TransformMut: Transform {
}
}
// Implementation for references to anything that implements Transform
impl<T: Transform> Transform for &T {
fn transform(&self) -> DAffine2 {
(*self).transform()
}
}
// Implementations for ImageFrame<P>
impl<P: Pixel> Transform for ImageFrame<P> {
fn transform(&self) -> DAffine2 {
self.transform
@@ -45,6 +48,54 @@ impl<P: Pixel> TransformMut for ImageFrame<P> {
&mut self.transform
}
}
// Implementations for ImageFrameTable<P>
impl<P: Pixel> Transform for ImageFrameTable<P>
where
P: dyn_any::StaticType,
P::Static: Pixel,
GraphicElement: From<ImageFrame<P>>,
{
fn transform(&self) -> DAffine2 {
let image_frame = self.one_item();
image_frame.transform
}
fn local_pivot(&self, pivot: DVec2) -> DVec2 {
let image_frame = self.one_item();
image_frame.local_pivot(pivot)
}
}
impl<P: Pixel> TransformMut for ImageFrameTable<P>
where
P: dyn_any::StaticType,
P::Static: Pixel,
GraphicElement: From<ImageFrame<P>>,
{
fn transform_mut(&mut self) -> &mut DAffine2 {
let image_frame = self.one_item_mut();
&mut image_frame.transform
}
}
// Implementations for TextureTable
impl Transform for TextureFrameTable {
fn transform(&self) -> DAffine2 {
let image_frame = self.one_item();
image_frame.transform
}
fn local_pivot(&self, pivot: DVec2) -> DVec2 {
let image_frame = self.one_item();
image_frame.local_pivot(pivot)
}
}
impl TransformMut for TextureFrameTable {
fn transform_mut(&mut self) -> &mut DAffine2 {
let image_frame = self.one_item_mut();
&mut image_frame.transform
}
}
// Implementations for GraphicGroup
impl Transform for GraphicGroup {
fn transform(&self) -> DAffine2 {
self.transform
@@ -55,19 +106,35 @@ impl TransformMut for GraphicGroup {
&mut self.transform
}
}
// Implementations for GraphicGroupTable
impl Transform for GraphicGroupTable {
fn transform(&self) -> DAffine2 {
let graphic_group = self.one_item();
graphic_group.transform
}
}
impl TransformMut for GraphicGroupTable {
fn transform_mut(&mut self) -> &mut DAffine2 {
let graphic_group = self.one_item_mut();
&mut graphic_group.transform
}
}
// Implementations for GraphicElement
impl Transform for GraphicElement {
fn transform(&self) -> DAffine2 {
match self {
GraphicElement::VectorData(vector_shape) => vector_shape.transform(),
GraphicElement::GraphicGroup(graphic_group) => graphic_group.transform(),
GraphicElement::Raster(raster) => raster.transform(),
GraphicElement::RasterFrame(raster) => raster.transform(),
}
}
fn local_pivot(&self, pivot: DVec2) -> DVec2 {
match self {
GraphicElement::VectorData(vector_shape) => vector_shape.local_pivot(pivot),
GraphicElement::GraphicGroup(graphic_group) => graphic_group.local_pivot(pivot),
GraphicElement::Raster(raster) => raster.local_pivot(pivot),
GraphicElement::RasterFrame(raster) => raster.local_pivot(pivot),
}
}
}
@@ -76,11 +143,12 @@ impl TransformMut for GraphicElement {
match self {
GraphicElement::VectorData(vector_shape) => vector_shape.transform_mut(),
GraphicElement::GraphicGroup(graphic_group) => graphic_group.transform_mut(),
GraphicElement::Raster(raster) => raster.transform_mut(),
GraphicElement::RasterFrame(raster) => raster.transform_mut(),
}
}
}
// Implementations for VectorData
impl Transform for VectorData {
fn transform(&self) -> DAffine2 {
self.transform
@@ -95,6 +163,25 @@ impl TransformMut for VectorData {
}
}
// Implementations for VectorDataTable
impl Transform for VectorDataTable {
fn transform(&self) -> DAffine2 {
let vector_data = self.one_item();
vector_data.transform
}
fn local_pivot(&self, pivot: DVec2) -> DVec2 {
let vector_data = self.one_item();
vector_data.local_pivot(pivot)
}
}
impl TransformMut for VectorDataTable {
fn transform_mut(&mut self) -> &mut DAffine2 {
let vector_data = self.one_item_mut();
&mut vector_data.transform
}
}
// Implementations for Artboard
impl Transform for Artboard {
fn transform(&self) -> DAffine2 {
DAffine2::from_translation(self.location.as_dvec2())
@@ -104,6 +191,7 @@ impl Transform for Artboard {
}
}
// Implementations for DAffine2
impl Transform for DAffine2 {
fn transform(&self) -> DAffine2 {
*self
@@ -115,6 +203,18 @@ impl TransformMut for DAffine2 {
}
}
// Implementations for Footprint
impl Transform for Footprint {
fn transform(&self) -> DAffine2 {
self.transform
}
}
impl TransformMut for Footprint {
fn transform_mut(&mut self) -> &mut DAffine2 {
&mut self.transform
}
}
#[derive(Debug, Clone, Copy, dyn_any::DynAny, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum RenderQuality {
@@ -177,7 +277,7 @@ impl From<()> for Footprint {
}
#[node_macro::node(category("Debug"))]
fn cull<T>(_footprint: Footprint, #[implementations(VectorData, GraphicGroup, Artboard, ImageFrame<Color>, ArtboardGroup)] data: T) -> T {
fn cull<T>(_footprint: Footprint, #[implementations(VectorDataTable, GraphicGroupTable, Artboard, ImageFrameTable<Color>, ArtboardGroup)] data: T) -> T {
data
}
@@ -188,17 +288,6 @@ impl core::hash::Hash for Footprint {
}
}
impl Transform for Footprint {
fn transform(&self) -> DAffine2 {
self.transform
}
}
impl TransformMut for Footprint {
fn transform_mut(&mut self) -> &mut DAffine2 {
&mut self.transform
}
}
pub trait ApplyTransform {
fn apply_transform(&mut self, modification: &DAffine2);
}
@@ -222,13 +311,13 @@ async fn transform<I: Into<Footprint> + 'n + ApplyTransform + Clone + Send + Syn
)]
mut input: I,
#[implementations(
() -> VectorData,
() -> GraphicGroup,
() -> ImageFrame<Color>,
() -> VectorDataTable,
() -> GraphicGroupTable,
() -> ImageFrameTable<Color>,
() -> TextureFrame,
Footprint -> VectorData,
Footprint -> GraphicGroup,
Footprint -> ImageFrame<Color>,
Footprint -> VectorDataTable,
Footprint -> GraphicGroupTable,
Footprint -> ImageFrameTable<Color>,
Footprint -> TextureFrame,
)]
transform_target: impl Node<I, Output = T>,
@@ -255,7 +344,7 @@ async fn transform<I: Into<Footprint> + 'n + ApplyTransform + Clone + Send + Syn
#[node_macro::node(category(""))]
fn replace_transform<Data: TransformMut, TransformInput: Transform>(
_: (),
#[implementations(VectorData, ImageFrame<Color>, GraphicGroup)] mut data: Data,
#[implementations(VectorDataTable, ImageFrameTable<Color>, GraphicGroupTable)] mut data: Data,
#[implementations(DAffine2)] transform: TransformInput,
) -> Data {
let data_transform = data.transform_mut();

View File

@@ -131,12 +131,15 @@ impl From<String> for ProtoNodeIdentifier {
fn migrate_type_descriptor_names<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Cow<'static, str>, D::Error> {
use serde::Deserialize;
// Rename "f32" to "f64"
let name = String::deserialize(deserializer)?;
let name = match name.as_str() {
"f32" => "f64".to_string(),
"graphene_core::graphic_element::GraphicGroup" => "graphene_core::graphic_element::Instances<graphene_core::graphic_element::GraphicGroup>".to_string(),
"graphene_core::vector::vector_data::VectorData" => "graphene_core::graphic_element::Instances<graphene_core::vector::vector_data::VectorData>".to_string(),
"graphene_core::raster::image::ImageFrame<Color>" => "graphene_core::graphic_element::Instances<graphene_core::raster::image::ImageFrame<Color>>".to_string(),
_ => name,
};
Ok(Cow::Owned(name))
}
@@ -150,9 +153,9 @@ pub struct TypeDescriptor {
pub name: Cow<'static, str>,
#[serde(default)]
pub alias: Option<Cow<'static, str>>,
#[serde(default)]
#[serde(skip)]
pub size: usize,
#[serde(default)]
#[serde(skip)]
pub align: usize,
}

View File

@@ -1,21 +1,20 @@
use super::HandleId;
use crate::transform::Footprint;
use crate::vector::{PointId, VectorData};
use crate::vector::{HandleId, PointId, VectorData, VectorDataTable};
use bezier_rs::Subpath;
use glam::DVec2;
trait CornerRadius {
fn generate(self, size: DVec2, clamped: bool) -> super::VectorData;
fn generate(self, size: DVec2, clamped: bool) -> VectorDataTable;
}
impl CornerRadius for f64 {
fn generate(self, size: DVec2, clamped: bool) -> super::VectorData {
fn generate(self, size: DVec2, clamped: bool) -> VectorDataTable {
let clamped_radius = if clamped { self.clamp(0., size.x.min(size.y).max(0.) / 2.) } else { self };
super::VectorData::from_subpath(Subpath::new_rounded_rect(size / -2., size / 2., [clamped_radius; 4]))
VectorDataTable::new(VectorData::from_subpath(Subpath::new_rounded_rect(size / -2., size / 2., [clamped_radius; 4])))
}
}
impl CornerRadius for [f64; 4] {
fn generate(self, size: DVec2, clamped: bool) -> super::VectorData {
fn generate(self, size: DVec2, clamped: bool) -> VectorDataTable {
let clamped_radius = if clamped {
// Algorithm follows the CSS spec: <https://drafts.csswg.org/css-backgrounds/#corner-overlap>
@@ -31,28 +30,31 @@ impl CornerRadius for [f64; 4] {
} else {
self
};
super::VectorData::from_subpath(Subpath::new_rounded_rect(size / -2., size / 2., clamped_radius))
VectorDataTable::new(VectorData::from_subpath(Subpath::new_rounded_rect(size / -2., size / 2., clamped_radius)))
}
}
#[node_macro::node(category("Vector: Shape"))]
fn circle<F: 'n + Send>(#[implementations((), Footprint)] _footprint: F, _primary: (), #[default(50.)] radius: f64) -> VectorData {
super::VectorData::from_subpath(Subpath::new_ellipse(DVec2::splat(-radius), DVec2::splat(radius)))
fn circle<F: 'n + Send>(#[implementations((), Footprint)] _footprint: F, _primary: (), #[default(50.)] radius: f64) -> VectorDataTable {
VectorDataTable::new(VectorData::from_subpath(Subpath::new_ellipse(DVec2::splat(-radius), DVec2::splat(radius))))
}
#[node_macro::node(category("Vector: Shape"))]
fn ellipse<F: 'n + Send>(#[implementations((), Footprint)] _footprint: F, _primary: (), #[default(50)] radius_x: f64, #[default(25)] radius_y: f64) -> VectorData {
fn ellipse<F: 'n + Send>(#[implementations((), Footprint)] _footprint: F, _primary: (), #[default(50)] radius_x: f64, #[default(25)] radius_y: f64) -> VectorDataTable {
let radius = DVec2::new(radius_x, radius_y);
let corner1 = -radius;
let corner2 = radius;
let mut ellipse = super::VectorData::from_subpath(Subpath::new_ellipse(corner1, corner2));
let mut ellipse = VectorData::from_subpath(Subpath::new_ellipse(corner1, corner2));
let len = ellipse.segment_domain.ids().len();
for i in 0..len {
ellipse
.colinear_manipulators
.push([HandleId::end(ellipse.segment_domain.ids()[i]), HandleId::primary(ellipse.segment_domain.ids()[(i + 1) % len])]);
}
ellipse
VectorDataTable::new(ellipse)
}
#[node_macro::node(category("Vector: Shape"), properties("rectangle_properties"))]
@@ -64,7 +66,7 @@ fn rectangle<F: 'n + Send, T: CornerRadius>(
_individual_corner_radii: bool, // TODO: Move this to the bottom once we have a migration capability
#[implementations(f64, [f64; 4])] corner_radius: T,
#[default(true)] clamped: bool,
) -> VectorData {
) -> VectorDataTable {
corner_radius.generate(DVec2::new(width, height), clamped)
}
@@ -76,10 +78,10 @@ fn regular_polygon<F: 'n + Send>(
#[min(3.)]
sides: u32,
#[default(50)] radius: f64,
) -> VectorData {
) -> VectorDataTable {
let points = sides.into();
let radius: f64 = radius * 2.;
super::VectorData::from_subpath(Subpath::new_regular_polygon(DVec2::splat(-radius), points, radius))
VectorDataTable::new(VectorData::from_subpath(Subpath::new_regular_polygon(DVec2::splat(-radius), points, radius)))
}
#[node_macro::node(category("Vector: Shape"))]
@@ -91,35 +93,39 @@ fn star<F: 'n + Send>(
sides: u32,
#[default(50)] radius: f64,
#[default(25)] inner_radius: f64,
) -> VectorData {
) -> VectorDataTable {
let points = sides.into();
let diameter: f64 = radius * 2.;
let inner_diameter = inner_radius * 2.;
super::VectorData::from_subpath(Subpath::new_star_polygon(DVec2::splat(-diameter), points, diameter, inner_diameter))
VectorDataTable::new(VectorData::from_subpath(Subpath::new_star_polygon(DVec2::splat(-diameter), points, diameter, inner_diameter)))
}
#[node_macro::node(category("Vector: Shape"))]
fn line<F: 'n + Send>(#[implementations((), Footprint)] _footprint: F, _primary: (), #[default((0., -50.))] start: DVec2, #[default((0., 50.))] end: DVec2) -> VectorData {
super::VectorData::from_subpath(Subpath::new_line(start, end))
fn line<F: 'n + Send>(#[implementations((), Footprint)] _footprint: F, _primary: (), #[default((0., -50.))] start: DVec2, #[default((0., 50.))] end: DVec2) -> VectorDataTable {
VectorDataTable::new(VectorData::from_subpath(Subpath::new_line(start, end)))
}
#[node_macro::node(category("Vector: Shape"))]
fn spline<F: 'n + Send>(#[implementations((), Footprint)] _footprint: F, _primary: (), points: Vec<DVec2>) -> VectorData {
let mut spline = super::VectorData::from_subpath(Subpath::new_cubic_spline(points));
fn spline<F: 'n + Send>(#[implementations((), Footprint)] _footprint: F, _primary: (), points: Vec<DVec2>) -> VectorDataTable {
let mut spline = VectorData::from_subpath(Subpath::new_cubic_spline(points));
for pair in spline.segment_domain.ids().windows(2) {
spline.colinear_manipulators.push([HandleId::end(pair[0]), HandleId::primary(pair[1])]);
}
spline
VectorDataTable::new(spline)
}
// TODO(TrueDoctor): I removed the Arc requirement we should think about when it makes sense to use it vs making a generic value node
#[node_macro::node(category(""))]
fn path<F: 'n + Send>(#[implementations((), Footprint)] _footprint: F, path_data: Vec<Subpath<PointId>>, colinear_manipulators: Vec<PointId>) -> super::VectorData {
let mut vector_data = super::VectorData::from_subpaths(path_data, false);
fn path<F: 'n + Send>(#[implementations((), Footprint)] _footprint: F, path_data: Vec<Subpath<PointId>>, colinear_manipulators: Vec<PointId>) -> VectorDataTable {
let mut vector_data = VectorData::from_subpaths(path_data, false);
vector_data.colinear_manipulators = colinear_manipulators
.iter()
.filter_map(|&point| super::ManipulatorPointId::Anchor(point).get_handle_pair(&vector_data))
.collect();
vector_data
VectorDataTable::new(vector_data)
}

View File

@@ -4,7 +4,8 @@ pub use attributes::*;
pub use modification::*;
use super::style::{PathStyle, Stroke};
use crate::{AlphaBlending, Color};
use crate::instances::Instances;
use crate::{AlphaBlending, Color, GraphicGroupTable};
use bezier_rs::ManipulatorGroup;
use dyn_any::DynAny;
@@ -12,6 +13,26 @@ use dyn_any::DynAny;
use core::borrow::Borrow;
use glam::{DAffine2, DVec2};
// TODO: Eventually remove this migration document upgrade code
pub fn migrate_vector_data<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<VectorDataTable, D::Error> {
use serde::Deserialize;
#[derive(serde::Serialize, serde::Deserialize)]
#[serde(untagged)]
#[allow(clippy::large_enum_variant)]
enum EitherFormat {
VectorData(VectorData),
VectorDataTable(VectorDataTable),
}
Ok(match EitherFormat::deserialize(deserializer)? {
EitherFormat::VectorData(vector_data) => VectorDataTable::new(vector_data),
EitherFormat::VectorDataTable(vector_data_table) => vector_data_table,
})
}
pub type VectorDataTable = Instances<VectorData>;
/// [VectorData] is passed between nodes.
/// It contains a list of subpaths (that may be open or closed), a transform, and some style information.
#[derive(Clone, Debug, PartialEq, DynAny)]
@@ -29,7 +50,7 @@ pub struct VectorData {
pub region_domain: RegionDomain,
// Used to store the upstream graphic group during destructive Boolean Operations (and other nodes with a similar effect) so that click targets can be preserved.
pub upstream_graphic_group: Option<crate::GraphicGroup>,
pub upstream_graphic_group: Option<GraphicGroupTable>,
}
impl core::hash::Hash for VectorData {

View File

@@ -1,4 +1,5 @@
use super::HandleId;
use crate::vector::vector_data::{HandleId, VectorData, VectorDataTable};
use crate::vector::ConcatElement;
use dyn_any::DynAny;
@@ -46,7 +47,7 @@ macro_rules! create_ids {
};
}
create_ids! { PointId, SegmentId, RegionId, StrokeId, FillId }
create_ids! { InstanceId, PointId, SegmentId, RegionId, StrokeId, FillId }
/// A no-op hasher that allows writing u64s (the id type).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
@@ -503,7 +504,7 @@ impl RegionDomain {
}
}
impl super::VectorData {
impl VectorData {
/// Construct a [`bezier_rs::Bezier`] curve spanning from the resolved position of the start and end points with the specified handles.
fn segment_to_bezier_with_index(&self, start: usize, end: usize, handles: bezier_rs::BezierHandles) -> bezier_rs::Bezier {
let start = self.point_domain.positions()[start];
@@ -698,7 +699,7 @@ impl StrokePathIterPointMetadata {
#[derive(Clone)]
pub struct StrokePathIter<'a> {
vector_data: &'a super::VectorData,
vector_data: &'a VectorData,
points: Vec<StrokePathIterPointMetadata>,
skip: usize,
done_one: bool,
@@ -774,7 +775,7 @@ impl bezier_rs::Identifier for PointId {
}
}
impl crate::vector::ConcatElement for super::VectorData {
impl ConcatElement for VectorData {
fn concat(&mut self, other: &Self, transform: glam::DAffine2, node_id: u64) {
let new_ids = other
.point_domain
@@ -813,6 +814,14 @@ impl crate::vector::ConcatElement for super::VectorData {
}
}
impl ConcatElement for VectorDataTable {
fn concat(&mut self, other: &Self, transform: glam::DAffine2, node_id: u64) {
for (instance, other_instance) in self.instances_mut().zip(other.instances()) {
instance.concat(other_instance, transform, node_id);
}
}
}
/// Represents the conversion of ids used when concatenating vector data with conflicting ids.
struct IdMap {
point_offset: usize,

View File

@@ -1,4 +1,5 @@
use super::*;
use crate::transform::Footprint;
use crate::uuid::generate_uuid;
use bezier_rs::BezierHandles;
@@ -421,7 +422,6 @@ impl core::hash::Hash for VectorModification {
}
}
use crate::transform::Footprint;
/// A node that applies a procedural modification to some [`VectorData`].
#[node_macro::node(category(""))]
async fn path_modify<F: 'n + Send + Sync + Clone>(
@@ -431,15 +431,18 @@ async fn path_modify<F: 'n + Send + Sync + Clone>(
)]
input: F,
#[implementations(
() -> VectorData,
Footprint -> VectorData,
() -> VectorDataTable,
Footprint -> VectorDataTable,
)]
vector_data: impl Node<F, Output = VectorData>,
vector_data: impl Node<F, Output = VectorDataTable>,
modification: Box<VectorModification>,
) -> VectorData {
) -> VectorDataTable {
let mut vector_data = vector_data.eval(input).await;
modification.apply(&mut vector_data);
vector_data
let vector_data = vector_data.one_item_mut();
modification.apply(vector_data);
VectorDataTable::new(vector_data.clone())
}
#[test]

View File

@@ -1,12 +1,12 @@
use super::misc::CentroidType;
use super::style::{Fill, Gradient, GradientStops, Stroke};
use super::{PointId, SegmentDomain, SegmentId, StrokeId, VectorData};
use super::{PointId, SegmentDomain, SegmentId, StrokeId, VectorData, VectorDataTable};
use crate::registry::types::{Angle, Fraction, IntegerCount, Length, SeedValue};
use crate::renderer::GraphicElementRendered;
use crate::transform::{Footprint, Transform, TransformMut};
use crate::vector::style::LineJoin;
use crate::vector::PointDomain;
use crate::{Color, GraphicElement, GraphicGroup};
use crate::{Color, GraphicElement, GraphicGroup, GraphicGroupTable};
use bezier_rs::{Cap, Join, Subpath, SubpathTValue, TValue};
use glam::{DAffine2, DVec2};
@@ -18,21 +18,27 @@ trait VectorIterMut {
fn vector_iter_mut(&mut self) -> impl Iterator<Item = (&mut VectorData, DAffine2)>;
}
impl VectorIterMut for GraphicGroup {
impl VectorIterMut for GraphicGroupTable {
fn vector_iter_mut(&mut self) -> impl Iterator<Item = (&mut VectorData, DAffine2)> {
let parent_transform = self.transform;
// Grab only the direct children (perhaps unintuitive?)
self.iter_mut().filter_map(|(element, _)| element.as_vector_data_mut()).map(move |vector| {
let transform = parent_transform * vector.transform;
(vector, transform)
let instance = self.one_item_mut();
let parent_transform = instance.transform;
// Grab only the direct children
instance.iter_mut().filter_map(|(element, _)| element.as_vector_data_mut()).map(move |vector_data| {
let vector_data = vector_data.one_item_mut();
let transform = parent_transform * vector_data.transform;
(vector_data, transform)
})
}
}
impl VectorIterMut for VectorData {
impl VectorIterMut for VectorDataTable {
fn vector_iter_mut(&mut self) -> impl Iterator<Item = (&mut VectorData, DAffine2)> {
let transform = self.transform;
std::iter::once((self, transform))
self.instances_mut().map(|instance| {
let transform = instance.transform;
(instance, transform)
})
}
}
@@ -45,10 +51,10 @@ async fn assign_colors<F: 'n + Send, T: VectorIterMut>(
)]
footprint: F,
#[implementations(
() -> GraphicGroup,
() -> VectorData,
Footprint -> GraphicGroup,
Footprint -> VectorData,
() -> GraphicGroupTable,
() -> VectorDataTable,
Footprint -> GraphicGroupTable,
Footprint -> VectorDataTable,
)]
#[widget(ParsedWidgetOverride::Hidden)]
vector_group: impl Node<F, Output = T>,
@@ -60,13 +66,14 @@ async fn assign_colors<F: 'n + Send, T: VectorIterMut>(
#[widget(ParsedWidgetOverride::Custom = "assign_colors_seed")] seed: SeedValue,
#[widget(ParsedWidgetOverride::Custom = "assign_colors_repeat_every")] repeat_every: u32,
) -> T {
let mut input = vector_group.eval(footprint).await;
let length = input.vector_iter_mut().count();
let mut vector_group = vector_group.eval(footprint).await;
let length = vector_group.vector_iter_mut().count();
let gradient = if reverse { gradient.reversed() } else { gradient };
let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into());
for (i, (vector_data, _)) in input.vector_iter_mut().enumerate() {
for (i, (vector_data, _)) in vector_group.vector_iter_mut().enumerate() {
let factor = match randomize {
true => rng.gen::<f64>(),
false => match repeat_every {
@@ -87,7 +94,8 @@ async fn assign_colors<F: 'n + Send, T: VectorIterMut>(
}
}
}
input
vector_group
}
#[node_macro::node(category("Vector: Style"), path(graphene_core::vector), properties("fill_properties"))]
@@ -112,22 +120,22 @@ async fn fill<F: 'n + Send, FillTy: Into<Fill> + 'n + Send, TargetTy: VectorIter
)]
footprint: F,
#[implementations(
() -> VectorData,
() -> VectorData,
() -> VectorData,
() -> VectorData,
() -> GraphicGroup,
() -> GraphicGroup,
() -> GraphicGroup,
() -> GraphicGroup,
Footprint -> VectorData,
Footprint -> VectorData,
Footprint -> VectorData,
Footprint -> VectorData,
Footprint -> GraphicGroup,
Footprint -> GraphicGroup,
Footprint -> GraphicGroup,
Footprint -> GraphicGroup,
() -> VectorDataTable,
() -> VectorDataTable,
() -> VectorDataTable,
() -> VectorDataTable,
() -> GraphicGroupTable,
() -> GraphicGroupTable,
() -> GraphicGroupTable,
() -> GraphicGroupTable,
Footprint -> VectorDataTable,
Footprint -> VectorDataTable,
Footprint -> VectorDataTable,
Footprint -> VectorDataTable,
Footprint -> GraphicGroupTable,
Footprint -> GraphicGroupTable,
Footprint -> GraphicGroupTable,
Footprint -> GraphicGroupTable,
)]
vector_data: impl Node<F, Output = TargetTy>,
#[implementations(
@@ -176,14 +184,14 @@ async fn stroke<F: 'n + Send, ColorTy: Into<Option<Color>> + 'n + Send, TargetTy
)]
footprint: F,
#[implementations(
() -> VectorData,
() -> VectorData,
() -> GraphicGroup,
() -> GraphicGroup,
Footprint -> VectorData,
Footprint -> VectorData,
Footprint -> GraphicGroup,
Footprint -> GraphicGroup,
() -> VectorDataTable,
() -> VectorDataTable,
() -> GraphicGroupTable,
() -> GraphicGroupTable,
Footprint -> VectorDataTable,
Footprint -> VectorDataTable,
Footprint -> GraphicGroupTable,
Footprint -> GraphicGroupTable,
)]
vector_data: impl Node<F, Output = TargetTy>,
#[implementations(
@@ -234,10 +242,10 @@ async fn repeat<F: 'n + Send + Copy, I: 'n + GraphicElementRendered + Transform
footprint: F,
// TODO: Implement other GraphicElementRendered types.
#[implementations(
() -> VectorData,
() -> GraphicGroup,
Footprint -> VectorData,
Footprint -> GraphicGroup,
() -> VectorDataTable,
() -> GraphicGroupTable,
Footprint -> VectorDataTable,
Footprint -> GraphicGroupTable,
)]
instance: impl Node<F, Output = I>,
#[default(100., 100.)]
@@ -245,7 +253,7 @@ async fn repeat<F: 'n + Send + Copy, I: 'n + GraphicElementRendered + Transform
direction: DVec2,
angle: Angle,
#[default(4)] instances: IntegerCount,
) -> GraphicGroup {
) -> GraphicGroupTable {
let instance = instance.eval(footprint).await;
let first_vector_transform = instance.transform();
@@ -253,10 +261,10 @@ async fn repeat<F: 'n + Send + Copy, I: 'n + GraphicElementRendered + Transform
let instances = instances.max(1);
let total = (instances - 1) as f64;
let mut result = GraphicGroup::EMPTY;
let mut result = GraphicGroup::default();
let Some(bounding_box) = instance.bounding_box(DAffine2::IDENTITY) else {
return result;
return GraphicGroupTable::new(result);
};
let center = (bounding_box[0] + bounding_box[1]) / 2.;
@@ -273,7 +281,7 @@ async fn repeat<F: 'n + Send + Copy, I: 'n + GraphicElementRendered + Transform
result.push((new_instance, None));
}
result
GraphicGroupTable::new(result)
}
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
@@ -287,24 +295,24 @@ async fn circular_repeat<F: 'n + Send + Copy, I: 'n + GraphicElementRendered + T
footprint: F,
// TODO: Implement other GraphicElementRendered types.
#[implementations(
() -> VectorData,
() -> GraphicGroup,
Footprint -> VectorData,
Footprint -> GraphicGroup,
() -> VectorDataTable,
() -> GraphicGroupTable,
Footprint -> VectorDataTable,
Footprint -> GraphicGroupTable,
)]
instance: impl Node<F, Output = I>,
angle_offset: Angle,
#[default(5)] radius: f64,
#[default(5)] instances: IntegerCount,
) -> GraphicGroup {
) -> GraphicGroupTable {
let instance = instance.eval(footprint).await;
let first_vector_transform = instance.transform();
let instances = instances.max(1);
let mut result = GraphicGroup::EMPTY;
let mut result = GraphicGroup::default();
let Some(bounding_box) = instance.bounding_box(DAffine2::IDENTITY) else {
return result;
return GraphicGroupTable::new(result);
};
let center = (bounding_box[0] + bounding_box[1]) / 2.;
@@ -322,7 +330,7 @@ async fn circular_repeat<F: 'n + Send + Copy, I: 'n + GraphicElementRendered + T
result.push((new_instance, None));
}
result
GraphicGroupTable::new(result)
}
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
@@ -334,17 +342,17 @@ async fn copy_to_points<F: 'n + Send + Copy, I: GraphicElementRendered + ConcatE
)]
footprint: F,
#[implementations(
() -> VectorData,
() -> VectorData,
Footprint -> VectorData,
() -> VectorDataTable,
() -> VectorDataTable,
Footprint -> VectorDataTable,
)]
points: impl Node<F, Output = VectorData>,
points: impl Node<F, Output = VectorDataTable>,
#[expose]
#[implementations(
() -> VectorData,
() -> GraphicGroup,
Footprint -> VectorData,
Footprint -> GraphicGroup,
() -> VectorDataTable,
() -> GraphicGroupTable,
Footprint -> VectorDataTable,
Footprint -> GraphicGroupTable,
)]
instance: impl Node<F, Output = I>,
#[default(1)] random_scale_min: f64,
@@ -353,9 +361,12 @@ async fn copy_to_points<F: 'n + Send + Copy, I: GraphicElementRendered + ConcatE
random_scale_seed: SeedValue,
random_rotation: Angle,
random_rotation_seed: SeedValue,
) -> GraphicGroup {
) -> GraphicGroupTable {
let points = points.eval(footprint).await;
let points = points.one_item();
let instance = instance.eval(footprint).await;
let instance_transform = instance.transform();
let random_scale_difference = random_scale_max - random_scale_min;
@@ -406,7 +417,7 @@ async fn copy_to_points<F: 'n + Send + Copy, I: GraphicElementRendered + ConcatE
result.push((new_instance, None));
}
result
GraphicGroupTable::new(result)
}
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
@@ -417,18 +428,20 @@ async fn bounding_box<F: 'n + Send>(
)]
footprint: F,
#[implementations(
() -> VectorData,
Footprint -> VectorData,
() -> VectorDataTable,
Footprint -> VectorDataTable,
)]
vector_data: impl Node<F, Output = VectorData>,
) -> VectorData {
vector_data: impl Node<F, Output = VectorDataTable>,
) -> VectorDataTable {
let vector_data = vector_data.eval(footprint).await;
let vector_data = vector_data.one_item();
let bounding_box = vector_data.bounding_box_with_transform(vector_data.transform).unwrap();
let mut result = VectorData::from_subpath(Subpath::new_rect(bounding_box[0], bounding_box[1]));
result.style = vector_data.style.clone();
result.style.set_stroke_transform(DAffine2::IDENTITY);
result
VectorDataTable::new(result)
}
#[node_macro::node(category("Vector"), path(graphene_core::vector), properties("offset_path_properties"))]
@@ -439,23 +452,23 @@ async fn offset_path<F: 'n + Send>(
)]
footprint: F,
#[implementations(
() -> VectorData,
Footprint -> VectorData,
() -> VectorDataTable,
Footprint -> VectorDataTable,
)]
vector_data: impl Node<F, Output = VectorData>,
vector_data: impl Node<F, Output = VectorDataTable>,
distance: f64,
line_join: LineJoin,
#[default(4.)] miter_limit: f64,
) -> VectorData {
) -> VectorDataTable {
let vector_data = vector_data.eval(footprint).await;
let vector_data = vector_data.one_item();
let subpaths = vector_data.stroke_bezier_paths();
let mut result = VectorData::empty();
result.style = vector_data.style.clone();
result.style.set_stroke_transform(DAffine2::IDENTITY);
// Perform operation on all subpaths in this shape.
for mut subpath in subpaths {
for mut subpath in vector_data.stroke_bezier_paths() {
subpath.apply_transform(vector_data.transform);
// Taking the existing stroke data and passing it to Bezier-rs to generate new paths.
@@ -472,7 +485,7 @@ async fn offset_path<F: 'n + Send>(
result.append_subpath(subpath_out, false);
}
result
VectorDataTable::new(result)
}
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
@@ -483,14 +496,17 @@ async fn solidify_stroke<F: 'n + Send>(
)]
footprint: F,
#[implementations(
() -> VectorData,
Footprint -> VectorData,
() -> VectorDataTable,
Footprint -> VectorDataTable,
)]
vector_data: impl Node<F, Output = VectorData>,
) -> VectorData {
vector_data: impl Node<F, Output = VectorDataTable>,
) -> VectorDataTable {
let vector_data = vector_data.eval(footprint).await;
let vector_data = vector_data.one_item();
let transform = &vector_data.transform;
let style = &vector_data.style;
let VectorData { transform, style, .. } = &vector_data;
let subpaths = vector_data.stroke_bezier_paths();
let mut result = VectorData::empty();
@@ -531,7 +547,7 @@ async fn solidify_stroke<F: 'n + Send>(
result.style.set_stroke(Stroke::default());
}
result
VectorDataTable::new(result)
}
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
@@ -542,12 +558,14 @@ async fn flatten_vector_elements<F: 'n + Send>(
)]
footprint: F,
#[implementations(
() -> GraphicGroup,
Footprint -> GraphicGroup,
() -> GraphicGroupTable,
Footprint -> GraphicGroupTable,
)]
graphic_group_input: impl Node<F, Output = GraphicGroup>,
) -> VectorData {
graphic_group_input: impl Node<F, Output = GraphicGroupTable>,
) -> VectorDataTable {
let graphic_group = graphic_group_input.eval(footprint).await;
let graphic_group = graphic_group.one_item();
// A node based solution to support passing through vector data could be a network node with a cache node connected to
// a flatten vector elements connected to an if else node, another connection from the cache directly
// To the if else node, and another connection from the cache to a matches type node connected to the if else node.
@@ -555,9 +573,12 @@ async fn flatten_vector_elements<F: 'n + Send>(
for (element, reference) in graphic_group.iter() {
match element {
GraphicElement::VectorData(vector_data) => {
result.concat(vector_data, current_transform, reference.map(|node_id| node_id.0).unwrap_or_default());
for instance in vector_data.instances() {
result.concat(instance, current_transform, reference.map(|node_id| node_id.0).unwrap_or_default());
}
}
GraphicElement::GraphicGroup(graphic_group) => {
let graphic_group = graphic_group.one_item();
concat_group(graphic_group, current_transform * graphic_group.transform, result);
}
_ => {}
@@ -566,25 +587,29 @@ async fn flatten_vector_elements<F: 'n + Send>(
}
let mut result = VectorData::empty();
concat_group(&graphic_group, DAffine2::IDENTITY, &mut result);
concat_group(graphic_group, DAffine2::IDENTITY, &mut result);
// TODO: This leads to incorrect stroke widths when flattening groups with different transforms.
result.style.set_stroke_transform(DAffine2::IDENTITY);
result
VectorDataTable::new(result)
}
pub trait ConcatElement {
fn concat(&mut self, other: &Self, transform: DAffine2, node_id: u64);
}
impl ConcatElement for GraphicGroup {
impl ConcatElement for GraphicGroupTable {
fn concat(&mut self, other: &Self, transform: DAffine2, _node_id: u64) {
let own = self.one_item_mut();
let other = other.one_item();
// TODO: Decide if we want to keep this behavior whereby the layers are flattened
for (mut element, footprint_mapping) in other.iter().cloned() {
*element.transform_mut() = transform * element.transform() * other.transform();
self.push((element, footprint_mapping));
own.push((element, footprint_mapping));
}
self.alpha_blending = other.alpha_blending;
own.alpha_blending = other.alpha_blending;
}
}
@@ -596,10 +621,10 @@ async fn sample_points<F: 'n + Send + Copy>(
)]
footprint: F,
#[implementations(
() -> VectorData,
Footprint -> VectorData,
() -> VectorDataTable,
Footprint -> VectorDataTable,
)]
vector_data: impl Node<F, Output = VectorData>,
vector_data: impl Node<F, Output = VectorDataTable>,
spacing: f64,
start_offset: f64,
stop_offset: f64,
@@ -609,9 +634,10 @@ async fn sample_points<F: 'n + Send + Copy>(
Footprint -> Vec<f64>,
)]
subpath_segment_lengths: impl Node<F, Output = Vec<f64>>,
) -> VectorData {
) -> VectorDataTable {
// Evaluate vector data and subpath segment lengths asynchronously.
let vector_data = vector_data.eval(footprint).await;
let vector_data = vector_data.one_item();
let subpath_segment_lengths = subpath_segment_lengths.eval(footprint).await;
// Create an iterator over the bezier segments with enumeration and peeking capability.
@@ -753,7 +779,7 @@ async fn sample_points<F: 'n + Send + Copy>(
result.style.set_stroke_transform(vector_data.transform);
// Return the resulting vector data with newly generated points and segments.
result
VectorDataTable::new(result)
}
#[node_macro::node(category(""), path(graphene_core::vector))]
@@ -764,22 +790,23 @@ async fn poisson_disk_points<F: 'n + Send>(
)]
footprint: F,
#[implementations(
() -> VectorData,
Footprint -> VectorData,
() -> VectorDataTable,
Footprint -> VectorDataTable,
)]
vector_data: impl Node<F, Output = VectorData>,
vector_data: impl Node<F, Output = VectorDataTable>,
#[default(10.)]
#[min(0.01)]
separation_disk_diameter: f64,
seed: SeedValue,
) -> VectorData {
) -> VectorDataTable {
let vector_data = vector_data.eval(footprint).await;
let vector_data = vector_data.one_item();
let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into());
let mut result = VectorData::empty();
if separation_disk_diameter <= 0.01 {
return result;
return VectorDataTable::new(result);
}
for mut subpath in vector_data.stroke_bezier_paths() {
@@ -812,7 +839,7 @@ async fn poisson_disk_points<F: 'n + Send>(
result.style = vector_data.style.clone();
result.style.set_stroke_transform(DAffine2::IDENTITY);
result
VectorDataTable::new(result)
}
#[node_macro::node(category(""), path(graphene_core::vector))]
@@ -823,12 +850,13 @@ async fn subpath_segment_lengths<F: 'n + Send>(
)]
footprint: F,
#[implementations(
() -> VectorData,
Footprint -> VectorData,
() -> VectorDataTable,
Footprint -> VectorDataTable,
)]
vector_data: impl Node<F, Output = VectorData>,
vector_data: impl Node<F, Output = VectorDataTable>,
) -> Vec<f64> {
let vector_data = vector_data.eval(footprint).await;
let vector_data = vector_data.one_item();
vector_data
.segment_bezier_iter()
@@ -844,17 +872,18 @@ async fn splines_from_points<F: 'n + Send>(
)]
footprint: F,
#[implementations(
() -> VectorData,
Footprint -> VectorData,
() -> VectorDataTable,
Footprint -> VectorDataTable,
)]
vector_data: impl Node<F, Output = VectorData>,
) -> VectorData {
vector_data: impl Node<F, Output = VectorDataTable>,
) -> VectorDataTable {
// Evaluate the vector data within the given footprint.
let mut vector_data = vector_data.eval(footprint).await;
let vector_data = vector_data.one_item_mut();
// Exit early if there are no points to generate splines from.
if vector_data.point_domain.positions().is_empty() {
return vector_data;
return VectorDataTable::new(vector_data.clone());
}
let mut segment_domain = SegmentDomain::default();
@@ -887,7 +916,7 @@ async fn splines_from_points<F: 'n + Send>(
}
vector_data.segment_domain = segment_domain;
vector_data
VectorDataTable::new(vector_data.clone())
}
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
@@ -898,14 +927,15 @@ async fn jitter_points<F: 'n + Send>(
)]
footprint: F,
#[implementations(
() -> VectorData,
Footprint -> VectorData,
() -> VectorDataTable,
Footprint -> VectorDataTable,
)]
vector_data: impl Node<F, Output = VectorData>,
vector_data: impl Node<F, Output = VectorDataTable>,
#[default(5.)] amount: f64,
seed: SeedValue,
) -> VectorData {
let mut vector_data = vector_data.eval(footprint).await;
) -> VectorDataTable {
let vector_data = vector_data.eval(footprint).await;
let mut vector_data = vector_data.one_item().clone();
let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into());
@@ -949,7 +979,7 @@ async fn jitter_points<F: 'n + Send>(
vector_data.transform = DAffine2::IDENTITY;
vector_data.style.set_stroke_transform(DAffine2::IDENTITY);
vector_data
VectorDataTable::new(vector_data)
}
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
@@ -960,23 +990,26 @@ async fn morph<F: 'n + Send + Copy>(
)]
footprint: F,
#[implementations(
() -> VectorData,
Footprint -> VectorData,
() -> VectorDataTable,
Footprint -> VectorDataTable,
)]
source: impl Node<F, Output = VectorData>,
source: impl Node<F, Output = VectorDataTable>,
#[expose]
#[implementations(
() -> VectorData,
Footprint -> VectorData,
() -> VectorDataTable,
Footprint -> VectorDataTable,
)]
target: impl Node<F, Output = VectorData>,
target: impl Node<F, Output = VectorDataTable>,
#[range((0., 1.))]
#[default(0.5)]
time: Fraction,
#[min(0.)] start_index: IntegerCount,
) -> VectorData {
) -> VectorDataTable {
let source = source.eval(footprint).await;
let source = source.one_item();
let target = target.eval(footprint).await;
let target = target.one_item();
let mut result = VectorData::empty();
let time = time.clamp(0., 1.);
@@ -1055,7 +1088,7 @@ async fn morph<F: 'n + Send + Copy>(
}
}
result
VectorDataTable::new(result)
}
fn bevel_algorithm(mut vector_data: VectorData, distance: f64) -> VectorData {
@@ -1173,18 +1206,24 @@ async fn bevel<F: 'n + Send + Copy>(
)]
footprint: F,
#[implementations(
() -> VectorData,
Footprint -> VectorData,
() -> VectorDataTable,
Footprint -> VectorDataTable,
)]
source: impl Node<F, Output = VectorData>,
source: impl Node<F, Output = VectorDataTable>,
#[default(10.)] distance: Length,
) -> VectorData {
bevel_algorithm(source.eval(footprint).await, distance)
) -> VectorDataTable {
let source = source.eval(footprint).await;
let source = source.one_item();
let result = bevel_algorithm(source.clone(), distance);
VectorDataTable::new(result)
}
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
async fn area(_: (), vector_data: impl Node<Footprint, Output = VectorData>) -> f64 {
async fn area(_: (), vector_data: impl Node<Footprint, Output = VectorDataTable>) -> f64 {
let vector_data = vector_data.eval(Footprint::default()).await;
let vector_data = vector_data.one_item();
let mut area = 0.;
let scale = vector_data.transform.decompose_scale();
@@ -1195,8 +1234,9 @@ async fn area(_: (), vector_data: impl Node<Footprint, Output = VectorData>) ->
}
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
async fn centroid(_: (), vector_data: impl Node<Footprint, Output = VectorData>, centroid_type: CentroidType) -> DVec2 {
async fn centroid(_: (), vector_data: impl Node<Footprint, Output = VectorDataTable>, centroid_type: CentroidType) -> DVec2 {
let vector_data = vector_data.eval(Footprint::default()).await;
let vector_data = vector_data.one_item();
if centroid_type == CentroidType::Area {
let mut area = 0.;
@@ -1260,8 +1300,8 @@ mod test {
}
}
fn vector_node(data: Subpath<PointId>) -> FutureWrapperNode<VectorData> {
FutureWrapperNode(VectorData::from_subpath(data))
fn vector_node(data: Subpath<PointId>) -> FutureWrapperNode<VectorDataTable> {
FutureWrapperNode(VectorDataTable::new(VectorData::from_subpath(data)))
}
#[tokio::test]
@@ -1270,6 +1310,7 @@ mod test {
let instances = 3;
let repeated = super::repeat(Footprint::default(), &vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE)), direction, 0., instances).await;
let vector_data = super::flatten_vector_elements(Footprint::default(), &FutureWrapperNode(repeated)).await;
let vector_data = vector_data.one_item();
assert_eq!(vector_data.region_bezier_paths().count(), 3);
for (index, (_, subpath)) in vector_data.region_bezier_paths().enumerate() {
assert!((subpath.manipulator_groups()[0].anchor - direction * index as f64 / (instances - 1) as f64).length() < 1e-5);
@@ -1281,6 +1322,7 @@ mod test {
let instances = 8;
let repeated = super::repeat(Footprint::default(), &vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE)), direction, 0., instances).await;
let vector_data = super::flatten_vector_elements(Footprint::default(), &FutureWrapperNode(repeated)).await;
let vector_data = vector_data.one_item();
assert_eq!(vector_data.region_bezier_paths().count(), 8);
for (index, (_, subpath)) in vector_data.region_bezier_paths().enumerate() {
assert!((subpath.manipulator_groups()[0].anchor - direction * index as f64 / (instances - 1) as f64).length() < 1e-5);
@@ -1290,6 +1332,7 @@ mod test {
async fn circle_repeat() {
let repeated = super::circular_repeat(Footprint::default(), &vector_node(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE)), 45., 4., 8).await;
let vector_data = super::flatten_vector_elements(Footprint::default(), &FutureWrapperNode(repeated)).await;
let vector_data = vector_data.one_item();
assert_eq!(vector_data.region_bezier_paths().count(), 8);
for (index, (_, subpath)) in vector_data.region_bezier_paths().enumerate() {
let expected_angle = (index as f64 + 1.) * 45.;
@@ -1304,18 +1347,20 @@ mod test {
vector_data: vector_node(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE)),
};
let bounding_box = bounding_box.eval(Footprint::default()).await;
let bounding_box = bounding_box.one_item();
assert_eq!(bounding_box.region_bezier_paths().count(), 1);
let subpath = bounding_box.region_bezier_paths().next().unwrap().1;
assert_eq!(&subpath.anchors()[..4], &[DVec2::NEG_ONE, DVec2::new(1., -1.), DVec2::ONE, DVec2::new(-1., 1.),]);
// test a VectorData with non-zero rotation
// Test a VectorData with non-zero rotation
let mut square = VectorData::from_subpath(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE));
square.transform *= DAffine2::from_angle(core::f64::consts::FRAC_PI_4);
let bounding_box = BoundingBoxNode {
vector_data: FutureWrapperNode(square),
vector_data: FutureWrapperNode(VectorDataTable::new(square)),
}
.eval(Footprint::default())
.await;
let bounding_box = bounding_box.one_item();
assert_eq!(bounding_box.region_bezier_paths().count(), 1);
let subpath = bounding_box.region_bezier_paths().next().unwrap().1;
let sqrt2 = core::f64::consts::SQRT_2;
@@ -1329,6 +1374,7 @@ mod test {
let expected_points = VectorData::from_subpath(points.clone()).point_domain.positions().to_vec();
let copy_to_points = super::copy_to_points(Footprint::default(), &vector_node(points), &vector_node(instance), 1., 1., 0., 0, 0., 0).await;
let flattened_copy_to_points = super::flatten_vector_elements(Footprint::default(), &FutureWrapperNode(copy_to_points)).await;
let flattened_copy_to_points = flattened_copy_to_points.one_item();
assert_eq!(flattened_copy_to_points.region_bezier_paths().count(), expected_points.len());
for (index, (_, subpath)) in flattened_copy_to_points.region_bezier_paths().enumerate() {
let offset = expected_points[index];
@@ -1342,6 +1388,7 @@ mod test {
async fn sample_points() {
let path = Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.));
let sample_points = super::sample_points(Footprint::default(), &vector_node(path), 30., 0., 0., false, &FutureWrapperNode(vec![100.])).await;
let sample_points = sample_points.one_item();
assert_eq!(sample_points.point_domain.positions().len(), 4);
for (pos, expected) in sample_points.point_domain.positions().iter().zip([DVec2::X * 0., DVec2::X * 30., DVec2::X * 60., DVec2::X * 90.]) {
assert!(pos.distance(expected) < 1e-3, "Expected {expected} found {pos}");
@@ -1351,6 +1398,7 @@ mod test {
async fn adaptive_spacing() {
let path = Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.));
let sample_points = super::sample_points(Footprint::default(), &vector_node(path), 18., 45., 10., true, &FutureWrapperNode(vec![100.])).await;
let sample_points = sample_points.one_item();
assert_eq!(sample_points.point_domain.positions().len(), 4);
for (pos, expected) in sample_points.point_domain.positions().iter().zip([DVec2::X * 45., DVec2::X * 60., DVec2::X * 75., DVec2::X * 90.]) {
assert!(pos.distance(expected) < 1e-3, "Expected {expected} found {pos}");
@@ -1365,6 +1413,7 @@ mod test {
0,
)
.await;
let sample_points = sample_points.one_item();
assert!(
(20..=40).contains(&sample_points.point_domain.positions().len()),
"actual len {}",
@@ -1383,6 +1432,7 @@ mod test {
#[tokio::test]
async fn spline() {
let spline = splines_from_points(Footprint::default(), &vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.))).await;
let spline = spline.one_item();
assert_eq!(spline.stroke_bezier_paths().count(), 1);
assert_eq!(spline.point_domain.positions(), &[DVec2::ZERO, DVec2::new(100., 0.), DVec2::new(100., 100.), DVec2::new(0., 100.)]);
}
@@ -1391,6 +1441,7 @@ mod test {
let source = Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.);
let target = Subpath::new_ellipse(DVec2::NEG_ONE * 100., DVec2::ZERO);
let sample_points = super::morph(Footprint::default(), &vector_node(source), &vector_node(target), 0.5, 0).await;
let sample_points = sample_points.one_item();
assert_eq!(
&sample_points.point_domain.positions()[..4],
vec![DVec2::new(-25., -50.), DVec2::new(50., -25.), DVec2::new(25., 50.), DVec2::new(-50., 25.)]
@@ -1408,6 +1459,8 @@ mod test {
async fn bevel_rect() {
let source = Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.);
let beveled = super::bevel(Footprint::default(), &vector_node(source), 5.).await;
let beveled = beveled.one_item();
assert_eq!(beveled.point_domain.positions().len(), 8);
assert_eq!(beveled.segment_domain.ids().len(), 8);
@@ -1429,6 +1482,7 @@ mod test {
let curve = Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::new(10., 0.), DVec2::new(10., 100.), DVec2::X * 100.);
let source = Subpath::from_beziers(&[Bezier::from_linear_dvec2(DVec2::X * -100., DVec2::ZERO), curve], false);
let beveled = super::bevel(Footprint::default(), &vector_node(source), 5.).await;
let beveled = beveled.one_item();
assert_eq!(beveled.point_domain.positions().len(), 4);
assert_eq!(beveled.segment_domain.ids().len(), 3);
@@ -1449,7 +1503,8 @@ mod test {
let mut vector_data = VectorData::from_subpath(source);
let transform = DAffine2::from_scale_angle_translation(DVec2::splat(10.), 1., DVec2::new(99., 77.));
vector_data.transform = transform;
let beveled = super::bevel(Footprint::default(), &FutureWrapperNode(vector_data), 5.).await;
let beveled = super::bevel(Footprint::default(), &FutureWrapperNode(VectorDataTable::new(vector_data)), 5.).await;
let beveled = beveled.one_item();
assert_eq!(beveled.point_domain.positions().len(), 4);
assert_eq!(beveled.segment_domain.ids().len(), 3);
@@ -1468,6 +1523,8 @@ mod test {
async fn bevel_too_high() {
let source = Subpath::from_anchors([DVec2::ZERO, DVec2::new(100., 0.), DVec2::new(100., 100.), DVec2::new(0., 100.)], false);
let beveled = super::bevel(Footprint::default(), &vector_node(source), 999.).await;
let beveled = beveled.one_item();
assert_eq!(beveled.point_domain.positions().len(), 6);
assert_eq!(beveled.segment_domain.ids().len(), 5);
@@ -1487,6 +1544,7 @@ mod test {
let point = Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::ZERO, DVec2::ZERO);
let source = Subpath::from_beziers(&[Bezier::from_linear_dvec2(DVec2::X * -100., DVec2::ZERO), point, curve], false);
let beveled = super::bevel(Footprint::default(), &vector_node(source), 5.).await;
let beveled = beveled.one_item();
assert_eq!(beveled.point_domain.positions().len(), 6);
assert_eq!(beveled.segment_domain.ids().len(), 5);