mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
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:
@@ -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")]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(_) => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
87
node-graph/gcore/src/instances.rs
Normal file
87
node-graph/gcore/src/instances.rs
Normal 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>;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()));
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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]
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user