Implement dynamic table attributes to generalize the graphic-specific Table type (#4050)
* Feature-gate serde derives behind cfg_attr in all runtime node graph type crates * Refactor Table to move its hard-coded fields into an attributes field * Encapsulate TableRow/TableRowRef/TableRowMut attribute fields behind accessor methods * Remove TaggedValue::GraphicUnused * Refactor Table<T> to use dynamic attributes instead fixed names * Fix code review soundness concerns * Add todo work * Replace row-oriented Table<T> API with column-oriented access * Fix attribute propagation bugs ---------
This commit is contained in:
parent
324b9e664c
commit
76938eb69a
|
|
@ -4,9 +4,8 @@ use crate::messages::portfolio::document::data_panel::DataPanelMessage;
|
|||
use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::tool_messages::tool_prelude::*;
|
||||
use glam::{Affine2, Vec2};
|
||||
use glam::{Affine2, DAffine2, Vec2};
|
||||
use graph_craft::document::NodeId;
|
||||
use graphene_std::Color;
|
||||
use graphene_std::Context;
|
||||
use graphene_std::gradient::GradientStops;
|
||||
use graphene_std::memo::IORecord;
|
||||
|
|
@ -14,6 +13,7 @@ use graphene_std::raster_types::{CPU, GPU, Raster};
|
|||
use graphene_std::table::Table;
|
||||
use graphene_std::vector::Vector;
|
||||
use graphene_std::vector::style::{Fill, FillChoice};
|
||||
use graphene_std::{AlphaBlending, Color};
|
||||
use graphene_std::{Artboard, Graphic};
|
||||
use std::any::Any;
|
||||
use std::sync::Arc;
|
||||
|
|
@ -247,9 +247,9 @@ impl<T: TableRowLayout> TableRowLayout for Table<T> {
|
|||
}
|
||||
fn element_page(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
if let Some(index) = data.desired_path.get(data.current_depth).copied() {
|
||||
if let Some(row) = self.get(index) {
|
||||
if let Some(element) = self.element(index) {
|
||||
data.current_depth += 1;
|
||||
let result = row.element.layout_with_breadcrumb(data);
|
||||
let result = element.layout_with_breadcrumb(data);
|
||||
data.current_depth -= 1;
|
||||
return result;
|
||||
} else {
|
||||
|
|
@ -258,23 +258,37 @@ impl<T: TableRowLayout> TableRowLayout for Table<T> {
|
|||
}
|
||||
}
|
||||
|
||||
let mut rows = self
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, row)| {
|
||||
vec![
|
||||
TextLabel::new(format!("{index}")).narrow(true).widget_instance(),
|
||||
row.element.element_widget(index),
|
||||
TextLabel::new(format_transform_matrix(row.transform)).narrow(true).widget_instance(),
|
||||
TextLabel::new(format!("{}", row.alpha_blending)).narrow(true).widget_instance(),
|
||||
TextLabel::new(row.source_node_id.map_or_else(|| "-".to_string(), |id| format!("{}", id.0)))
|
||||
.narrow(true)
|
||||
.widget_instance(),
|
||||
]
|
||||
let attribute_keys: Vec<String> = self.attribute_keys().map(str::to_string).collect();
|
||||
|
||||
let mut rows = (0..self.len())
|
||||
.map(|index| {
|
||||
let element = self.element(index).unwrap();
|
||||
let mut cells = vec![TextLabel::new(format!("{index}")).narrow(true).widget_instance(), element.element_widget(index)];
|
||||
for key in &attribute_keys {
|
||||
let value = self
|
||||
.attribute_display_value(key, index, |ty| {
|
||||
if let Some(&value) = ty.downcast_ref::<DAffine2>() {
|
||||
Some(format_transform_matrix(value))
|
||||
} else if let Some(&value) = ty.downcast_ref::<DVec2>() {
|
||||
Some(format_dvec2(value))
|
||||
} else if let Some(&value) = ty.downcast_ref::<AlphaBlending>() {
|
||||
Some(format_alpha_blending(value))
|
||||
} else if let Some(&value) = ty.downcast_ref::<Option<NodeId>>() {
|
||||
Some(value.map_or_else(|| "-".to_string(), |id| id.to_string()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| "-".to_string());
|
||||
cells.push(TextLabel::new(value).narrow(true).widget_instance());
|
||||
}
|
||||
cells
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
rows.insert(0, column_headings(&["", "element", "transform", "alpha_blending", "source_node_id"]));
|
||||
let mut column_names = vec!["", "element"];
|
||||
column_names.extend(attribute_keys.iter().map(|s| s.as_str()));
|
||||
rows.insert(0, column_headings(&column_names));
|
||||
|
||||
vec![LayoutGroup::table(rows, false)]
|
||||
}
|
||||
|
|
@ -430,7 +444,7 @@ impl TableRowLayout for Vector {
|
|||
]);
|
||||
table_rows.push(vec![
|
||||
TextLabel::new("Stroke Transform").narrow(true).widget_instance(),
|
||||
TextLabel::new(format_transform_matrix(&stroke.transform)).narrow(true).widget_instance(),
|
||||
TextLabel::new(format_transform_matrix(stroke.transform)).narrow(true).widget_instance(),
|
||||
]);
|
||||
table_rows.push(vec![
|
||||
TextLabel::new("Stroke Paint Order").narrow(true).widget_instance(),
|
||||
|
|
@ -695,7 +709,7 @@ impl TableRowLayout for DAffine2 {
|
|||
"Transform".to_string()
|
||||
}
|
||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
let widgets = vec![TextLabel::new(format_transform_matrix(self)).widget_instance()];
|
||||
let widgets = vec![TextLabel::new(format_transform_matrix(*self)).widget_instance()];
|
||||
vec![LayoutGroup::row(widgets)]
|
||||
}
|
||||
}
|
||||
|
|
@ -709,12 +723,12 @@ impl TableRowLayout for Affine2 {
|
|||
}
|
||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
let matrix = DAffine2::from_cols_array(&self.to_cols_array().map(|x| x as f64));
|
||||
let widgets = vec![TextLabel::new(format_transform_matrix(&matrix)).widget_instance()];
|
||||
let widgets = vec![TextLabel::new(format_transform_matrix(matrix)).widget_instance()];
|
||||
vec![LayoutGroup::row(widgets)]
|
||||
}
|
||||
}
|
||||
|
||||
fn format_transform_matrix(transform: &DAffine2) -> String {
|
||||
fn format_transform_matrix(transform: DAffine2) -> String {
|
||||
let (scale, angle, translation) = if transform.matrix2.determinant().abs() <= f64::EPSILON {
|
||||
let [col_0, col_1] = transform.matrix2.to_cols_array_2d().map(|[x, y]| DVec2::new(x, y));
|
||||
|
||||
|
|
@ -748,3 +762,14 @@ fn format_dvec2(value: DVec2) -> String {
|
|||
let round = |x: f64| (x * 1e3).round() / 1e3;
|
||||
format!("({} px, {} px)", round(value.x), round(value.y))
|
||||
}
|
||||
|
||||
fn format_alpha_blending(value: AlphaBlending) -> String {
|
||||
let round = |x: f32| (x * 1e3).round() / 1e3;
|
||||
format!(
|
||||
"Blend Mode: {} — Opacity: {}% — Fill: {}% — Clip: {}",
|
||||
value.blend_mode,
|
||||
round(value.opacity * 100.),
|
||||
round(value.fill * 100.),
|
||||
if value.clip { "Yes" } else { "No" }
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -713,10 +713,10 @@ fn set_import_child_positions(
|
|||
let child_pos = IVec2::new(child_x, current_y);
|
||||
|
||||
if i == 0 {
|
||||
// Top of stack — set to `Absolute` position
|
||||
// Top of stack: set to `Absolute` position
|
||||
network_interface.set_layer_position_for_import(&child_layer.to_node(), LayerPosition::Absolute(child_pos), &[]);
|
||||
} else {
|
||||
// Below top — set `Stack` with `y_offset` based on previous sibling's subtree extent
|
||||
// Below top: set `Stack` with `y_offset` based on previous sibling's subtree extent
|
||||
let prev_sibling_svg_index = n - i;
|
||||
let y_offset = child_extents_svg_order[prev_sibling_svg_index] + STACK_VERTICAL_GAP as u32;
|
||||
network_interface.set_layer_position_for_import(&child_layer.to_node(), LayerPosition::Stack(y_offset), &[]);
|
||||
|
|
|
|||
|
|
@ -1178,8 +1178,8 @@ pub fn color_widget(parameter_widgets_info: ParameterWidgetsInfo, color_button:
|
|||
match &**tagged_value {
|
||||
TaggedValue::Color(color_table) => widgets.push(
|
||||
color_button
|
||||
.value(match color_table.iter().next() {
|
||||
Some(color) => FillChoice::Solid(*color.element),
|
||||
.value(match color_table.element(0) {
|
||||
Some(color) => FillChoice::Solid(*color),
|
||||
None => FillChoice::None,
|
||||
})
|
||||
.on_update(update_value(
|
||||
|
|
@ -1192,8 +1192,8 @@ pub fn color_widget(parameter_widgets_info: ParameterWidgetsInfo, color_button:
|
|||
),
|
||||
TaggedValue::GradientTable(gradient_table) => widgets.push(
|
||||
color_button
|
||||
.value(match gradient_table.iter().next() {
|
||||
Some(row) => FillChoice::Gradient(row.element.clone()),
|
||||
.value(match gradient_table.element(0) {
|
||||
Some(gradient) => FillChoice::Gradient(gradient.clone()),
|
||||
None => FillChoice::Gradient(GradientStops::default()),
|
||||
})
|
||||
.on_update(update_value(
|
||||
|
|
|
|||
|
|
@ -1166,16 +1166,18 @@ impl OverlayContextInternal {
|
|||
fn render_text_paths(&mut self, text_table: &Table<Vector>, font_color: &str, base_transform: kurbo::Affine) {
|
||||
let color = Self::parse_color(font_color);
|
||||
|
||||
for row in text_table.iter() {
|
||||
for index in 0..text_table.len() {
|
||||
// Use the existing bezier_to_path infrastructure to convert Vector to BezPath
|
||||
let mut path = BezPath::new();
|
||||
let mut last_point = None;
|
||||
let transform: DAffine2 = text_table.attribute_cloned_or_default("transform", index);
|
||||
|
||||
for (_, bezier, start_id, end_id) in row.element.segment_iter() {
|
||||
let Some(element) = text_table.element(index) else { continue };
|
||||
for (_, bezier, start_id, end_id) in element.segment_iter() {
|
||||
let move_to = last_point != Some(start_id);
|
||||
last_point = Some(end_id);
|
||||
|
||||
self.bezier_to_path(bezier, *row.transform, move_to, &mut path);
|
||||
self.bezier_to_path(bezier, transform, move_to, &mut path);
|
||||
}
|
||||
|
||||
// Render the path
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ impl DocumentMetadata {
|
|||
.any(|upstream| Some(upstream) == source)
|
||||
{
|
||||
use_local = false;
|
||||
info!("Local transform is invalid — using the identity for the local transform instead")
|
||||
info!("Local transform is invalid. Using the identity for the local transform instead.");
|
||||
}
|
||||
let local_transform = use_local.then(|| self.local_transforms.get(&layer.to_node()).copied()).flatten().unwrap_or_default();
|
||||
|
||||
|
|
|
|||
|
|
@ -5521,11 +5521,11 @@ impl NodeNetworkInterface {
|
|||
|
||||
match post_node_input {
|
||||
NodeInput::Value { .. } | NodeInput::Scope(_) | NodeInput::Inline(_) | NodeInput::Reflection(_) => {
|
||||
// First child in the stack — wire layer output to the post_node input
|
||||
// First child in the stack: wire layer output to the post_node input
|
||||
self.set_input_for_import(&post_node, layer_output, network_path);
|
||||
}
|
||||
NodeInput::Node { .. } => {
|
||||
// Subsequent child — insert layer between post_node and its current upstream:
|
||||
// Subsequent child: insert layer between post_node and its current upstream...
|
||||
// 1. Disconnect old upstream from post_node, wire layer output to post_node
|
||||
self.set_input_for_import(&post_node, layer_output, network_path);
|
||||
// 2. Wire old upstream into layer's primary (stack) input
|
||||
|
|
|
|||
|
|
@ -1962,7 +1962,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
|
|||
&& let TaggedValue::Vector(vector_table) = &**tagged_value
|
||||
&& !vector_table.is_empty()
|
||||
{
|
||||
let vector = vector_table.iter().next()?.element;
|
||||
let vector = vector_table.element(0)?;
|
||||
let modification = Box::new(graphene_std::vector::VectorModification::create_from_vector(vector));
|
||||
|
||||
// Reset input 0 to the default exposed state
|
||||
|
|
@ -1981,9 +1981,9 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
|
|||
if reference == DefinitionIdentifier::ProtoNode(graphene_std::raster_nodes::std_nodes::image::IDENTIFIER)
|
||||
&& let Some(NodeInput::Value { tagged_value, .. }) = node.inputs.get(1)
|
||||
&& let TaggedValue::Raster(raster_table) = &**tagged_value
|
||||
&& let Some(row) = raster_table.iter().next()
|
||||
&& let Some(element) = raster_table.element(0)
|
||||
{
|
||||
let image = row.element.data().clone();
|
||||
let image = element.data().clone();
|
||||
|
||||
document
|
||||
.network_interface
|
||||
|
|
|
|||
|
|
@ -646,10 +646,12 @@ mod test_artboard {
|
|||
|
||||
/// Check if all of the artboards exist in any ordering
|
||||
async fn has_artboards(editor: &mut EditorTestUtils, mut expected: Vec<ArtboardLayoutDocument>) {
|
||||
let artboards = get_artboards(editor)
|
||||
.await
|
||||
.iter()
|
||||
.map(|row| ArtboardLayoutDocument::new(row.element.location, row.element.dimensions))
|
||||
let artboards = get_artboards(editor).await;
|
||||
let artboards = (0..artboards.len())
|
||||
.map(|index| {
|
||||
let element = artboards.element(index).unwrap();
|
||||
ArtboardLayoutDocument::new(element.location, element.dimensions)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(artboards.len(), expected.len(), "incorrect len: actual {:?}, expected {:?}", artboards, expected);
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ use graphene_std::ops::Convert;
|
|||
use graphene_std::platform_application_io::canvas_utils::{Canvas, CanvasSurface, CanvasSurfaceHandle};
|
||||
use graphene_std::raster_types::Raster;
|
||||
use graphene_std::renderer::{Render, RenderParams, RenderSvgSegmentList, SvgRender, SvgSegment};
|
||||
use graphene_std::table::{Table, TableRow};
|
||||
use graphene_std::table::Table;
|
||||
use graphene_std::text::FontCache;
|
||||
use graphene_std::transform::RenderQuality;
|
||||
use graphene_std::vector::Vector;
|
||||
|
|
@ -441,9 +441,7 @@ impl NodeRuntime {
|
|||
// Vector table: vector modifications
|
||||
else if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, Table<Vector>>>() {
|
||||
// Insert the vector modify
|
||||
let default = TableRow::default();
|
||||
self.vector_modify
|
||||
.insert(parent_network_node_id, io.output.iter().next().unwrap_or_else(|| default.as_ref()).element.clone());
|
||||
self.vector_modify.insert(parent_network_node_id, io.output.element(0).cloned().unwrap_or_default());
|
||||
}
|
||||
// Other
|
||||
else {
|
||||
|
|
|
|||
|
|
@ -23,15 +23,15 @@ wasm = [
|
|||
# Local dependencies
|
||||
dyn-any = { workspace = true }
|
||||
graphene-hash = { workspace = true }
|
||||
core-types = { workspace = true }
|
||||
brush-nodes = { workspace = true }
|
||||
graphene-core = { workspace = true }
|
||||
graphene-application-io = { workspace = true }
|
||||
rendering = { workspace = true }
|
||||
raster-nodes = { workspace = true }
|
||||
vector-nodes = { workspace = true }
|
||||
graphic-types = { workspace = true }
|
||||
text-nodes = { workspace = true }
|
||||
core-types = { workspace = true, features = ["serde"] }
|
||||
brush-nodes = { workspace = true, features = ["serde"] }
|
||||
graphene-core = { workspace = true, features = ["serde"] }
|
||||
graphene-application-io = { workspace = true, features = ["serde"] }
|
||||
rendering = { workspace = true, features = ["serde"] }
|
||||
raster-nodes = { workspace = true, features = ["serde"] }
|
||||
vector-nodes = { workspace = true, features = ["serde"] }
|
||||
graphic-types = { workspace = true, features = ["serde"] }
|
||||
text-nodes = { workspace = true, features = ["serde"] }
|
||||
|
||||
# Workspace dependencies
|
||||
log = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -187,7 +187,6 @@ tagged_value! {
|
|||
// ===========
|
||||
// TABLE TYPES
|
||||
// ===========
|
||||
GraphicUnused(Graphic), // TODO: This is unused but removing it causes `cargo test` to infinitely recurse its type solving; figure out why and then remove this
|
||||
#[serde(deserialize_with = "graphic_types::migrations::migrate_vector")] // TODO: Eventually remove this migration document upgrade code
|
||||
#[serde(alias = "VectorData")]
|
||||
Vector(Table<Vector>),
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ authors = ["Graphite Authors <contact@graphite.art>"]
|
|||
license = "MIT OR Apache-2.0"
|
||||
|
||||
[features]
|
||||
default = ["serde"]
|
||||
serde = ["dep:serde", "core-types/serde", "vector-types/serde", "text-nodes/serde"]
|
||||
wasm = ["dep:web-sys"]
|
||||
wgpu = ["dep:wgpu"]
|
||||
|
||||
|
|
@ -19,9 +21,11 @@ text-nodes = { workspace = true }
|
|||
|
||||
# Workspace dependencies
|
||||
glam = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
log = { workspace = true }
|
||||
|
||||
# Optional workspace dependencies
|
||||
serde = { workspace = true, optional = true }
|
||||
|
||||
# Optional workspace dependencies
|
||||
web-sys = { workspace = true, optional = true }
|
||||
wgpu = { workspace = true, optional = true }
|
||||
|
|
|
|||
|
|
@ -73,7 +73,8 @@ pub enum ApplicationError {
|
|||
InvalidUrl,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum NodeGraphUpdateMessage {}
|
||||
|
||||
pub trait NodeGraphUpdateSender {
|
||||
|
|
@ -90,26 +91,29 @@ pub trait GetEditorPreferences {
|
|||
fn max_render_region_area(&self) -> u32;
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum ExportFormat {
|
||||
#[default]
|
||||
Svg,
|
||||
Raster,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, DynAny)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct TimingInformation {
|
||||
pub time: f64,
|
||||
pub animation_time: Duration,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, DynAny)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct RenderConfig {
|
||||
pub viewport: Footprint,
|
||||
pub scale: f64,
|
||||
pub time: TimingInformation,
|
||||
pub pointer: DVec2,
|
||||
#[serde(alias = "view_mode")]
|
||||
#[cfg_attr(feature = "serde", serde(alias = "view_mode"))]
|
||||
pub render_mode: RenderMode,
|
||||
pub export_format: ExportFormat,
|
||||
pub for_export: bool,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ license = "MIT OR Apache-2.0"
|
|||
|
||||
[features]
|
||||
default = ["serde"]
|
||||
serde = ["dep:serde"]
|
||||
nightly = []
|
||||
type_id_logging = []
|
||||
dealloc_nodes = []
|
||||
|
|
|
|||
|
|
@ -130,7 +130,8 @@ impl<T: Ctx + InjectVarArgs + ExtractVarArgs> ModifyVarArgs for T {}
|
|||
// ================
|
||||
|
||||
// Public enum for flexible node macro codegen
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum ContextFeature {
|
||||
ExtractFootprint,
|
||||
ExtractRealTime,
|
||||
|
|
@ -151,7 +152,8 @@ pub enum ContextFeature {
|
|||
// Internal bitflags for fast compiler analysis
|
||||
use bitflags::bitflags;
|
||||
bitflags! {
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, dyn_any::DynAny, serde::Serialize, serde::Deserialize, Default)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, dyn_any::DynAny, Default)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct ContextFeatures: u32 {
|
||||
const FOOTPRINT = 1 << 0;
|
||||
const REAL_TIME = 1 << 1;
|
||||
|
|
@ -188,7 +190,8 @@ impl ContextFeatures {
|
|||
// CONTEXT DEPENDENCIES
|
||||
// ====================
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, graphene_hash::CacheHash, dyn_any::DynAny, serde::Serialize, serde::Deserialize, Default)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, graphene_hash::CacheHash, dyn_any::DynAny, Default)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct ContextDependencies {
|
||||
pub extract: ContextFeatures,
|
||||
pub inject: ContextFeatures,
|
||||
|
|
|
|||
|
|
@ -67,8 +67,8 @@ pub fn migrate_color<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Resul
|
|||
use no_std_types::color::Color;
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[serde(untagged)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "serde", serde(untagged))]
|
||||
enum ColorFormat {
|
||||
Color(Color),
|
||||
OptionalColor(Option<Color>),
|
||||
|
|
|
|||
|
|
@ -62,11 +62,9 @@ impl<U, T: TableConvert<U> + Send> Convert<Table<U>, ()> for Table<T> {
|
|||
async fn convert(self, _: Footprint, _: ()) -> Table<U> {
|
||||
let table: Table<U> = self
|
||||
.into_iter()
|
||||
.map(|row| TableRow {
|
||||
element: row.element.convert_row(),
|
||||
transform: row.transform,
|
||||
alpha_blending: row.alpha_blending,
|
||||
source_node_id: row.source_node_id,
|
||||
.map(|row| {
|
||||
let (element, attributes) = row.into_parts();
|
||||
TableRow::from_parts(element.convert_row(), attributes)
|
||||
})
|
||||
.collect();
|
||||
table
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ pub trait RenderComplexity {
|
|||
|
||||
impl<T: RenderComplexity> RenderComplexity for Table<T> {
|
||||
fn render_complexity(&self) -> usize {
|
||||
self.iter().map(|row| row.element.render_complexity()).fold(0, usize::saturating_add)
|
||||
self.iter_element_values().map(|element| element.render_complexity()).fold(0, usize::saturating_add)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -11,7 +11,8 @@ pub use to_path::*;
|
|||
/// Alignment of lines of type within a text block.
|
||||
#[repr(C)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, node_macro::ChoiceType)]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, DynAny, node_macro::ChoiceType)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[widget(Radio)]
|
||||
pub enum TextAlign {
|
||||
#[default]
|
||||
|
|
@ -34,7 +35,8 @@ impl From<TextAlign> for parley::Alignment {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(PartialEq, Clone, Copy, Debug)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct TypesettingConfig {
|
||||
pub font_size: f64,
|
||||
pub line_height_ratio: f64,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@ use glam::{DAffine2, DMat2, DVec2, UVec2};
|
|||
/// Controls whether the Decompose Scale node returns axis-length magnitudes or pure scale factors.
|
||||
#[repr(C)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, graphene_hash::CacheHash, DynAny, node_macro::ChoiceType)]
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash, graphene_hash::CacheHash, DynAny, node_macro::ChoiceType)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[widget(Radio)]
|
||||
pub enum ScaleType {
|
||||
/// The visual length of each axis (always positive, includes any skew contribution).
|
||||
|
|
@ -141,7 +142,8 @@ impl TransformMut for Footprint {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, dyn_any::DynAny, PartialEq, graphene_hash::CacheHash, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Debug, Clone, Copy, dyn_any::DynAny, PartialEq, graphene_hash::CacheHash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum RenderQuality {
|
||||
/// Low quality, fast rendering
|
||||
Preview,
|
||||
|
|
@ -154,7 +156,8 @@ pub enum RenderQuality {
|
|||
/// Render at full quality
|
||||
Full,
|
||||
}
|
||||
#[derive(Debug, Clone, Copy, dyn_any::DynAny, PartialEq, graphene_hash::CacheHash, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Debug, Clone, Copy, dyn_any::DynAny, PartialEq, graphene_hash::CacheHash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct Footprint {
|
||||
/// Inverse of the transform which will be applied to the node output during the rendering process
|
||||
pub transform: DAffine2,
|
||||
|
|
|
|||
|
|
@ -77,7 +77,8 @@ macro_rules! fn_type_fut {
|
|||
}
|
||||
|
||||
// TODO: Rename to NodeSignatureMonomorphization
|
||||
#[derive(Clone, PartialEq, Eq, Hash, graphene_hash::CacheHash, Default, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, PartialEq, Eq, Hash, graphene_hash::CacheHash, Default)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct NodeIOTypes {
|
||||
pub call_argument: Type,
|
||||
pub return_value: Type,
|
||||
|
|
@ -126,7 +127,8 @@ impl std::fmt::Debug for NodeIOTypes {
|
|||
}
|
||||
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, graphene_hash::CacheHash, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, graphene_hash::CacheHash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct ProtoNodeIdentifier {
|
||||
name: Cow<'static, str>,
|
||||
}
|
||||
|
|
@ -180,17 +182,18 @@ fn migrate_type_descriptor_names<'de, D: serde::Deserializer<'de>>(deserializer:
|
|||
}
|
||||
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Clone, Debug, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Debug, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct TypeDescriptor {
|
||||
#[serde(skip)]
|
||||
#[cfg_attr(feature = "serde", serde(skip))]
|
||||
pub id: Option<TypeId>,
|
||||
#[serde(deserialize_with = "migrate_type_descriptor_names")]
|
||||
#[cfg_attr(feature = "serde", serde(deserialize_with = "migrate_type_descriptor_names"))]
|
||||
pub name: Cow<'static, str>,
|
||||
#[serde(default)]
|
||||
#[cfg_attr(feature = "serde", serde(default))]
|
||||
pub alias: Option<Cow<'static, str>>,
|
||||
#[serde(skip)]
|
||||
#[cfg_attr(feature = "serde", serde(skip))]
|
||||
pub size: usize,
|
||||
#[serde(skip)]
|
||||
#[cfg_attr(feature = "serde", serde(skip))]
|
||||
pub align: usize,
|
||||
}
|
||||
|
||||
|
|
@ -228,7 +231,8 @@ impl PartialEq for TypeDescriptor {
|
|||
|
||||
/// Graph runtime type information used for type inference.
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Clone, PartialEq, Eq, Hash, graphene_hash::CacheHash, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, PartialEq, Eq, Hash, graphene_hash::CacheHash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum Type {
|
||||
/// A wrapper for some type variable used within the inference system. Resolved at inference time and replaced with a concrete type.
|
||||
Generic(Cow<'static, str>),
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@ use dyn_any::DynAny;
|
|||
pub use uuid_generation::*;
|
||||
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Clone, Copy, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Copy)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct Uuid(#[serde(with = "u64_string")] u64);
|
||||
|
||||
mod u64_string {
|
||||
|
|
@ -68,7 +69,8 @@ mod uuid_generation {
|
|||
|
||||
#[repr(transparent)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify), tsify(large_number_types_as_bigints))]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, graphene_hash::CacheHash, PartialOrd, Ord, serde::Serialize, serde::Deserialize, DynAny)]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, graphene_hash::CacheHash, PartialOrd, Ord, DynAny)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct NodeId(pub u64);
|
||||
|
||||
impl NodeId {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ license = "MIT OR Apache-2.0"
|
|||
|
||||
[features]
|
||||
default = ["serde"]
|
||||
serde = ["dep:serde", "core-types/serde", "vector-types/serde", "raster-types/serde"]
|
||||
wasm = [
|
||||
"core-types/wasm",
|
||||
"vector-types/wasm",
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@ use glam::{DAffine2, DVec2, IVec2};
|
|||
use graphene_hash::CacheHash;
|
||||
|
||||
/// Some [`ArtboardData`] with some optional clipping bounds that can be exported.
|
||||
#[derive(Clone, Debug, CacheHash, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Debug, CacheHash, PartialEq, DynAny)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct Artboard {
|
||||
pub content: Table<Graphic>,
|
||||
pub label: String,
|
||||
|
|
@ -49,9 +50,22 @@ impl BoundingBox for Artboard {
|
|||
return RenderBoundingBox::Rectangle(artboard_bounds());
|
||||
}
|
||||
|
||||
match self.content.bounding_box(transform, include_stroke) {
|
||||
RenderBoundingBox::Rectangle(content_bounds) => RenderBoundingBox::Rectangle(Quad::combine_bounds(content_bounds, artboard_bounds())),
|
||||
other => other,
|
||||
let mut combined_bounds = None;
|
||||
|
||||
for (element, row_transform) in self.content.iter_element_values().zip(self.content.iter_attribute_values_or_default::<DAffine2>("transform")) {
|
||||
match element.bounding_box(transform * row_transform, include_stroke) {
|
||||
RenderBoundingBox::None => continue,
|
||||
RenderBoundingBox::Infinite => return RenderBoundingBox::Infinite,
|
||||
RenderBoundingBox::Rectangle(bounds) => match combined_bounds {
|
||||
Some(existing) => combined_bounds = Some(Quad::combine_bounds(existing, bounds)),
|
||||
None => combined_bounds = Some(bounds),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
match combined_bounds {
|
||||
Some(content_bounds) => RenderBoundingBox::Rectangle(Quad::combine_bounds(content_bounds, artboard_bounds())),
|
||||
None => RenderBoundingBox::Rectangle(artboard_bounds()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -76,22 +90,24 @@ impl Transform for Artboard {
|
|||
pub fn migrate_artboard<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Table<Artboard>, D::Error> {
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Clone, Default, Debug, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Default, Debug, PartialEq, DynAny)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct ArtboardGroup {
|
||||
pub artboards: Vec<(Artboard, Option<NodeId>)>,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[serde(untagged)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "serde", serde(untagged))]
|
||||
enum ArtboardFormat {
|
||||
ArtboardGroup(ArtboardGroup),
|
||||
OldArtboardTable(OldTable<Artboard>),
|
||||
ArtboardTable(Table<Artboard>),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct OldTable<T> {
|
||||
#[serde(alias = "instances", alias = "instance")]
|
||||
#[cfg_attr(feature = "serde", serde(alias = "instances", alias = "instance"))]
|
||||
element: Vec<T>,
|
||||
transform: Vec<DAffine2>,
|
||||
alpha_blending: Vec<AlphaBlending>,
|
||||
|
|
@ -101,12 +117,12 @@ pub fn migrate_artboard<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Re
|
|||
ArtboardFormat::ArtboardGroup(artboard_group) => {
|
||||
let mut table = Table::new();
|
||||
for (artboard, source_node_id) in artboard_group.artboards {
|
||||
table.push(TableRow {
|
||||
element: artboard,
|
||||
transform: DAffine2::IDENTITY,
|
||||
alpha_blending: AlphaBlending::default(),
|
||||
source_node_id,
|
||||
});
|
||||
table.push(
|
||||
TableRow::new_from_element(artboard)
|
||||
.with_attribute("transform", DAffine2::IDENTITY)
|
||||
.with_attribute("alpha_blending", AlphaBlending::default())
|
||||
.with_attribute("source_node_id", source_node_id),
|
||||
);
|
||||
}
|
||||
table
|
||||
}
|
||||
|
|
@ -114,11 +130,11 @@ pub fn migrate_artboard<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Re
|
|||
.element
|
||||
.into_iter()
|
||||
.zip(old_table.transform.into_iter().zip(old_table.alpha_blending))
|
||||
.map(|(element, (transform, alpha_blending))| TableRow {
|
||||
element,
|
||||
transform,
|
||||
alpha_blending,
|
||||
source_node_id: None,
|
||||
.map(|(element, (transform, alpha_blending))| {
|
||||
TableRow::new_from_element(element)
|
||||
.with_attribute("transform", transform)
|
||||
.with_attribute("alpha_blending", alpha_blending)
|
||||
.with_attribute("source_node_id", None::<NodeId>)
|
||||
})
|
||||
.collect(),
|
||||
ArtboardFormat::ArtboardTable(artboard_table) => artboard_table,
|
||||
|
|
|
|||
|
|
@ -15,7 +15,8 @@ use vector_types::GradientStops;
|
|||
pub type Vector = vector_types::Vector<Option<Table<Graphic>>>;
|
||||
|
||||
/// The possible forms of graphical content that can be rendered by the Render node into either an image or SVG syntax.
|
||||
#[derive(Clone, Debug, CacheHash, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Debug, CacheHash, PartialEq, DynAny)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum Graphic {
|
||||
Graphic(Table<Graphic>),
|
||||
Vector(Table<Vector>),
|
||||
|
|
@ -31,6 +32,28 @@ impl Default for Graphic {
|
|||
}
|
||||
}
|
||||
|
||||
// Explicit `Send`/`Sync` impls. All fields are themselves `Send`/`Sync`, so these would normally
|
||||
// be inferred, but the type participates in two mutually recursive cycles through `Table<Graphic>`
|
||||
// and `Table<Vector>` (where `Vector = vector_types::Vector<Option<Table<Graphic>>>`). The second
|
||||
// path, wrapped in `Option<_>` and a generic type parameter, produces a distinct auto-trait
|
||||
// obligation that the solver cannot recognize as the same cycle node, causing
|
||||
// `overflow evaluating the requirement` errors at the workspace's `once_cell::sync::Lazy` statics.
|
||||
// Providing these impls explicitly anchors the proof and lets the coinductive cache close both cycles.
|
||||
//
|
||||
// These can be removed (reverting to auto-derived `Send`/`Sync`) once any of the following holds:
|
||||
// - We remove the TaggedValue or its variants that contain tables.
|
||||
// - The `Vector` alias no longer references `Graphic` through a generic type parameter, breaking
|
||||
// the second cycle so only the direct `Table<Graphic>` self-cycle remains (which the solver
|
||||
// already handles on its own).
|
||||
// - `Graphic` stops containing `Table<Graphic>` directly, e.g. by boxing children through a trait
|
||||
// object or opaque handle so the recursion is no longer structural.
|
||||
// - A future rustc release improves the auto-trait solver to recognize cycles across generic-
|
||||
// parameter substitutions. Try deleting these impls and running:
|
||||
// `cargo check --tests -p graphite-editor`
|
||||
// If no `overflow evaluating the requirement` errors appear, they're no longer needed).
|
||||
unsafe impl Send for Graphic {}
|
||||
unsafe impl Sync for Graphic {}
|
||||
|
||||
// Graphic
|
||||
impl From<Table<Graphic>> for Graphic {
|
||||
fn from(graphic: Table<Graphic>) -> Self {
|
||||
|
|
@ -118,14 +141,19 @@ fn flatten_graphic_table<T>(content: Table<Graphic>, extract_variant: fn(Graphic
|
|||
|
||||
fn flatten_recursive<T>(output: &mut Table<T>, current_graphic_table: Table<Graphic>, extract_variant: fn(Graphic) -> Option<Table<T>>) {
|
||||
for current_graphic_row in current_graphic_table.into_iter() {
|
||||
let source_node_id = current_graphic_row.source_node_id;
|
||||
let source_node_id: Option<NodeId> = current_graphic_row.attribute_cloned_or_default("source_node_id");
|
||||
let current_transform: DAffine2 = current_graphic_row.attribute_cloned_or_default("transform");
|
||||
let current_alpha_blending: AlphaBlending = current_graphic_row.attribute_cloned_or_default("alpha_blending");
|
||||
|
||||
match current_graphic_row.element {
|
||||
match current_graphic_row.into_element() {
|
||||
// Recurse into nested graphic tables, composing the parent's transform onto each child
|
||||
Graphic::Graphic(mut sub_table) => {
|
||||
for graphic in sub_table.iter_mut() {
|
||||
*graphic.transform = current_graphic_row.transform * *graphic.transform;
|
||||
*graphic.alpha_blending = compose_alpha_blending(current_graphic_row.alpha_blending, *graphic.alpha_blending);
|
||||
for index in 0..sub_table.len() {
|
||||
let child_transform: DAffine2 = sub_table.attribute_cloned_or_default("transform", index);
|
||||
let child_alpha_blending: AlphaBlending = sub_table.attribute_cloned_or_default("alpha_blending", index);
|
||||
|
||||
sub_table.set_attribute("transform", index, current_transform * child_transform);
|
||||
sub_table.set_attribute("alpha_blending", index, compose_alpha_blending(current_alpha_blending, child_alpha_blending));
|
||||
}
|
||||
|
||||
flatten_recursive(output, sub_table, extract_variant);
|
||||
|
|
@ -134,12 +162,15 @@ fn flatten_graphic_table<T>(content: Table<Graphic>, extract_variant: fn(Graphic
|
|||
other => {
|
||||
if let Some(typed_table) = extract_variant(other) {
|
||||
for row in typed_table.into_iter() {
|
||||
output.push(TableRow {
|
||||
element: row.element,
|
||||
transform: current_graphic_row.transform * row.transform,
|
||||
alpha_blending: compose_alpha_blending(current_graphic_row.alpha_blending, row.alpha_blending),
|
||||
source_node_id,
|
||||
});
|
||||
let row_transform: DAffine2 = row.attribute_cloned_or_default("transform");
|
||||
let row_alpha_blending: AlphaBlending = row.attribute_cloned_or_default("alpha_blending");
|
||||
let (element, mut attributes) = row.into_parts();
|
||||
|
||||
attributes.insert("transform", current_transform * row_transform);
|
||||
attributes.insert("alpha_blending", compose_alpha_blending(current_alpha_blending, row_alpha_blending));
|
||||
attributes.insert("source_node_id", source_node_id);
|
||||
|
||||
output.push(TableRow::from_parts(element, attributes));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -289,22 +320,26 @@ impl Graphic {
|
|||
}
|
||||
|
||||
pub fn had_clip_enabled(&self) -> bool {
|
||||
fn all_clipped<T>(table: &Table<T>) -> bool {
|
||||
table.iter_attribute_values_or_default::<AlphaBlending>("alpha_blending").all(|a| a.clip)
|
||||
}
|
||||
match self {
|
||||
Graphic::Vector(vector) => vector.iter().all(|row| row.alpha_blending.clip),
|
||||
Graphic::Graphic(graphic) => graphic.iter().all(|row| row.alpha_blending.clip),
|
||||
Graphic::RasterCPU(raster) => raster.iter().all(|row| row.alpha_blending.clip),
|
||||
Graphic::RasterGPU(raster) => raster.iter().all(|row| row.alpha_blending.clip),
|
||||
Graphic::Color(color) => color.iter().all(|row| row.alpha_blending.clip),
|
||||
Graphic::Gradient(gradient) => gradient.iter().all(|row| row.alpha_blending.clip),
|
||||
Graphic::Vector(table) => all_clipped(table),
|
||||
Graphic::Graphic(table) => all_clipped(table),
|
||||
Graphic::RasterCPU(table) => all_clipped(table),
|
||||
Graphic::RasterGPU(table) => all_clipped(table),
|
||||
Graphic::Color(table) => all_clipped(table),
|
||||
Graphic::Gradient(table) => all_clipped(table),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn can_reduce_to_clip_path(&self) -> bool {
|
||||
match self {
|
||||
Graphic::Vector(vector) => vector.iter().all(|row| {
|
||||
let style = &row.element.style;
|
||||
let alpha_blending = &row.alpha_blending;
|
||||
(alpha_blending.opacity > 1. - f32::EPSILON) && style.fill().is_opaque() && style.stroke().is_none_or(|stroke| !stroke.has_renderable_stroke())
|
||||
Graphic::Vector(vector) => vector
|
||||
.iter_element_values()
|
||||
.zip(vector.iter_attribute_values_or_default::<AlphaBlending>("alpha_blending"))
|
||||
.all(|(element, alpha_blending)| {
|
||||
(alpha_blending.opacity > 1. - f32::EPSILON) && element.style.fill().is_opaque() && element.style.stroke().is_none_or(|stroke| !stroke.has_renderable_stroke())
|
||||
}),
|
||||
_ => false,
|
||||
}
|
||||
|
|
@ -314,12 +349,12 @@ impl Graphic {
|
|||
impl BoundingBox for Graphic {
|
||||
fn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> RenderBoundingBox {
|
||||
match self {
|
||||
Graphic::Vector(vector) => vector.bounding_box(transform, include_stroke),
|
||||
Graphic::RasterCPU(raster) => raster.bounding_box(transform, include_stroke),
|
||||
Graphic::RasterGPU(raster) => raster.bounding_box(transform, include_stroke),
|
||||
Graphic::Graphic(graphic) => graphic.bounding_box(transform, include_stroke),
|
||||
Graphic::Color(color) => color.bounding_box(transform, include_stroke),
|
||||
Graphic::Gradient(gradient) => gradient.bounding_box(transform, include_stroke),
|
||||
Graphic::Vector(table) => table.bounding_box(transform, include_stroke),
|
||||
Graphic::RasterCPU(table) => table.bounding_box(transform, include_stroke),
|
||||
Graphic::RasterGPU(table) => table.bounding_box(transform, include_stroke),
|
||||
Graphic::Graphic(table) => table.bounding_box(transform, include_stroke),
|
||||
Graphic::Color(table) => table.bounding_box(transform, include_stroke),
|
||||
Graphic::Gradient(table) => table.bounding_box(transform, include_stroke),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -375,25 +410,15 @@ impl<T: Clone> AtIndex for Table<T> {
|
|||
type Output = Table<T>;
|
||||
|
||||
fn at_index(&self, index: usize) -> Option<Self::Output> {
|
||||
self.clone_row(index).map(|row| {
|
||||
let mut result_table = Self::default();
|
||||
if let Some(row) = self.iter().nth(index) {
|
||||
result_table.push(row.into_cloned());
|
||||
Some(result_table)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
result_table.push(row);
|
||||
result_table
|
||||
})
|
||||
}
|
||||
|
||||
fn at_index_from_end(&self, index: usize) -> Option<Self::Output> {
|
||||
let mut result_table = Self::default();
|
||||
if index == 0 || index > self.len() {
|
||||
None
|
||||
} else if let Some(row) = self.iter().nth(self.len() - index) {
|
||||
result_table.push(row.into_cloned());
|
||||
Some(result_table)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
if index == 0 || index > self.len() { None } else { self.at_index(self.len() - index) }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -416,9 +441,11 @@ impl<T: Clone> OmitIndex for Vec<T> {
|
|||
impl<T: Clone> OmitIndex for Table<T> {
|
||||
fn omit_index(&self, index: usize) -> Self {
|
||||
let mut result = Self::default();
|
||||
for (i, row) in self.iter().enumerate() {
|
||||
if i != index {
|
||||
result.push(row.into_cloned());
|
||||
for i in 0..self.len() {
|
||||
if i != index
|
||||
&& let Some(row) = self.clone_row(i)
|
||||
{
|
||||
result.push(row);
|
||||
}
|
||||
}
|
||||
result
|
||||
|
|
@ -436,35 +463,39 @@ impl<T: Clone> OmitIndex for Table<T> {
|
|||
pub fn migrate_graphic<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Table<Graphic>, D::Error> {
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DynAny, Default, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Debug, PartialEq, DynAny, Default)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct OldGraphicGroup {
|
||||
elements: Vec<(Graphic, Option<NodeId>)>,
|
||||
transform: DAffine2,
|
||||
alpha_blending: AlphaBlending,
|
||||
}
|
||||
#[derive(Clone, Debug, PartialEq, DynAny, Default, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Debug, PartialEq, DynAny, Default)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct GraphicGroup {
|
||||
elements: Vec<(Graphic, Option<NodeId>)>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct OlderTable<T> {
|
||||
id: Vec<u64>,
|
||||
#[serde(alias = "instances", alias = "instance")]
|
||||
#[cfg_attr(feature = "serde", serde(alias = "instances", alias = "instance"))]
|
||||
element: Vec<T>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct OldTable<T> {
|
||||
id: Vec<u64>,
|
||||
#[serde(alias = "instances", alias = "instance")]
|
||||
#[cfg_attr(feature = "serde", serde(alias = "instances", alias = "instance"))]
|
||||
element: Vec<T>,
|
||||
transform: Vec<DAffine2>,
|
||||
alpha_blending: Vec<AlphaBlending>,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[serde(untagged)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "serde", serde(untagged))]
|
||||
enum GraphicFormat {
|
||||
OldGraphicGroup(OldGraphicGroup),
|
||||
OlderTableOldGraphicGroup(OlderTable<OldGraphicGroup>),
|
||||
|
|
@ -477,12 +508,12 @@ pub fn migrate_graphic<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Res
|
|||
GraphicFormat::OldGraphicGroup(old) => {
|
||||
let mut graphic_table = Table::new();
|
||||
for (graphic, source_node_id) in old.elements {
|
||||
graphic_table.push(TableRow {
|
||||
element: graphic,
|
||||
transform: old.transform,
|
||||
alpha_blending: old.alpha_blending,
|
||||
source_node_id,
|
||||
});
|
||||
graphic_table.push(
|
||||
TableRow::new_from_element(graphic)
|
||||
.with_attribute("transform", old.transform)
|
||||
.with_attribute("alpha_blending", old.alpha_blending)
|
||||
.with_attribute("source_node_id", source_node_id),
|
||||
);
|
||||
}
|
||||
graphic_table
|
||||
}
|
||||
|
|
@ -490,11 +521,11 @@ pub fn migrate_graphic<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Res
|
|||
.element
|
||||
.into_iter()
|
||||
.flat_map(|element| {
|
||||
element.elements.into_iter().map(move |(graphic, source_node_id)| TableRow {
|
||||
element: graphic,
|
||||
transform: element.transform,
|
||||
alpha_blending: element.alpha_blending,
|
||||
source_node_id,
|
||||
element.elements.into_iter().map(move |(graphic, source_node_id)| {
|
||||
TableRow::new_from_element(graphic)
|
||||
.with_attribute("transform", element.transform)
|
||||
.with_attribute("alpha_blending", element.alpha_blending)
|
||||
.with_attribute("source_node_id", source_node_id)
|
||||
})
|
||||
})
|
||||
.collect(),
|
||||
|
|
@ -502,11 +533,11 @@ pub fn migrate_graphic<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Res
|
|||
.element
|
||||
.into_iter()
|
||||
.flat_map(|element| {
|
||||
element.elements.into_iter().map(move |(graphic, source_node_id)| TableRow {
|
||||
element: graphic,
|
||||
transform: element.transform,
|
||||
alpha_blending: element.alpha_blending,
|
||||
source_node_id,
|
||||
element.elements.into_iter().map(move |(graphic, source_node_id)| {
|
||||
TableRow::new_from_element(graphic)
|
||||
.with_attribute("transform", element.transform)
|
||||
.with_attribute("alpha_blending", element.alpha_blending)
|
||||
.with_attribute("source_node_id", source_node_id)
|
||||
})
|
||||
})
|
||||
.collect(),
|
||||
|
|
@ -514,11 +545,11 @@ pub fn migrate_graphic<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Res
|
|||
.element
|
||||
.into_iter()
|
||||
.flat_map(|element| {
|
||||
element.elements.into_iter().map(move |(graphic, source_node_id)| TableRow {
|
||||
element: graphic,
|
||||
transform: Default::default(),
|
||||
alpha_blending: Default::default(),
|
||||
source_node_id,
|
||||
element.elements.into_iter().map(move |(graphic, source_node_id)| {
|
||||
TableRow::new_from_element(graphic)
|
||||
.with_attribute("transform", DAffine2::IDENTITY)
|
||||
.with_attribute("alpha_blending", AlphaBlending::default())
|
||||
.with_attribute("source_node_id", source_node_id)
|
||||
})
|
||||
})
|
||||
.collect(),
|
||||
|
|
@ -526,14 +557,17 @@ pub fn migrate_graphic<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Res
|
|||
// Try to deserialize as either table format
|
||||
if let Ok(old_table) = serde_json::from_value::<Table<GraphicGroup>>(value.clone()) {
|
||||
let mut graphic_table = Table::new();
|
||||
for row in old_table.iter() {
|
||||
for (graphic, source_node_id) in &row.element.elements {
|
||||
graphic_table.push(TableRow {
|
||||
element: graphic.clone(),
|
||||
transform: *row.transform,
|
||||
alpha_blending: *row.alpha_blending,
|
||||
source_node_id: *source_node_id,
|
||||
});
|
||||
for index in 0..old_table.len() {
|
||||
let row_transform: DAffine2 = old_table.attribute_cloned_or_default("transform", index);
|
||||
let row_alpha_blending: AlphaBlending = old_table.attribute_cloned_or_default("alpha_blending", index);
|
||||
|
||||
for (graphic, source_node_id) in &old_table.element(index).unwrap().elements {
|
||||
graphic_table.push(
|
||||
TableRow::new_from_element(graphic.clone())
|
||||
.with_attribute("transform", row_transform)
|
||||
.with_attribute("alpha_blending", row_alpha_blending)
|
||||
.with_attribute("source_node_id", *source_node_id),
|
||||
);
|
||||
}
|
||||
}
|
||||
graphic_table
|
||||
|
|
|
|||
|
|
@ -25,7 +25,8 @@ pub mod migrations {
|
|||
pub fn migrate_vector<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Table<Vector>, D::Error> {
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Debug, PartialEq, DynAny)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct OldVectorData {
|
||||
pub transform: DAffine2,
|
||||
pub alpha_blending: AlphaBlending,
|
||||
|
|
@ -41,23 +42,25 @@ pub mod migrations {
|
|||
pub upstream_graphic_group: Option<Table<Graphic>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct OldTable<T> {
|
||||
#[serde(alias = "instances", alias = "instance")]
|
||||
#[cfg_attr(feature = "serde", serde(alias = "instances", alias = "instance"))]
|
||||
element: Vec<T>,
|
||||
transform: Vec<DAffine2>,
|
||||
alpha_blending: Vec<AlphaBlending>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct OlderTable<T> {
|
||||
id: Vec<u64>,
|
||||
#[serde(alias = "instances", alias = "instance")]
|
||||
#[cfg_attr(feature = "serde", serde(alias = "instances", alias = "instance"))]
|
||||
element: Vec<T>,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[serde(untagged)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "serde", serde(untagged))]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
enum VectorFormat {
|
||||
Vector(Vector),
|
||||
|
|
@ -78,20 +81,29 @@ pub mod migrations {
|
|||
region_domain: old.region_domain,
|
||||
upstream_data: old.upstream_graphic_group,
|
||||
});
|
||||
*vector_table.iter_mut().next().unwrap().transform = old.transform;
|
||||
*vector_table.iter_mut().next().unwrap().alpha_blending = old.alpha_blending;
|
||||
vector_table.set_attribute("transform", 0, old.transform);
|
||||
vector_table.set_attribute("alpha_blending", 0, old.alpha_blending);
|
||||
vector_table
|
||||
}
|
||||
VectorFormat::OlderVectorTable(older_table) => older_table.element.into_iter().map(|element| TableRow { element, ..Default::default() }).collect(),
|
||||
VectorFormat::OlderVectorTable(older_table) => older_table
|
||||
.element
|
||||
.into_iter()
|
||||
.map(|element| {
|
||||
TableRow::new_from_element(element)
|
||||
.with_attribute("transform", DAffine2::IDENTITY)
|
||||
.with_attribute("alpha_blending", AlphaBlending::default())
|
||||
.with_attribute("source_node_id", None::<core_types::uuid::NodeId>)
|
||||
})
|
||||
.collect(),
|
||||
VectorFormat::OldVectorTable(old_table) => old_table
|
||||
.element
|
||||
.into_iter()
|
||||
.zip(old_table.transform.into_iter().zip(old_table.alpha_blending))
|
||||
.map(|(element, (transform, alpha_blending))| TableRow {
|
||||
element,
|
||||
transform,
|
||||
alpha_blending,
|
||||
source_node_id: None,
|
||||
.map(|(element, (transform, alpha_blending))| {
|
||||
TableRow::new_from_element(element)
|
||||
.with_attribute("transform", transform)
|
||||
.with_attribute("alpha_blending", alpha_blending)
|
||||
.with_attribute("source_node_id", None::<core_types::uuid::NodeId>)
|
||||
})
|
||||
.collect(),
|
||||
VectorFormat::VectorTable(vector_table) => vector_table,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ license = "MIT OR Apache-2.0"
|
|||
|
||||
[features]
|
||||
default = ["serde"]
|
||||
serde = ["dep:serde", "core-types/serde"]
|
||||
wgpu = ["dep:wgpu"]
|
||||
wasm = ["core-types/wasm", "tsify", "wasm-bindgen"]
|
||||
|
||||
|
|
|
|||
|
|
@ -39,15 +39,16 @@ mod base64_serde {
|
|||
}
|
||||
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Clone, Eq, Default, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Eq, Default)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct Image<P: Pixel> {
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
#[serde(serialize_with = "base64_serde::as_base64", deserialize_with = "base64_serde::from_base64")]
|
||||
#[cfg_attr(feature = "serde", serde(serialize_with = "base64_serde::as_base64", deserialize_with = "base64_serde::from_base64"))]
|
||||
pub data: Vec<P>,
|
||||
/// Optional: Stores a base64 string representation of the image which can be used to speed up the conversion
|
||||
/// to an svg string. This is used as a cache in order to not have to encode the data on every graph evaluation.
|
||||
#[serde(skip)]
|
||||
#[cfg_attr(feature = "serde", serde(skip))]
|
||||
pub base64_string: Option<String>,
|
||||
// TODO: Add an `origin` field to store where in the local space the image is anchored.
|
||||
// TODO: Currently it is always anchored at the top left corner at (0, 0). The bottom right corner of the new origin field would correspond to (1, 1).
|
||||
|
|
@ -60,7 +61,8 @@ impl<P: Pixel + PartialEq> PartialEq for Image<P> {
|
|||
}
|
||||
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Debug, Clone, dyn_any::DynAny, Default, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Debug, Clone, dyn_any::DynAny, Default, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct TransformImage(pub DAffine2);
|
||||
|
||||
impl core_types::CacheHash for TransformImage {
|
||||
|
|
@ -238,13 +240,15 @@ pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, core_types::CacheHash, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Debug, core_types::CacheHash, PartialEq, DynAny)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum GraphicElement {
|
||||
GraphicGroup(Table<GraphicElement>),
|
||||
RasterFrame(RasterFrame),
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Default, Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct ImageFrame<P: Pixel> {
|
||||
pub image: Image<P>,
|
||||
}
|
||||
|
|
@ -257,7 +261,7 @@ pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
|
|||
fn from(element: GraphicElement) -> Self {
|
||||
match element {
|
||||
GraphicElement::RasterFrame(RasterFrame::ImageFrame(image)) => Self {
|
||||
image: image.iter().next().unwrap().element.clone(),
|
||||
image: image.element(0).unwrap().clone(),
|
||||
},
|
||||
_ => panic!("Expected Image, found {element:?}"),
|
||||
}
|
||||
|
|
@ -272,15 +276,16 @@ pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
|
|||
type Static = ImageFrame<P::Static>;
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Default, Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct OldImageFrame<P: Pixel> {
|
||||
image: Image<P>,
|
||||
transform: DAffine2,
|
||||
alpha_blending: AlphaBlending,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[serde(untagged)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "serde", serde(untagged))]
|
||||
enum FormatVersions {
|
||||
Image(Image<Color>),
|
||||
OldImageFrame(OldImageFrame<Color>),
|
||||
|
|
@ -293,23 +298,25 @@ pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
|
|||
RasterTable(Table<Raster<CPU>>),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct OldTable<T> {
|
||||
#[serde(alias = "instances", alias = "instance")]
|
||||
#[cfg_attr(feature = "serde", serde(alias = "instances", alias = "instance"))]
|
||||
element: Vec<T>,
|
||||
transform: Vec<DAffine2>,
|
||||
alpha_blending: Vec<AlphaBlending>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct OlderTable<T> {
|
||||
id: Vec<u64>,
|
||||
#[serde(alias = "instances", alias = "instance")]
|
||||
#[cfg_attr(feature = "serde", serde(alias = "instances", alias = "instance"))]
|
||||
element: Vec<T>,
|
||||
}
|
||||
|
||||
fn from_image_table(table: Table<Image<Color>>) -> Table<Raster<CPU>> {
|
||||
Table::new_from_element(Raster::new_cpu(table.iter().next().unwrap().element.clone()))
|
||||
Table::new_from_element(Raster::new_cpu(table.element(0).unwrap().clone()))
|
||||
}
|
||||
|
||||
fn old_table_to_new_table<T>(old_table: OldTable<T>) -> Table<T> {
|
||||
|
|
@ -317,11 +324,11 @@ pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
|
|||
.element
|
||||
.into_iter()
|
||||
.zip(old_table.transform.into_iter().zip(old_table.alpha_blending))
|
||||
.map(|(element, (transform, alpha_blending))| TableRow {
|
||||
element,
|
||||
transform,
|
||||
alpha_blending,
|
||||
source_node_id: None,
|
||||
.map(|(element, (transform, alpha_blending))| {
|
||||
TableRow::new_from_element(element)
|
||||
.with_attribute("transform", transform)
|
||||
.with_attribute("alpha_blending", alpha_blending)
|
||||
.with_attribute("source_node_id", None::<core_types::uuid::NodeId>)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
|
@ -330,33 +337,27 @@ pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
|
|||
old_table
|
||||
.element
|
||||
.into_iter()
|
||||
.map(|element| TableRow {
|
||||
element,
|
||||
transform: DAffine2::IDENTITY,
|
||||
alpha_blending: AlphaBlending::default(),
|
||||
source_node_id: None,
|
||||
.map(|element| {
|
||||
TableRow::new_from_element(element)
|
||||
.with_attribute("transform", DAffine2::IDENTITY)
|
||||
.with_attribute("alpha_blending", AlphaBlending::default())
|
||||
.with_attribute("source_node_id", None::<core_types::uuid::NodeId>)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn from_image_frame_table(image_frame: Table<ImageFrame<Color>>) -> Table<Raster<CPU>> {
|
||||
Table::new_from_element(Raster::new_cpu(
|
||||
image_frame
|
||||
.iter()
|
||||
.next()
|
||||
.unwrap_or(Table::new_from_element(ImageFrame::default()).iter().next().unwrap())
|
||||
.element
|
||||
.image
|
||||
.clone(),
|
||||
))
|
||||
let default = ImageFrame::default();
|
||||
let element = image_frame.element(0).unwrap_or(&default);
|
||||
Table::new_from_element(Raster::new_cpu(element.image.clone()))
|
||||
}
|
||||
|
||||
Ok(match FormatVersions::deserialize(deserializer)? {
|
||||
FormatVersions::Image(image) => Table::new_from_element(Raster::new_cpu(image)),
|
||||
FormatVersions::OldImageFrame(OldImageFrame { image, transform, alpha_blending }) => {
|
||||
let mut image_frame_table = Table::new_from_element(Raster::new_cpu(image));
|
||||
*image_frame_table.iter_mut().next().unwrap().transform = transform;
|
||||
*image_frame_table.iter_mut().next().unwrap().alpha_blending = alpha_blending;
|
||||
image_frame_table.set_attribute("transform", 0, transform);
|
||||
image_frame_table.set_attribute("alpha_blending", 0, alpha_blending);
|
||||
image_frame_table
|
||||
}
|
||||
FormatVersions::OlderImageFrameTable(old_table) => from_image_frame_table(older_table_to_new_table(old_table)),
|
||||
|
|
@ -391,14 +392,16 @@ pub fn migrate_image_frame_row<'de, D: serde::Deserializer<'de>>(deserializer: D
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Debug, 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(Table<GraphicElement>),
|
||||
RasterFrame(RasterFrame),
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Default, Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct ImageFrame<P: Pixel> {
|
||||
pub image: Image<P>,
|
||||
}
|
||||
|
|
@ -411,7 +414,7 @@ pub fn migrate_image_frame_row<'de, D: serde::Deserializer<'de>>(deserializer: D
|
|||
fn from(element: GraphicElement) -> Self {
|
||||
match element {
|
||||
GraphicElement::RasterFrame(RasterFrame::ImageFrame(image)) => Self {
|
||||
image: image.iter().next().unwrap().element.clone(),
|
||||
image: image.element(0).unwrap().clone(),
|
||||
},
|
||||
_ => panic!("Expected Image, found {element:?}"),
|
||||
}
|
||||
|
|
@ -426,15 +429,16 @@ pub fn migrate_image_frame_row<'de, D: serde::Deserializer<'de>>(deserializer: D
|
|||
type Static = ImageFrame<P::Static>;
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Default, Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct OldImageFrame<P: Pixel> {
|
||||
image: Image<P>,
|
||||
transform: DAffine2,
|
||||
alpha_blending: AlphaBlending,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[serde(untagged)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "serde", serde(untagged))]
|
||||
enum FormatVersions {
|
||||
Image(Image<Color>),
|
||||
OldImageFrame(OldImageFrame<Color>),
|
||||
|
|
@ -444,20 +448,18 @@ pub fn migrate_image_frame_row<'de, D: serde::Deserializer<'de>>(deserializer: D
|
|||
}
|
||||
|
||||
Ok(match FormatVersions::deserialize(deserializer)? {
|
||||
FormatVersions::Image(image) => TableRow {
|
||||
element: Raster::new_cpu(image),
|
||||
..Default::default()
|
||||
},
|
||||
FormatVersions::OldImageFrame(image_frame_with_transform_and_blending) => TableRow {
|
||||
element: Raster::new_cpu(image_frame_with_transform_and_blending.image),
|
||||
transform: image_frame_with_transform_and_blending.transform,
|
||||
alpha_blending: image_frame_with_transform_and_blending.alpha_blending,
|
||||
source_node_id: None,
|
||||
},
|
||||
FormatVersions::ImageFrameTable(image_frame) => TableRow {
|
||||
element: Raster::new_cpu(image_frame.iter().next().unwrap().element.image.clone()),
|
||||
..Default::default()
|
||||
},
|
||||
FormatVersions::Image(image) => TableRow::new_from_element(Raster::new_cpu(image))
|
||||
.with_attribute("transform", DAffine2::IDENTITY)
|
||||
.with_attribute("alpha_blending", AlphaBlending::default())
|
||||
.with_attribute("source_node_id", None::<core_types::uuid::NodeId>),
|
||||
FormatVersions::OldImageFrame(image_frame_with_transform_and_blending) => TableRow::new_from_element(Raster::new_cpu(image_frame_with_transform_and_blending.image))
|
||||
.with_attribute("transform", image_frame_with_transform_and_blending.transform)
|
||||
.with_attribute("alpha_blending", image_frame_with_transform_and_blending.alpha_blending)
|
||||
.with_attribute("source_node_id", None::<core_types::uuid::NodeId>),
|
||||
FormatVersions::ImageFrameTable(image_frame) => TableRow::new_from_element(Raster::new_cpu(image_frame.element(0).unwrap().image.clone()))
|
||||
.with_attribute("transform", DAffine2::IDENTITY)
|
||||
.with_attribute("alpha_blending", AlphaBlending::default())
|
||||
.with_attribute("source_node_id", None::<core_types::uuid::NodeId>),
|
||||
FormatVersions::RasterTable(image_frame_table) => image_frame_table.into_iter().next().unwrap_or_default(),
|
||||
FormatVersions::RasterTableRow(image_table_row) => image_table_row,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -6,6 +6,10 @@ description = "SVG rendering for Graphene"
|
|||
authors = ["Graphite Authors <contact@graphite.art>"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
[features]
|
||||
default = ["serde"]
|
||||
serde = ["dep:serde", "core-types/serde", "vector-types/serde", "graphic-types/serde"]
|
||||
|
||||
[dependencies]
|
||||
# Local dependencies
|
||||
dyn-any = { workspace = true }
|
||||
|
|
@ -14,7 +18,6 @@ graphene-hash = { workspace = true }
|
|||
|
||||
# Workspace dependencies
|
||||
glam = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
base64 = { workspace = true }
|
||||
log = { workspace = true }
|
||||
num-traits = { workspace = true }
|
||||
|
|
@ -22,7 +25,7 @@ usvg = { workspace = true }
|
|||
kurbo = { workspace = true }
|
||||
vector-types = { workspace = true }
|
||||
graphic-types = { workspace = true }
|
||||
|
||||
|
||||
# Workspace dependencies
|
||||
vello = { workspace = true }
|
||||
|
||||
# Optional workspace dependencies
|
||||
serde = { workspace = true, optional = true }
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use crate::render_ext::RenderExt;
|
||||
use crate::to_peniko::BlendModeExt;
|
||||
use core_types::AlphaBlending;
|
||||
use core_types::CacheHash;
|
||||
use core_types::blending::BlendMode;
|
||||
use core_types::bounds::{BoundingBox, RenderBoundingBox};
|
||||
|
|
@ -68,7 +69,8 @@ pub fn checkerboard_brush() -> peniko::Brush {
|
|||
})
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
enum MaskType {
|
||||
Clip,
|
||||
Mask,
|
||||
|
|
@ -316,7 +318,8 @@ fn draw_raster_outline(scene: &mut Scene, outline_transform: &DAffine2, render_p
|
|||
|
||||
// TODO: Click targets can be removed from the render output, since the vector data is available in the vector modify data from Monitor nodes.
|
||||
// This will require that the transform for child layers into that layer space be calculated, or it could be returned from the RenderOutput instead of click targets.
|
||||
#[derive(Debug, Default, Clone, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Debug, Default, Clone, PartialEq, DynAny)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct RenderMetadata {
|
||||
pub upstream_footprints: HashMap<NodeId, Footprint>,
|
||||
pub local_transforms: HashMap<NodeId, DAffine2>,
|
||||
|
|
@ -408,41 +411,44 @@ impl Render for Graphic {
|
|||
Graphic::Vector(table) => {
|
||||
metadata.upstream_footprints.insert(element_id, footprint);
|
||||
// TODO: Find a way to handle more than the first row
|
||||
if let Some(row) = table.iter().next() {
|
||||
metadata.first_element_source_id.insert(element_id, *row.source_node_id);
|
||||
metadata.local_transforms.insert(element_id, *row.transform);
|
||||
if !table.is_empty() {
|
||||
let source_node_id: Option<NodeId> = table.attribute_cloned_or_default("source_node_id", 0);
|
||||
let transform: DAffine2 = table.attribute_cloned_or_default("transform", 0);
|
||||
|
||||
metadata.first_element_source_id.insert(element_id, source_node_id);
|
||||
metadata.local_transforms.insert(element_id, transform);
|
||||
}
|
||||
}
|
||||
Graphic::RasterCPU(table) => {
|
||||
metadata.upstream_footprints.insert(element_id, footprint);
|
||||
|
||||
// TODO: Find a way to handle more than the first row
|
||||
if let Some(row) = table.iter().next() {
|
||||
metadata.local_transforms.insert(element_id, *row.transform);
|
||||
if !table.is_empty() {
|
||||
metadata.local_transforms.insert(element_id, table.attribute_cloned_or_default("transform", 0));
|
||||
}
|
||||
}
|
||||
Graphic::RasterGPU(table) => {
|
||||
metadata.upstream_footprints.insert(element_id, footprint);
|
||||
|
||||
// TODO: Find a way to handle more than the first row
|
||||
if let Some(row) = table.iter().next() {
|
||||
metadata.local_transforms.insert(element_id, *row.transform);
|
||||
if !table.is_empty() {
|
||||
metadata.local_transforms.insert(element_id, table.attribute_cloned_or_default("transform", 0));
|
||||
}
|
||||
}
|
||||
Graphic::Color(table) => {
|
||||
metadata.upstream_footprints.insert(element_id, footprint);
|
||||
|
||||
// TODO: Find a way to handle more than the first row
|
||||
if let Some(row) = table.iter().next() {
|
||||
metadata.local_transforms.insert(element_id, *row.transform);
|
||||
if !table.is_empty() {
|
||||
metadata.local_transforms.insert(element_id, table.attribute_cloned_or_default("transform", 0));
|
||||
}
|
||||
}
|
||||
Graphic::Gradient(table) => {
|
||||
metadata.upstream_footprints.insert(element_id, footprint);
|
||||
|
||||
// TODO: Find a way to handle more than the first row
|
||||
if let Some(row) = table.iter().next() {
|
||||
metadata.local_transforms.insert(element_id, *row.transform);
|
||||
if !table.is_empty() {
|
||||
metadata.local_transforms.insert(element_id, table.attribute_cloned_or_default("transform", 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -636,65 +642,69 @@ impl Render for Artboard {
|
|||
|
||||
impl Render for Table<Artboard> {
|
||||
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
|
||||
for artboard in self.iter() {
|
||||
artboard.element.render_svg(render, render_params);
|
||||
for element in self.iter_element_values() {
|
||||
element.render_svg(render, render_params);
|
||||
}
|
||||
}
|
||||
|
||||
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) {
|
||||
for row in self.iter() {
|
||||
row.element.render_to_vello(scene, transform, context, render_params);
|
||||
for element in self.iter_element_values() {
|
||||
element.render_to_vello(scene, transform, context, render_params);
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, _element_id: Option<NodeId>) {
|
||||
for row in self.iter() {
|
||||
row.element.collect_metadata(metadata, footprint, *row.source_node_id);
|
||||
for index in 0..self.len() {
|
||||
let source_node_id: Option<NodeId> = self.attribute_cloned_or_default("source_node_id", index);
|
||||
self.element(index).unwrap().collect_metadata(metadata, footprint, source_node_id);
|
||||
}
|
||||
}
|
||||
|
||||
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
|
||||
for row in self.iter() {
|
||||
row.element.add_upstream_click_targets(click_targets);
|
||||
for element in self.iter_element_values() {
|
||||
element.add_upstream_click_targets(click_targets);
|
||||
}
|
||||
}
|
||||
|
||||
fn contains_artboard(&self) -> bool {
|
||||
self.iter().count() > 0
|
||||
!self.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for Table<Graphic> {
|
||||
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
|
||||
let mut iter = self.iter().peekable();
|
||||
let mut mask_state = None;
|
||||
|
||||
while let Some(row) = iter.next() {
|
||||
for index in 0..self.len() {
|
||||
let transform: DAffine2 = self.attribute_cloned_or_default("transform", index);
|
||||
let alpha_blending: AlphaBlending = self.attribute_cloned_or_default("alpha_blending", index);
|
||||
let element = self.element(index).unwrap();
|
||||
|
||||
render.parent_tag(
|
||||
"g",
|
||||
|attributes| {
|
||||
let matrix = format_transform_matrix(*row.transform);
|
||||
let matrix = format_transform_matrix(transform);
|
||||
if !matrix.is_empty() {
|
||||
attributes.push("transform", matrix);
|
||||
}
|
||||
|
||||
let opacity = row.alpha_blending.opacity(render_params.for_mask);
|
||||
let opacity = alpha_blending.opacity(render_params.for_mask);
|
||||
if opacity < 1. {
|
||||
attributes.push("opacity", opacity.to_string());
|
||||
}
|
||||
|
||||
if row.alpha_blending.blend_mode != BlendMode::default() {
|
||||
attributes.push("style", row.alpha_blending.blend_mode.render());
|
||||
if alpha_blending.blend_mode != BlendMode::default() {
|
||||
attributes.push("style", alpha_blending.blend_mode.render());
|
||||
}
|
||||
|
||||
let next_clips = iter.peek().is_some_and(|next_row| next_row.element.had_clip_enabled());
|
||||
let next_clips = index + 1 < self.len() && self.element(index + 1).unwrap().had_clip_enabled();
|
||||
|
||||
if next_clips && mask_state.is_none() {
|
||||
let uuid = generate_uuid();
|
||||
let mask_type = if row.element.can_reduce_to_clip_path() { MaskType::Clip } else { MaskType::Mask };
|
||||
let mask_type = if element.can_reduce_to_clip_path() { MaskType::Clip } else { MaskType::Mask };
|
||||
mask_state = Some((uuid, mask_type));
|
||||
let mut svg = SvgRender::new();
|
||||
row.element.render_svg(&mut svg, &render_params.for_clipper());
|
||||
element.render_svg(&mut svg, &render_params.for_clipper());
|
||||
|
||||
write!(&mut attributes.0.svg_defs, r##"{}"##, svg.svg_defs).unwrap();
|
||||
mask_type.write_to_defs(&mut attributes.0.svg_defs, uuid, svg.svg.to_svg_string());
|
||||
|
|
@ -710,19 +720,20 @@ impl Render for Table<Graphic> {
|
|||
}
|
||||
},
|
||||
|render| {
|
||||
row.element.render_svg(render, render_params);
|
||||
element.render_svg(render, render_params);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) {
|
||||
let mut iter = self.iter().peekable();
|
||||
let mut mask_element_and_transform = None;
|
||||
|
||||
while let Some(row) = iter.next() {
|
||||
let transform = transform * *row.transform;
|
||||
let alpha_blending = *row.alpha_blending;
|
||||
for index in 0..self.len() {
|
||||
let row_transform: DAffine2 = self.attribute_cloned_or_default("transform", index);
|
||||
let transform = transform * row_transform;
|
||||
let alpha_blending: AlphaBlending = self.attribute_cloned_or_default("alpha_blending", index);
|
||||
let element = self.element(index).unwrap();
|
||||
|
||||
let mut layer = false;
|
||||
|
||||
|
|
@ -732,9 +743,9 @@ impl Render for Table<Graphic> {
|
|||
};
|
||||
let mut bounds = RenderBoundingBox::None;
|
||||
|
||||
let opacity = row.alpha_blending.opacity(render_params.for_mask);
|
||||
let opacity = alpha_blending.opacity(render_params.for_mask);
|
||||
if opacity < 1. || (render_params.render_mode != RenderMode::Outline && alpha_blending.blend_mode != BlendMode::default()) {
|
||||
bounds = row.element.bounding_box(transform, true);
|
||||
bounds = element.bounding_box(transform, true);
|
||||
|
||||
if let RenderBoundingBox::Rectangle(bounds) = bounds {
|
||||
scene.push_layer(
|
||||
|
|
@ -748,17 +759,17 @@ impl Render for Table<Graphic> {
|
|||
}
|
||||
}
|
||||
|
||||
let next_clips = iter.peek().is_some_and(|next_row| next_row.element.had_clip_enabled());
|
||||
let next_clips = index + 1 < self.len() && self.element(index + 1).unwrap().had_clip_enabled();
|
||||
if next_clips && mask_element_and_transform.is_none() {
|
||||
mask_element_and_transform = Some((row.element, transform));
|
||||
mask_element_and_transform = Some((element, transform));
|
||||
|
||||
row.element.render_to_vello(scene, transform, context, render_params);
|
||||
element.render_to_vello(scene, transform, context, render_params);
|
||||
} else if let Some((mask_element, transform_mask)) = mask_element_and_transform {
|
||||
if !next_clips {
|
||||
mask_element_and_transform = None;
|
||||
}
|
||||
if !layer {
|
||||
bounds = row.element.bounding_box(transform, true);
|
||||
bounds = element.bounding_box(transform, true);
|
||||
}
|
||||
|
||||
if let RenderBoundingBox::Rectangle(bounds) = bounds {
|
||||
|
|
@ -775,14 +786,14 @@ impl Render for Table<Graphic> {
|
|||
);
|
||||
}
|
||||
|
||||
row.element.render_to_vello(scene, transform, context, render_params);
|
||||
element.render_to_vello(scene, transform, context, render_params);
|
||||
|
||||
if matches!(bounds, RenderBoundingBox::Rectangle(_)) {
|
||||
scene.pop_layer();
|
||||
scene.pop_layer();
|
||||
}
|
||||
} else {
|
||||
row.element.render_to_vello(scene, transform, context, render_params);
|
||||
element.render_to_vello(scene, transform, context, render_params);
|
||||
}
|
||||
|
||||
if layer {
|
||||
|
|
@ -792,27 +803,33 @@ impl Render for Table<Graphic> {
|
|||
}
|
||||
|
||||
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) {
|
||||
for row in self.iter() {
|
||||
let mut footprint = footprint;
|
||||
footprint.transform *= *row.transform;
|
||||
for index in 0..self.len() {
|
||||
let row_transform: DAffine2 = self.attribute_cloned_or_default("transform", index);
|
||||
let source_node_id: Option<NodeId> = self.attribute_cloned_or_default("source_node_id", index);
|
||||
let element = self.element(index).unwrap();
|
||||
|
||||
if let Some(element_id) = row.source_node_id {
|
||||
row.element.collect_metadata(metadata, footprint, Some(*element_id));
|
||||
let mut footprint = footprint;
|
||||
footprint.transform *= row_transform;
|
||||
|
||||
if let Some(element_id) = source_node_id {
|
||||
element.collect_metadata(metadata, footprint, Some(element_id));
|
||||
} else {
|
||||
// Recurse through anonymous wrapper rows to reach nested content with source_node_ids
|
||||
row.element.collect_metadata(metadata, footprint, None);
|
||||
element.collect_metadata(metadata, footprint, None);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(element_id) = element_id {
|
||||
let mut all_upstream_click_targets = Vec::new();
|
||||
|
||||
for row in self.iter() {
|
||||
for index in 0..self.len() {
|
||||
let row_transform: DAffine2 = self.attribute_cloned_or_default("transform", index);
|
||||
let element = self.element(index).unwrap();
|
||||
let mut new_click_targets = Vec::new();
|
||||
row.element.add_upstream_click_targets(&mut new_click_targets);
|
||||
element.add_upstream_click_targets(&mut new_click_targets);
|
||||
|
||||
for click_target in new_click_targets.iter_mut() {
|
||||
click_target.apply_transform(*row.transform)
|
||||
click_target.apply_transform(row_transform)
|
||||
}
|
||||
|
||||
all_upstream_click_targets.extend(new_click_targets);
|
||||
|
|
@ -823,13 +840,15 @@ impl Render for Table<Graphic> {
|
|||
}
|
||||
|
||||
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
|
||||
for row in self.iter() {
|
||||
for index in 0..self.len() {
|
||||
let row_transform: DAffine2 = self.attribute_cloned_or_default("transform", index);
|
||||
let element = self.element(index).unwrap();
|
||||
let mut new_click_targets = Vec::new();
|
||||
|
||||
row.element.add_upstream_click_targets(&mut new_click_targets);
|
||||
element.add_upstream_click_targets(&mut new_click_targets);
|
||||
|
||||
for click_target in new_click_targets.iter_mut() {
|
||||
click_target.apply_transform(*row.transform)
|
||||
click_target.apply_transform(row_transform)
|
||||
}
|
||||
|
||||
click_targets.extend(new_click_targets);
|
||||
|
|
@ -837,25 +856,28 @@ impl Render for Table<Graphic> {
|
|||
}
|
||||
|
||||
fn contains_artboard(&self) -> bool {
|
||||
self.iter().any(|row| row.element.contains_artboard())
|
||||
self.iter_element_values().any(|element| element.contains_artboard())
|
||||
}
|
||||
|
||||
fn new_ids_from_hash(&mut self, _reference: Option<NodeId>) {
|
||||
for row in self.iter_mut() {
|
||||
row.element.new_ids_from_hash(*row.source_node_id);
|
||||
let (elements, source_node_ids) = self.element_and_attribute_slices_mut::<Option<NodeId>>("source_node_id");
|
||||
for (element, source_node_id) in elements.iter_mut().zip(source_node_ids.iter()) {
|
||||
element.new_ids_from_hash(*source_node_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for Table<Vector> {
|
||||
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
|
||||
for row in self.iter() {
|
||||
let multiplied_transform = *row.transform;
|
||||
let vector = &row.element;
|
||||
for index in 0..self.len() {
|
||||
let Some(vector) = self.element(index) else { continue };
|
||||
let multiplied_transform: DAffine2 = self.attribute_cloned_or_default("transform", index);
|
||||
let alpha_blending: AlphaBlending = self.attribute_cloned_or_default("alpha_blending", index);
|
||||
|
||||
// Only consider strokes with non-zero weight, since default strokes with zero weight would prevent assigning the correct stroke transform
|
||||
let has_real_stroke = vector.style.stroke().filter(|stroke| stroke.weight() > 0.);
|
||||
let set_stroke_transform = has_real_stroke.map(|stroke| stroke.transform).filter(|transform| transform.matrix2.determinant() != 0.);
|
||||
let applied_stroke_transform = set_stroke_transform.unwrap_or(*row.transform);
|
||||
let applied_stroke_transform = set_stroke_transform.unwrap_or(multiplied_transform);
|
||||
let applied_stroke_transform = render_params.alignment_parent_transform.unwrap_or(applied_stroke_transform);
|
||||
let element_transform = set_stroke_transform.map(|stroke_transform| multiplied_transform * stroke_transform.inverse());
|
||||
let element_transform = element_transform.unwrap_or(DAffine2::IDENTITY);
|
||||
|
|
@ -867,7 +889,7 @@ impl Render for Table<Vector> {
|
|||
|
||||
let mut path = String::new();
|
||||
|
||||
for mut bezpath in row.element.stroke_bezpath_iter() {
|
||||
for mut bezpath in vector.stroke_bezpath_iter() {
|
||||
bezpath.apply_affine(Affine::new(applied_stroke_transform.to_cols_array()));
|
||||
path.push_str(bezpath.to_svg().as_str());
|
||||
}
|
||||
|
|
@ -880,7 +902,7 @@ impl Render for Table<Vector> {
|
|||
|
||||
let path_is_closed = vector.stroke_bezier_paths().all(|path| path.closed());
|
||||
let can_draw_aligned_stroke = path_is_closed && vector.style.stroke().is_some_and(|stroke| stroke.has_renderable_stroke() && stroke.align.is_not_centered());
|
||||
let can_use_paint_order = !(row.element.style.fill().is_none() || !row.element.style.fill().is_opaque() || mask_type == MaskType::Clip);
|
||||
let can_use_paint_order = !(vector.style.fill().is_none() || !vector.style.fill().is_opaque() || mask_type == MaskType::Clip);
|
||||
|
||||
let needs_separate_alignment_fill = can_draw_aligned_stroke && !can_use_paint_order;
|
||||
let wants_stroke_below = vector.style.stroke().map(|s| s.paint_order) == Some(PaintOrder::StrokeBelow);
|
||||
|
|
@ -892,7 +914,7 @@ impl Render for Table<Vector> {
|
|||
if !matrix.is_empty() {
|
||||
attributes.push("transform", matrix);
|
||||
}
|
||||
let mut style = row.element.style.clone();
|
||||
let mut style = vector.style.clone();
|
||||
style.clear_stroke();
|
||||
let fill_and_stroke = style.render(
|
||||
&mut attributes.0.svg_defs,
|
||||
|
|
@ -909,16 +931,16 @@ impl Render for Table<Vector> {
|
|||
let push_id = needs_separate_alignment_fill.then_some({
|
||||
let id = format!("alignment-{}", generate_uuid());
|
||||
|
||||
let mut element = row.element.clone();
|
||||
element.style.clear_stroke();
|
||||
element.style.set_fill(Fill::solid(Color::BLACK));
|
||||
let mut cloned_vector = vector.clone();
|
||||
cloned_vector.style.clear_stroke();
|
||||
cloned_vector.style.set_fill(Fill::solid(Color::BLACK));
|
||||
|
||||
let vector_row = Table::new_from_row(TableRow {
|
||||
element,
|
||||
alpha_blending: *row.alpha_blending,
|
||||
transform: *row.transform,
|
||||
source_node_id: None,
|
||||
});
|
||||
let vector_row = Table::new_from_row(
|
||||
TableRow::new_from_element(cloned_vector)
|
||||
.with_attribute("transform", multiplied_transform)
|
||||
.with_attribute("alpha_blending", alpha_blending)
|
||||
.with_attribute("source_node_id", None::<NodeId>),
|
||||
);
|
||||
|
||||
(id, mask_type, vector_row)
|
||||
});
|
||||
|
|
@ -935,7 +957,7 @@ impl Render for Table<Vector> {
|
|||
if !matrix.is_empty() {
|
||||
attributes.push("transform", matrix);
|
||||
}
|
||||
let mut style = row.element.style.clone();
|
||||
let mut style = vector.style.clone();
|
||||
style.clear_stroke();
|
||||
let fill_only = style.render(
|
||||
&mut attributes.0.svg_defs,
|
||||
|
|
@ -961,7 +983,7 @@ impl Render for Table<Vector> {
|
|||
if let Some((ref id, mask_type, ref vector_row)) = push_id {
|
||||
let mut svg = SvgRender::new();
|
||||
vector_row.render_svg(&mut svg, &render_params.for_alignment(applied_stroke_transform));
|
||||
let stroke = row.element.style.stroke().unwrap();
|
||||
let stroke = vector.style.stroke().unwrap();
|
||||
let weight = stroke.effective_width() * max_scale(applied_stroke_transform);
|
||||
let quad = Quad::from_box(transformed_bounds).inflate(weight);
|
||||
let (x, y) = quad.top_left().into();
|
||||
|
|
@ -986,7 +1008,7 @@ impl Render for Table<Vector> {
|
|||
render_params.aligned_strokes = can_draw_aligned_stroke;
|
||||
render_params.override_paint_order = can_draw_aligned_stroke && can_use_paint_order;
|
||||
|
||||
let mut style = row.element.style.clone();
|
||||
let mut style = vector.style.clone();
|
||||
if needs_separate_alignment_fill || use_face_fill {
|
||||
style.clear_fill();
|
||||
}
|
||||
|
|
@ -1003,13 +1025,13 @@ impl Render for Table<Vector> {
|
|||
attributes.push("fill-rule", "evenodd");
|
||||
}
|
||||
|
||||
let opacity = row.alpha_blending.opacity(render_params.for_mask);
|
||||
let opacity = alpha_blending.opacity(render_params.for_mask);
|
||||
if opacity < 1. {
|
||||
attributes.push("opacity", opacity.to_string());
|
||||
}
|
||||
|
||||
if row.alpha_blending.blend_mode != BlendMode::default() {
|
||||
attributes.push("style", row.alpha_blending.blend_mode.render());
|
||||
if alpha_blending.blend_mode != BlendMode::default() {
|
||||
attributes.push("style", alpha_blending.blend_mode.render());
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -1021,7 +1043,7 @@ impl Render for Table<Vector> {
|
|||
if !matrix.is_empty() {
|
||||
attributes.push("transform", matrix);
|
||||
}
|
||||
let mut style = row.element.style.clone();
|
||||
let mut style = vector.style.clone();
|
||||
style.clear_stroke();
|
||||
let fill_and_stroke = style.render(
|
||||
&mut attributes.0.svg_defs,
|
||||
|
|
@ -1040,11 +1062,14 @@ impl Render for Table<Vector> {
|
|||
fn render_to_vello(&self, scene: &mut Scene, parent_transform: DAffine2, _context: &mut RenderContext, render_params: &RenderParams) {
|
||||
use graphic_types::vector_types::vector::style::{GradientType, StrokeCap, StrokeJoin};
|
||||
|
||||
for row in self.iter() {
|
||||
for index in 0..self.len() {
|
||||
use graphic_types::vector_types::vector;
|
||||
|
||||
let multiplied_transform = parent_transform * *row.transform;
|
||||
let has_real_stroke = row.element.style.stroke().filter(|stroke| stroke.weight() > 0.);
|
||||
let Some(element) = self.element(index) else { continue };
|
||||
let row_transform: DAffine2 = self.attribute_cloned_or_default("transform", index);
|
||||
let alpha_blending: AlphaBlending = self.attribute_cloned_or_default("alpha_blending", index);
|
||||
let multiplied_transform = parent_transform * row_transform;
|
||||
let has_real_stroke = element.style.stroke().filter(|stroke| stroke.weight() > 0.);
|
||||
let set_stroke_transform = has_real_stroke.map(|stroke| stroke.transform).filter(|transform| transform.matrix2.determinant() != 0.);
|
||||
let mut applied_stroke_transform = set_stroke_transform.unwrap_or(multiplied_transform);
|
||||
let mut element_transform = set_stroke_transform
|
||||
|
|
@ -1058,11 +1083,11 @@ impl Render for Table<Vector> {
|
|||
multiplied_transform
|
||||
};
|
||||
}
|
||||
let layer_bounds = row.element.bounding_box().unwrap_or_default();
|
||||
let layer_bounds = element.bounding_box().unwrap_or_default();
|
||||
|
||||
let to_point = |p: DVec2| kurbo::Point::new(p.x, p.y);
|
||||
let mut path = kurbo::BezPath::new();
|
||||
for mut bezpath in row.element.stroke_bezpath_iter() {
|
||||
for mut bezpath in element.stroke_bezpath_iter() {
|
||||
bezpath.apply_affine(Affine::new(applied_stroke_transform.to_cols_array()));
|
||||
for element in bezpath {
|
||||
path.push(element);
|
||||
|
|
@ -1072,14 +1097,14 @@ impl Render for Table<Vector> {
|
|||
// If we're using opacity or a blend mode, we need to push a layer
|
||||
let blend_mode = match render_params.render_mode {
|
||||
RenderMode::Outline => peniko::Mix::Normal,
|
||||
_ => row.alpha_blending.blend_mode.to_peniko(),
|
||||
_ => alpha_blending.blend_mode.to_peniko(),
|
||||
};
|
||||
let mut layer = false;
|
||||
|
||||
let opacity = row.alpha_blending.opacity(render_params.for_mask);
|
||||
if opacity < 1. || row.alpha_blending.blend_mode != BlendMode::default() {
|
||||
let opacity = alpha_blending.opacity(render_params.for_mask);
|
||||
if opacity < 1. || alpha_blending.blend_mode != BlendMode::default() {
|
||||
layer = true;
|
||||
let weight = row.element.style.stroke().as_ref().map_or(0., Stroke::effective_width);
|
||||
let weight = element.style.stroke().as_ref().map_or(0., Stroke::effective_width);
|
||||
let quad = Quad::from_box(layer_bounds).inflate(weight * max_scale(applied_stroke_transform));
|
||||
let layer_bounds = quad.bounding_box();
|
||||
scene.push_layer(
|
||||
|
|
@ -1092,13 +1117,13 @@ impl Render for Table<Vector> {
|
|||
}
|
||||
|
||||
let can_draw_aligned_stroke =
|
||||
row.element.style.stroke().is_some_and(|stroke| stroke.has_renderable_stroke() && stroke.align.is_not_centered()) && row.element.stroke_bezier_paths().all(|path| path.closed());
|
||||
element.style.stroke().is_some_and(|stroke| stroke.has_renderable_stroke() && stroke.align.is_not_centered()) && element.stroke_bezier_paths().all(|path| path.closed());
|
||||
|
||||
let use_layer = can_draw_aligned_stroke;
|
||||
let wants_stroke_below = row.element.style.stroke().is_some_and(|s| s.paint_order == vector::style::PaintOrder::StrokeBelow);
|
||||
let wants_stroke_below = element.style.stroke().is_some_and(|s| s.paint_order == vector::style::PaintOrder::StrokeBelow);
|
||||
|
||||
// Closures to avoid duplicated fill/stroke drawing logic
|
||||
let do_fill_path = |scene: &mut Scene, path: &kurbo::BezPath, fill_rule: peniko::Fill| match row.element.style.fill() {
|
||||
let do_fill_path = |scene: &mut Scene, path: &kurbo::BezPath, fill_rule: peniko::Fill| match element.style.fill() {
|
||||
Fill::Solid(color) => {
|
||||
let fill = peniko::Brush::Solid(peniko::Color::new([color.r(), color.g(), color.b(), color.a()]));
|
||||
scene.fill(fill_rule, kurbo::Affine::new(element_transform.to_cols_array()), &fill, None, path);
|
||||
|
|
@ -1112,7 +1137,7 @@ impl Render for Table<Vector> {
|
|||
});
|
||||
}
|
||||
|
||||
let bounds = row.element.nonzero_bounding_box();
|
||||
let bounds = element.nonzero_bounding_box();
|
||||
let bound_transform = DAffine2::from_scale_angle_translation(bounds[1] - bounds[0], 0., bounds[0]);
|
||||
|
||||
let inverse_parent_transform = if parent_transform.matrix2.determinant() != 0. {
|
||||
|
|
@ -1164,10 +1189,10 @@ impl Render for Table<Vector> {
|
|||
};
|
||||
|
||||
// Branching vectors without regions (e.g. mesh grids) need face-by-face fill rendering.
|
||||
let use_face_fill = row.element.use_face_fill();
|
||||
let use_face_fill = element.use_face_fill();
|
||||
let do_fill = |scene: &mut Scene| {
|
||||
if use_face_fill {
|
||||
for mut face_path in row.element.construct_faces().filter(|face| face.area() >= 0.) {
|
||||
for mut face_path in element.construct_faces().filter(|face| face.area() >= 0.) {
|
||||
face_path.apply_affine(Affine::new(applied_stroke_transform.to_cols_array()));
|
||||
let mut kurbo_path = kurbo::BezPath::new();
|
||||
for element in face_path {
|
||||
|
|
@ -1175,7 +1200,7 @@ impl Render for Table<Vector> {
|
|||
}
|
||||
do_fill_path(scene, &kurbo_path, peniko::Fill::NonZero);
|
||||
}
|
||||
} else if row.element.is_branching() {
|
||||
} else if element.is_branching() {
|
||||
do_fill_path(scene, &path, peniko::Fill::EvenOdd);
|
||||
} else {
|
||||
do_fill_path(scene, &path, peniko::Fill::NonZero);
|
||||
|
|
@ -1183,7 +1208,7 @@ impl Render for Table<Vector> {
|
|||
};
|
||||
|
||||
let do_stroke = |scene: &mut Scene, width_scale: f64| {
|
||||
if let Some(stroke) = row.element.style.stroke() {
|
||||
if let Some(stroke) = element.style.stroke() {
|
||||
let color = match stroke.color {
|
||||
Some(color) => peniko::Color::new([color.r(), color.g(), color.b(), color.a()]),
|
||||
None => peniko::Color::TRANSPARENT,
|
||||
|
|
@ -1224,24 +1249,24 @@ impl Render for Table<Vector> {
|
|||
}
|
||||
_ => {
|
||||
if use_layer {
|
||||
let mut element = row.element.clone();
|
||||
element.style.clear_stroke();
|
||||
element.style.set_fill(Fill::solid(Color::BLACK));
|
||||
let mut cloned_element = element.clone();
|
||||
cloned_element.style.clear_stroke();
|
||||
cloned_element.style.set_fill(Fill::solid(Color::BLACK));
|
||||
|
||||
let vector_table = Table::new_from_row(TableRow {
|
||||
element,
|
||||
alpha_blending: *row.alpha_blending,
|
||||
transform: *row.transform,
|
||||
source_node_id: None,
|
||||
});
|
||||
let vector_table = Table::new_from_row(
|
||||
TableRow::new_from_element(cloned_element)
|
||||
.with_attribute("transform", row_transform)
|
||||
.with_attribute("alpha_blending", alpha_blending)
|
||||
.with_attribute("source_node_id", None::<NodeId>),
|
||||
);
|
||||
|
||||
let bounds = row.element.bounding_box_with_transform(multiplied_transform).unwrap_or(layer_bounds);
|
||||
let weight = row.element.style.stroke().as_ref().map_or(0., Stroke::effective_width);
|
||||
let bounds = element.bounding_box_with_transform(multiplied_transform).unwrap_or(layer_bounds);
|
||||
let weight = element.style.stroke().as_ref().map_or(0., Stroke::effective_width);
|
||||
let quad = Quad::from_box(bounds).inflate(weight * max_scale(applied_stroke_transform));
|
||||
let bounds = quad.bounding_box();
|
||||
let rect = kurbo::Rect::new(bounds[0].x, bounds[0].y, bounds[1].x, bounds[1].y);
|
||||
|
||||
let compose = if row.element.style.stroke().is_some_and(|x| x.align == StrokeAlign::Outside) {
|
||||
let compose = if element.style.stroke().is_some_and(|x| x.align == StrokeAlign::Outside) {
|
||||
peniko::Compose::SrcOut
|
||||
} else {
|
||||
peniko::Compose::SrcIn
|
||||
|
|
@ -1278,7 +1303,7 @@ impl Render for Table<Vector> {
|
|||
Stroke,
|
||||
}
|
||||
|
||||
let order = match row.element.style.stroke().is_some_and(|stroke| !stroke.paint_order.is_default()) {
|
||||
let order = match element.style.stroke().is_some_and(|stroke| !stroke.paint_order.is_default()) {
|
||||
true => [Op::Stroke, Op::Fill],
|
||||
false => [Op::Fill, Op::Stroke], // Default
|
||||
};
|
||||
|
|
@ -1301,11 +1326,12 @@ impl Render for Table<Vector> {
|
|||
}
|
||||
|
||||
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, caller_element_id: Option<NodeId>) {
|
||||
for row in self.iter() {
|
||||
let transform = *row.transform;
|
||||
let vector = row.element;
|
||||
for index in 0..self.len() {
|
||||
let Some(vector) = self.element(index) else { continue };
|
||||
let transform: DAffine2 = self.attribute_cloned_or_default("transform", index);
|
||||
let source_node_id: Option<NodeId> = self.attribute_cloned_or_default("source_node_id", index);
|
||||
|
||||
if let Some(element_id) = caller_element_id.or(*row.source_node_id) {
|
||||
if let Some(element_id) = caller_element_id.or(source_node_id) {
|
||||
// When recovering element_id from the row's source_node_id (because the caller
|
||||
// passed None), also store the transform metadata that Graphic::collect_metadata
|
||||
// normally provides but skipped due to the None element_id.
|
||||
|
|
@ -1356,32 +1382,35 @@ impl Render for Table<Vector> {
|
|||
}
|
||||
|
||||
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
|
||||
for row in self.iter() {
|
||||
let stroke_width = row.element.style.stroke().as_ref().map_or(0., Stroke::effective_width);
|
||||
let filled = row.element.style.fill() != &Fill::None;
|
||||
for index in 0..self.len() {
|
||||
let Some(vector) = self.element(index) else { continue };
|
||||
let transform: DAffine2 = self.attribute_cloned_or_default("transform", index);
|
||||
|
||||
let stroke_width = vector.style.stroke().as_ref().map_or(0., Stroke::effective_width);
|
||||
let filled = vector.style.fill() != &Fill::None;
|
||||
let fill = |mut subpath: Subpath<_>| {
|
||||
if filled {
|
||||
subpath.set_closed(true);
|
||||
}
|
||||
subpath
|
||||
};
|
||||
click_targets.extend(row.element.stroke_bezier_paths().map(fill).map(|subpath| {
|
||||
click_targets.extend(vector.stroke_bezier_paths().map(fill).map(|subpath| {
|
||||
let mut click_target = ClickTarget::new_with_subpath(subpath, stroke_width);
|
||||
click_target.apply_transform(*row.transform);
|
||||
click_target.apply_transform(transform);
|
||||
click_target
|
||||
}));
|
||||
|
||||
// For free-floating anchors, we need to add a click target for each
|
||||
let single_anchors_targets = row.element.point_domain.ids().iter().filter_map(|&point_id| {
|
||||
if row.element.any_connected(point_id) {
|
||||
let single_anchors_targets = vector.point_domain.ids().iter().filter_map(|&point_id| {
|
||||
if vector.any_connected(point_id) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let anchor = row.element.point_domain.position_from_id(point_id).unwrap_or_default();
|
||||
let anchor = vector.point_domain.position_from_id(point_id).unwrap_or_default();
|
||||
let point = FreePoint::new(point_id, anchor);
|
||||
|
||||
let mut click_target = ClickTarget::new_with_free_point(point);
|
||||
click_target.apply_transform(*row.transform);
|
||||
click_target.apply_transform(transform);
|
||||
Some(click_target)
|
||||
});
|
||||
click_targets.extend(single_anchors_targets);
|
||||
|
|
@ -1389,18 +1418,19 @@ impl Render for Table<Vector> {
|
|||
}
|
||||
|
||||
fn new_ids_from_hash(&mut self, reference: Option<NodeId>) {
|
||||
for row in self.iter_mut() {
|
||||
row.element.vector_new_ids_from_hash(reference.map(|id| id.0).unwrap_or_default());
|
||||
for vector in self.iter_element_values_mut() {
|
||||
vector.vector_new_ids_from_hash(reference.map(|id| id.0).unwrap_or_default());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for Table<Raster<CPU>> {
|
||||
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
|
||||
for row in self.iter() {
|
||||
let image = row.element;
|
||||
for index in 0..self.len() {
|
||||
let Some(image) = self.element(index) else { continue };
|
||||
|
||||
let transform = *row.transform;
|
||||
let transform: DAffine2 = self.attribute_cloned_or_default("transform", index);
|
||||
let alpha_blending: AlphaBlending = self.attribute_cloned_or_default("alpha_blending", index);
|
||||
|
||||
if image.data.is_empty() {
|
||||
continue;
|
||||
|
|
@ -1427,13 +1457,13 @@ impl Render for Table<Raster<CPU>> {
|
|||
attributes.push("width", size.x.to_string());
|
||||
attributes.push("height", size.y.to_string());
|
||||
|
||||
let opacity = row.alpha_blending.opacity(render_params.for_mask);
|
||||
let opacity = alpha_blending.opacity(render_params.for_mask);
|
||||
if opacity < 1. {
|
||||
attributes.push("opacity", opacity.to_string());
|
||||
}
|
||||
|
||||
if row.alpha_blending.blend_mode != BlendMode::default() {
|
||||
attributes.push("style", row.alpha_blending.blend_mode.render());
|
||||
if alpha_blending.blend_mode != BlendMode::default() {
|
||||
attributes.push("style", alpha_blending.blend_mode.render());
|
||||
}
|
||||
},
|
||||
|render| {
|
||||
|
|
@ -1467,12 +1497,12 @@ impl Render for Table<Raster<CPU>> {
|
|||
attributes.push("transform", matrix);
|
||||
}
|
||||
|
||||
let opacity = row.alpha_blending.opacity(render_params.for_mask);
|
||||
let opacity = alpha_blending.opacity(render_params.for_mask);
|
||||
if opacity < 1. {
|
||||
attributes.push("opacity", opacity.to_string());
|
||||
}
|
||||
if row.alpha_blending.blend_mode != BlendMode::default() {
|
||||
attributes.push("style", row.alpha_blending.blend_mode.render());
|
||||
if alpha_blending.blend_mode != BlendMode::default() {
|
||||
attributes.push("style", alpha_blending.blend_mode.render());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -1480,13 +1510,13 @@ impl Render for Table<Raster<CPU>> {
|
|||
}
|
||||
|
||||
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, _: &mut RenderContext, render_params: &RenderParams) {
|
||||
for row in self.iter() {
|
||||
let image = &row.element;
|
||||
for index in 0..self.len() {
|
||||
let Some(image) = self.element(index) else { continue };
|
||||
if image.data.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let alpha_blending = *row.alpha_blending;
|
||||
let alpha_blending: AlphaBlending = self.attribute_cloned_or_default("alpha_blending", index);
|
||||
let blend_mode = alpha_blending.blend_mode.to_peniko();
|
||||
|
||||
let opacity = alpha_blending.opacity(render_params.for_mask);
|
||||
|
|
@ -1501,8 +1531,10 @@ impl Render for Table<Raster<CPU>> {
|
|||
layer = true;
|
||||
}
|
||||
|
||||
let transform_attribute: DAffine2 = self.attribute_cloned_or_default("transform", index);
|
||||
|
||||
if let RenderMode::Outline = render_params.render_mode {
|
||||
let outline_transform = transform * *row.transform;
|
||||
let outline_transform: DAffine2 = transform * transform_attribute;
|
||||
draw_raster_outline(scene, &outline_transform, render_params);
|
||||
|
||||
if layer {
|
||||
|
|
@ -1512,7 +1544,7 @@ impl Render for Table<Raster<CPU>> {
|
|||
continue;
|
||||
}
|
||||
|
||||
let image_transform = transform * *row.transform * DAffine2::from_scale(1. / DVec2::new(image.width as f64, image.height as f64));
|
||||
let image_transform = transform * transform_attribute * DAffine2::from_scale(1. / DVec2::new(image.width as f64, image.height as f64));
|
||||
|
||||
let image_brush = peniko::ImageBrush::new(peniko::ImageData {
|
||||
data: image.to_flat_u8().0.into(),
|
||||
|
|
@ -1538,8 +1570,8 @@ impl Render for Table<Raster<CPU>> {
|
|||
metadata.click_targets.insert(element_id, vec![ClickTarget::new_with_subpath(subpath, 0.).into()]);
|
||||
metadata.upstream_footprints.insert(element_id, footprint);
|
||||
// TODO: Find a way to handle more than one row of the raster table
|
||||
if let Some(raster) = self.iter().next() {
|
||||
metadata.local_transforms.insert(element_id, *raster.transform);
|
||||
if !self.is_empty() {
|
||||
metadata.local_transforms.insert(element_id, self.attribute_cloned_or_default("transform", 0));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1557,8 +1589,9 @@ impl Render for Table<Raster<GPU>> {
|
|||
}
|
||||
|
||||
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) {
|
||||
for row in self.iter() {
|
||||
let alpha_blending = *row.alpha_blending;
|
||||
for index in 0..self.len() {
|
||||
let Some(raster) = self.element(index) else { continue };
|
||||
let alpha_blending: AlphaBlending = self.attribute_cloned_or_default("alpha_blending", index);
|
||||
let blend_mode = match render_params.render_mode {
|
||||
RenderMode::Outline => peniko::Mix::Normal,
|
||||
_ => alpha_blending.blend_mode.to_peniko(),
|
||||
|
|
@ -1575,8 +1608,10 @@ impl Render for Table<Raster<GPU>> {
|
|||
layer = true;
|
||||
}
|
||||
|
||||
let transform_attribute: DAffine2 = self.attribute_cloned_or_default("transform", index);
|
||||
|
||||
if let RenderMode::Outline = render_params.render_mode {
|
||||
let outline_transform = transform * *row.transform;
|
||||
let outline_transform = transform * transform_attribute;
|
||||
draw_raster_outline(scene, &outline_transform, render_params);
|
||||
|
||||
if layer {
|
||||
|
|
@ -1586,8 +1621,8 @@ impl Render for Table<Raster<GPU>> {
|
|||
continue;
|
||||
}
|
||||
|
||||
let width = row.element.data().width();
|
||||
let height = row.element.data().height();
|
||||
let width = raster.data().width();
|
||||
let height = raster.data().height();
|
||||
let image = peniko::ImageBrush::new(peniko::ImageData {
|
||||
data: peniko::Blob::new(LAZY_ARC_VEC_ZERO_U8.deref().clone()),
|
||||
format: peniko::ImageFormat::Rgba8,
|
||||
|
|
@ -1596,9 +1631,9 @@ impl Render for Table<Raster<GPU>> {
|
|||
alpha_type: peniko::ImageAlphaType::Alpha,
|
||||
})
|
||||
.with_extend(peniko::Extend::Repeat);
|
||||
let image_transform = transform * *row.transform * DAffine2::from_scale(1. / DVec2::new(width as f64, height as f64));
|
||||
let image_transform = transform * transform_attribute * DAffine2::from_scale(1. / DVec2::new(width as f64, height as f64));
|
||||
scene.draw_image(&image, kurbo::Affine::new(image_transform.to_cols_array()));
|
||||
context.resource_overrides.push((image, row.element.data().clone()));
|
||||
context.resource_overrides.push((image, raster.data().clone()));
|
||||
|
||||
if layer {
|
||||
scene.pop_layer()
|
||||
|
|
@ -1613,8 +1648,8 @@ impl Render for Table<Raster<GPU>> {
|
|||
metadata.click_targets.insert(element_id, vec![ClickTarget::new_with_subpath(subpath, 0.).into()]);
|
||||
metadata.upstream_footprints.insert(element_id, footprint);
|
||||
// TODO: Find a way to handle more than one row of the raster table
|
||||
if let Some(raster) = self.iter().next() {
|
||||
metadata.local_transforms.insert(element_id, *raster.transform);
|
||||
if !self.is_empty() {
|
||||
metadata.local_transforms.insert(element_id, self.attribute_cloned_or_default("transform", 0));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1632,25 +1667,24 @@ impl Render for Table<Raster<GPU>> {
|
|||
// later replace with the current viewport transform before each render.
|
||||
impl Render for Table<Color> {
|
||||
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
|
||||
for row in self.iter() {
|
||||
for (color, alpha_blending) in self.iter_element_values().zip(self.iter_attribute_values_or_default::<AlphaBlending>("alpha_blending")) {
|
||||
render.leaf_tag("polyline", |attributes| {
|
||||
// Chrome doesn't like drawing centered rectangles bigger than ~20 million so we draw a polyline quad instead
|
||||
let max = u64::MAX;
|
||||
attributes.push("points", format!("{max},{max} -{max},{max} -{max},-{max} {max},-{max}"));
|
||||
|
||||
let color = row.element;
|
||||
attributes.push("fill", format!("#{}", color.to_rgb_hex_srgb_from_gamma()));
|
||||
if color.a() < 1. {
|
||||
attributes.push("fill-opacity", ((color.a() * 1000.).round() / 1000.).to_string());
|
||||
}
|
||||
|
||||
let opacity = row.alpha_blending.opacity(render_params.for_mask);
|
||||
let opacity = alpha_blending.opacity(render_params.for_mask);
|
||||
if opacity < 1. {
|
||||
attributes.push("opacity", opacity.to_string());
|
||||
}
|
||||
|
||||
if row.alpha_blending.blend_mode != BlendMode::default() {
|
||||
attributes.push("style", row.alpha_blending.blend_mode.render());
|
||||
if alpha_blending.blend_mode != BlendMode::default() {
|
||||
attributes.push("style", alpha_blending.blend_mode.render());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -1659,12 +1693,10 @@ impl Render for Table<Color> {
|
|||
fn render_to_vello(&self, scene: &mut Scene, _parent_transform: DAffine2, _context: &mut RenderContext, render_params: &RenderParams) {
|
||||
use vello::peniko;
|
||||
|
||||
for row in self.iter() {
|
||||
let alpha_blending = *row.alpha_blending;
|
||||
for (color, alpha_blending) in self.iter_element_values().zip(self.iter_attribute_values_or_default::<AlphaBlending>("alpha_blending")) {
|
||||
let blend_mode = alpha_blending.blend_mode.to_peniko();
|
||||
let opacity = alpha_blending.opacity(render_params.for_mask);
|
||||
|
||||
let color = row.element;
|
||||
let vello_color = peniko::Color::new([color.r(), color.g(), color.b(), color.a()]);
|
||||
|
||||
let rect = kurbo::Rect::from_origin_size(kurbo::Point::ZERO, kurbo::Size::new(1., 1.));
|
||||
|
|
@ -1688,14 +1720,17 @@ impl Render for Table<Color> {
|
|||
impl Render for Table<GradientStops> {
|
||||
// TODO: Fix infinite gradient rendering
|
||||
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
|
||||
for row in self.iter() {
|
||||
for index in 0..self.len() {
|
||||
let Some(gradient) = self.element(index) else { continue };
|
||||
let transform: DAffine2 = self.attribute_cloned_or_default("transform", index);
|
||||
let alpha_blending: AlphaBlending = self.attribute_cloned_or_default("alpha_blending", index);
|
||||
render.leaf_tag("rect", |attributes| {
|
||||
// Chrome doesn't like drawing centered rectangles bigger than ~20 million so we draw a polyline quad instead
|
||||
let max = u64::MAX;
|
||||
attributes.push("points", format!("{max},{max} -{max},{max} -{max},-{max} {max},-{max}"));
|
||||
|
||||
let mut stop_string = String::new();
|
||||
for (position, color, original_midpoint) in row.element.interpolated_samples() {
|
||||
for (position, color, original_midpoint) in gradient.interpolated_samples() {
|
||||
let _ = write!(stop_string, r##"<stop offset="{}" stop-color="#{}""##, position, color.to_rgb_hex_srgb_from_gamma());
|
||||
if color.a() < 1. {
|
||||
let _ = write!(stop_string, r#" stop-opacity="{}""#, color.a());
|
||||
|
|
@ -1706,7 +1741,7 @@ impl Render for Table<GradientStops> {
|
|||
stop_string.push_str(" />");
|
||||
}
|
||||
|
||||
let gradient_transform = render_params.footprint.transform * *row.transform;
|
||||
let gradient_transform = render_params.footprint.transform * transform;
|
||||
let gradient_transform_matrix = format_transform_matrix(gradient_transform);
|
||||
let gradient_transform_attribute = if gradient_transform_matrix.is_empty() {
|
||||
String::new()
|
||||
|
|
@ -1739,13 +1774,13 @@ impl Render for Table<GradientStops> {
|
|||
|
||||
attributes.push("fill", format!("url('#{gradient_id}')"));
|
||||
|
||||
let opacity = row.alpha_blending.opacity(render_params.for_mask);
|
||||
let opacity = alpha_blending.opacity(render_params.for_mask);
|
||||
if opacity < 1. {
|
||||
attributes.push("opacity", opacity.to_string());
|
||||
}
|
||||
|
||||
if row.alpha_blending.blend_mode != BlendMode::default() {
|
||||
attributes.push("style", row.alpha_blending.blend_mode.render());
|
||||
if alpha_blending.blend_mode != BlendMode::default() {
|
||||
attributes.push("style", alpha_blending.blend_mode.render());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -1755,12 +1790,11 @@ impl Render for Table<GradientStops> {
|
|||
fn render_to_vello(&self, scene: &mut Scene, _parent_transform: DAffine2, _context: &mut RenderContext, render_params: &RenderParams) {
|
||||
use vello::peniko;
|
||||
|
||||
for row in self.iter() {
|
||||
let alpha_blending = *row.alpha_blending;
|
||||
for (gradient, alpha_blending) in self.iter_element_values().zip(self.iter_attribute_values_or_default::<AlphaBlending>("alpha_blending")) {
|
||||
let blend_mode = alpha_blending.blend_mode.to_peniko();
|
||||
let opacity = alpha_blending.opacity(render_params.for_mask);
|
||||
|
||||
let color = row.element.color.first().copied().unwrap_or(Color::MAGENTA);
|
||||
let color = gradient.color.first().copied().unwrap_or(Color::MAGENTA);
|
||||
let vello_color = peniko::Color::new([color.r(), color.g(), color.b(), color.a()]);
|
||||
|
||||
let rect = kurbo::Rect::from_origin_size(kurbo::Point::ZERO, kurbo::Size::new(1., 1.));
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ license = "MIT OR Apache-2.0"
|
|||
|
||||
[features]
|
||||
default = ["serde"]
|
||||
serde = ["dep:serde", "core-types/serde"]
|
||||
wasm = ["core-types/wasm", "tsify", "wasm-bindgen"]
|
||||
|
||||
[dependencies]
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@ use dyn_any::DynAny;
|
|||
use glam::{DAffine2, DVec2};
|
||||
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Default, PartialEq, Eq, Clone, Copy, Debug, Hash, graphene_hash::CacheHash, serde::Serialize, serde::Deserialize, DynAny, node_macro::ChoiceType)]
|
||||
#[derive(Default, PartialEq, Eq, Clone, Copy, Debug, Hash, graphene_hash::CacheHash, DynAny, node_macro::ChoiceType)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[widget(Radio)]
|
||||
pub enum GradientType {
|
||||
#[default]
|
||||
|
|
@ -15,7 +16,8 @@ pub enum GradientType {
|
|||
// TODO: Use linear not gamma colors
|
||||
/// A list of colors associated with positions (in the range 0 to 1) along a gradient.
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Debug, Clone, PartialEq, graphene_hash::CacheHash, serde::Serialize, DynAny)]
|
||||
#[derive(Debug, Clone, PartialEq, graphene_hash::CacheHash, DynAny)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
||||
pub struct GradientStops {
|
||||
/// The position of this stop, a factor from 0-1 along the length of the full gradient.
|
||||
pub position: Vec<f64>,
|
||||
|
|
@ -36,7 +38,7 @@ impl<'de> serde::Deserialize<'de> for GradientStops {
|
|||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
#[serde(untagged)]
|
||||
#[cfg_attr(feature = "serde", serde(untagged))]
|
||||
enum GradientStopsFormat {
|
||||
New(NewFormat),
|
||||
Old(Vec<(f64, Color)>),
|
||||
|
|
@ -325,7 +327,8 @@ impl GradientStops {
|
|||
|
||||
#[repr(C)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Default, PartialEq, Eq, Clone, Copy, Debug, Hash, graphene_hash::CacheHash, serde::Serialize, serde::Deserialize, DynAny, node_macro::ChoiceType)]
|
||||
#[derive(Default, PartialEq, Eq, Clone, Copy, Debug, Hash, graphene_hash::CacheHash, DynAny, node_macro::ChoiceType)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[widget(Radio)]
|
||||
pub enum GradientSpreadMethod {
|
||||
#[default]
|
||||
|
|
@ -349,13 +352,14 @@ impl GradientSpreadMethod {
|
|||
/// Contains the start and end points, along with the colors at varying points along the length.
|
||||
#[repr(C)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Debug, Clone, PartialEq, graphene_hash::CacheHash, serde::Serialize, serde::Deserialize, DynAny)]
|
||||
#[derive(Debug, Clone, PartialEq, graphene_hash::CacheHash, DynAny)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct Gradient {
|
||||
pub stops: GradientStops,
|
||||
pub gradient_type: GradientType,
|
||||
pub start: DVec2,
|
||||
pub end: DVec2,
|
||||
#[serde(default)]
|
||||
#[cfg_attr(feature = "serde", serde(default))]
|
||||
pub spread_method: GradientSpreadMethod,
|
||||
}
|
||||
|
||||
|
|
@ -468,7 +472,7 @@ pub fn migrate_gradient_stops<'de, D: serde::Deserializer<'de>>(deserializer: D)
|
|||
use serde::Deserialize;
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
#[serde(untagged)]
|
||||
#[cfg_attr(feature = "serde", serde(untagged))]
|
||||
enum GradientStopsFormat {
|
||||
GradientStops(GradientStops),
|
||||
GradientTable(Table<GradientStops>),
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@ use kurbo::{Affine, BezPath, ParamCurve, PathSeg, Shape};
|
|||
|
||||
type BoundingBox = Option<[DVec2; 2]>;
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct FreePoint {
|
||||
pub id: PointId,
|
||||
pub position: DVec2,
|
||||
|
|
@ -29,7 +30,8 @@ impl FreePoint {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum ClickTargetType {
|
||||
Subpath(Subpath<PointId>),
|
||||
FreePoint(FreePoint),
|
||||
|
|
@ -115,12 +117,13 @@ impl BoundingBoxCache {
|
|||
}
|
||||
|
||||
/// Represents a clickable target for the layer
|
||||
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct ClickTarget {
|
||||
target_type: ClickTargetType,
|
||||
stroke_width: f64,
|
||||
bounding_box: BoundingBox,
|
||||
#[serde(skip)]
|
||||
#[cfg_attr(feature = "serde", serde(skip))]
|
||||
bounding_box_cache: Arc<RwLock<BoundingBoxCache>>,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@ use kurbo::{BezPath, CubicBez, Line, ParamCurve, ParamCurveDeriv, PathSeg, Point
|
|||
use std::ops::Sub;
|
||||
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, node_macro::ChoiceType)]
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash, DynAny, node_macro::ChoiceType)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[widget(Radio)]
|
||||
pub enum BooleanOperation {
|
||||
#[default]
|
||||
|
|
@ -26,7 +27,8 @@ pub enum BooleanOperation {
|
|||
|
||||
/// Represents different geometric interpretations of calculating the centroid (center of mass).
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, node_macro::ChoiceType)]
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash, DynAny, node_macro::ChoiceType)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[widget(Radio)]
|
||||
pub enum CentroidType {
|
||||
/// The center of mass for the area of a solid shape's interior, as if made out of an infinitely flat material.
|
||||
|
|
@ -38,7 +40,8 @@ pub enum CentroidType {
|
|||
|
||||
#[repr(C)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, node_macro::ChoiceType)]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, DynAny, node_macro::ChoiceType)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[widget(Radio)]
|
||||
pub enum RowsOrColumns {
|
||||
#[default]
|
||||
|
|
@ -85,7 +88,8 @@ impl AsI64 for f64 {
|
|||
}
|
||||
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, node_macro::ChoiceType)]
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash, DynAny, node_macro::ChoiceType)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[widget(Radio)]
|
||||
pub enum GridType {
|
||||
#[default]
|
||||
|
|
@ -95,7 +99,8 @@ pub enum GridType {
|
|||
|
||||
#[repr(C)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, node_macro::ChoiceType)]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, DynAny, node_macro::ChoiceType)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[widget(Radio)]
|
||||
pub enum ArcType {
|
||||
#[default]
|
||||
|
|
@ -106,7 +111,8 @@ pub enum ArcType {
|
|||
|
||||
#[repr(C)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, node_macro::ChoiceType)]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, DynAny, node_macro::ChoiceType)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[widget(Radio)]
|
||||
pub enum MergeByDistanceAlgorithm {
|
||||
#[default]
|
||||
|
|
@ -116,7 +122,8 @@ pub enum MergeByDistanceAlgorithm {
|
|||
|
||||
#[repr(C)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, node_macro::ChoiceType)]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, DynAny, node_macro::ChoiceType)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[widget(Radio)]
|
||||
pub enum ExtrudeJoiningAlgorithm {
|
||||
All,
|
||||
|
|
@ -127,7 +134,8 @@ pub enum ExtrudeJoiningAlgorithm {
|
|||
|
||||
#[repr(C)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, node_macro::ChoiceType)]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, DynAny, node_macro::ChoiceType)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[widget(Radio)]
|
||||
pub enum PointSpacingType {
|
||||
#[default]
|
||||
|
|
@ -368,7 +376,8 @@ impl Tangent for kurbo::PathSeg {
|
|||
}
|
||||
|
||||
/// A selectable part of a curve, either an anchor (start or end of a bézier) or a handle (doesn't necessarily go through the bézier but influences curvature).
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, graphene_hash::CacheHash, Debug, DynAny, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, graphene_hash::CacheHash, Debug, DynAny)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum ManipulatorPointId {
|
||||
/// A control anchor - the start or end point of a bézier.
|
||||
Anchor(PointId),
|
||||
|
|
@ -479,7 +488,8 @@ impl ManipulatorPointId {
|
|||
}
|
||||
|
||||
/// The type of handle found on a bézier curve.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, graphene_hash::CacheHash, Debug, DynAny, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, graphene_hash::CacheHash, Debug, DynAny)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum HandleType {
|
||||
/// The first handle on a cubic bézier or the only handle on a quadratic bézier.
|
||||
Primary,
|
||||
|
|
@ -488,7 +498,8 @@ pub enum HandleType {
|
|||
}
|
||||
|
||||
/// Represents a primary or end handle found in a particular segment.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, graphene_hash::CacheHash, Debug, DynAny, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, graphene_hash::CacheHash, Debug, DynAny)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct HandleId {
|
||||
pub ty: HandleType,
|
||||
pub segment: SegmentId,
|
||||
|
|
@ -547,7 +558,8 @@ impl HandleId {
|
|||
}
|
||||
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, node_macro::ChoiceType)]
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash, DynAny, node_macro::ChoiceType)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[widget(Dropdown)]
|
||||
pub enum SpiralType {
|
||||
#[default]
|
||||
|
|
@ -557,7 +569,8 @@ pub enum SpiralType {
|
|||
|
||||
/// Controls how the morph/blend progression spends its time along the interpolation path, allowing for constant speed/spacing with respect to different parameters of change.
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, node_macro::ChoiceType)]
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash, DynAny, node_macro::ChoiceType)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[widget(Dropdown)]
|
||||
pub enum InterpolationDistribution {
|
||||
/// All objects occupy an equal portion of the progression range, regardless of their changing distances, angles, sizes, or slants.
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@ use core_types::math::bbox::AxisAlignedBbox;
|
|||
use glam::DVec2;
|
||||
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Clone, Copy, Debug, Default, Hash, graphene_hash::CacheHash, Eq, PartialEq, dyn_any::DynAny, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Copy, Debug, Default, Hash, graphene_hash::CacheHash, Eq, PartialEq, dyn_any::DynAny)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum ReferencePoint {
|
||||
#[default]
|
||||
None,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
//! Contains stylistic options for SVG elements.
|
||||
|
||||
pub use crate::gradient::*;
|
||||
use core_types::AlphaBlending;
|
||||
use core_types::Color;
|
||||
use core_types::color::Alpha;
|
||||
use core_types::table::Table;
|
||||
|
|
@ -16,7 +17,8 @@ use std::f64::consts::{PI, TAU};
|
|||
/// In the future we'll probably also add a pattern fill. This will probably be named "Paint" in the future.
|
||||
#[repr(C)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Default, Debug, Clone, PartialEq, graphene_hash::CacheHash, serde::Serialize, serde::Deserialize, DynAny)]
|
||||
#[derive(Default, Debug, Clone, PartialEq, graphene_hash::CacheHash, DynAny)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum Fill {
|
||||
#[default]
|
||||
None,
|
||||
|
|
@ -133,8 +135,8 @@ impl From<Option<Color>> for Fill {
|
|||
|
||||
impl From<Table<Color>> for Fill {
|
||||
fn from(color: Table<Color>) -> Fill {
|
||||
let alpha = color.get(0).map(|c| c.alpha_blending.opacity).unwrap_or(1.);
|
||||
let color: Option<Color> = color.into();
|
||||
let alpha = color.attribute_cloned_or_default::<AlphaBlending>("alpha_blending", 0).opacity;
|
||||
let color = color.element(0).copied();
|
||||
Fill::solid_or_none(color.map(|c| c.with_alpha(c.alpha() * alpha)))
|
||||
}
|
||||
}
|
||||
|
|
@ -142,7 +144,7 @@ impl From<Table<Color>> for Fill {
|
|||
impl From<Table<GradientStops>> for Fill {
|
||||
fn from(gradient: Table<GradientStops>) -> Fill {
|
||||
Fill::Gradient(Gradient {
|
||||
stops: gradient.iter().nth(0).map(|row| row.element.clone()).unwrap_or_default(),
|
||||
stops: gradient.element(0).cloned().unwrap_or_default(),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
|
@ -161,7 +163,8 @@ impl From<Gradient> for Fill {
|
|||
/// In the future we'll probably also add a pattern fill.
|
||||
#[repr(C)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Default, Debug, Clone, PartialEq, graphene_hash::CacheHash, serde::Serialize, serde::Deserialize, DynAny)]
|
||||
#[derive(Default, Debug, Clone, PartialEq, graphene_hash::CacheHash, DynAny)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum FillChoice {
|
||||
#[default]
|
||||
None,
|
||||
|
|
@ -209,7 +212,8 @@ impl From<Fill> for FillChoice {
|
|||
|
||||
#[repr(C)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, serde::Serialize, serde::Deserialize, DynAny, Hash, graphene_hash::CacheHash, node_macro::ChoiceType)]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, DynAny, Hash, graphene_hash::CacheHash, node_macro::ChoiceType)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[widget(Radio)]
|
||||
pub enum FillType {
|
||||
#[default]
|
||||
|
|
@ -220,7 +224,8 @@ pub enum FillType {
|
|||
/// The stroke (outline) style of an SVG element.
|
||||
#[repr(C)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, graphene_hash::CacheHash, DynAny, node_macro::ChoiceType)]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, graphene_hash::CacheHash, DynAny, node_macro::ChoiceType)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[widget(Radio)]
|
||||
pub enum StrokeCap {
|
||||
#[default]
|
||||
|
|
@ -241,7 +246,8 @@ impl StrokeCap {
|
|||
|
||||
#[repr(C)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, graphene_hash::CacheHash, DynAny, node_macro::ChoiceType)]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, graphene_hash::CacheHash, DynAny, node_macro::ChoiceType)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[widget(Radio)]
|
||||
pub enum StrokeJoin {
|
||||
#[default]
|
||||
|
|
@ -262,7 +268,8 @@ impl StrokeJoin {
|
|||
|
||||
#[repr(C)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, graphene_hash::CacheHash, DynAny, node_macro::ChoiceType)]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, graphene_hash::CacheHash, DynAny, node_macro::ChoiceType)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[widget(Radio)]
|
||||
pub enum StrokeAlign {
|
||||
#[default]
|
||||
|
|
@ -279,7 +286,8 @@ impl StrokeAlign {
|
|||
|
||||
#[repr(C)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, graphene_hash::CacheHash, DynAny, node_macro::ChoiceType)]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, graphene_hash::CacheHash, DynAny, node_macro::ChoiceType)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[widget(Radio)]
|
||||
pub enum PaintOrder {
|
||||
#[default]
|
||||
|
|
@ -299,8 +307,9 @@ fn daffine2_identity() -> DAffine2 {
|
|||
|
||||
#[repr(C)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Debug, Clone, PartialEq, graphene_hash::CacheHash, serde::Serialize, serde::Deserialize, DynAny)]
|
||||
#[serde(default)]
|
||||
#[derive(Debug, Clone, PartialEq, graphene_hash::CacheHash, DynAny)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "serde", serde(default))]
|
||||
pub struct Stroke {
|
||||
/// Stroke color
|
||||
pub color: Option<Color>,
|
||||
|
|
@ -308,17 +317,17 @@ pub struct Stroke {
|
|||
pub weight: f64,
|
||||
pub dash_lengths: Vec<f64>,
|
||||
pub dash_offset: f64,
|
||||
#[serde(alias = "line_cap")]
|
||||
#[cfg_attr(feature = "serde", serde(alias = "line_cap"))]
|
||||
pub cap: StrokeCap,
|
||||
#[serde(alias = "line_join")]
|
||||
#[cfg_attr(feature = "serde", serde(alias = "line_join"))]
|
||||
pub join: StrokeJoin,
|
||||
#[serde(alias = "line_join_miter_limit")]
|
||||
#[cfg_attr(feature = "serde", serde(alias = "line_join_miter_limit"))]
|
||||
pub join_miter_limit: f64,
|
||||
#[serde(default)]
|
||||
#[cfg_attr(feature = "serde", serde(default))]
|
||||
pub align: StrokeAlign,
|
||||
#[serde(default = "daffine2_identity")]
|
||||
#[cfg_attr(feature = "serde", serde(default = "daffine2_identity"))]
|
||||
pub transform: DAffine2,
|
||||
#[serde(default)]
|
||||
#[cfg_attr(feature = "serde", serde(default))]
|
||||
pub paint_order: PaintOrder,
|
||||
}
|
||||
|
||||
|
|
@ -494,7 +503,8 @@ impl Default for Stroke {
|
|||
|
||||
#[repr(C)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Debug, Clone, PartialEq, Default, graphene_hash::CacheHash, serde::Serialize, serde::Deserialize, DynAny)]
|
||||
#[derive(Debug, Clone, PartialEq, Default, graphene_hash::CacheHash, DynAny)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct PathStyle {
|
||||
pub stroke: Option<Stroke>,
|
||||
pub fill: Fill,
|
||||
|
|
@ -655,7 +665,8 @@ impl PathStyle {
|
|||
|
||||
/// Ways the user can choose to view the artwork in the viewport.
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, graphene_hash::CacheHash, DynAny)]
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash, graphene_hash::CacheHash, DynAny)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum RenderMode {
|
||||
/// Render with normal coloration at the current viewport resolution
|
||||
#[default]
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ macro_rules! create_ids {
|
|||
($($id:ident),*) => {
|
||||
$(
|
||||
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Ord, Eq, Hash, graphene_hash::CacheHash, DynAny)]
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
/// A strongly typed ID
|
||||
pub struct $id(u64);
|
||||
|
||||
|
|
@ -79,11 +79,12 @@ impl std::hash::BuildHasher for NoHashBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, graphene_hash::CacheHash, DynAny, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Debug, Default, PartialEq, graphene_hash::CacheHash, DynAny)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
/// Stores data which is per-point. Each point is merely a position and can be used in a point cloud or to for a bézier path. In future this will be extendable at runtime with custom attributes.
|
||||
pub struct PointDomain {
|
||||
id: Vec<PointId>,
|
||||
#[serde(alias = "positions")]
|
||||
#[cfg_attr(feature = "serde", serde(alias = "positions"))]
|
||||
pub(crate) position: Vec<DVec2>,
|
||||
}
|
||||
|
||||
|
|
@ -205,10 +206,11 @@ impl PointDomain {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, graphene_hash::CacheHash, DynAny, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Debug, Default, PartialEq, graphene_hash::CacheHash, DynAny)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
/// Stores data which is per-segment. A segment is a bézier curve between two end points with a stroke. In future this will be extendable at runtime with custom attributes.
|
||||
pub struct SegmentDomain {
|
||||
#[serde(alias = "ids")]
|
||||
#[cfg_attr(feature = "serde", serde(alias = "ids"))]
|
||||
id: Vec<SegmentId>,
|
||||
start_point: Vec<usize>,
|
||||
end_point: Vec<usize>,
|
||||
|
|
@ -587,11 +589,12 @@ impl SegmentDomain {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Hash, graphene_hash::CacheHash, DynAny, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Debug, Default, PartialEq, Hash, graphene_hash::CacheHash, DynAny)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
/// Stores data which is per-region. A region is an enclosed area composed of a range of segments from the
|
||||
/// [`SegmentDomain`] that can be given a fill. In future this will be extendable at runtime with custom attributes.
|
||||
pub struct RegionDomain {
|
||||
#[serde(alias = "ids")]
|
||||
#[cfg_attr(feature = "serde", serde(alias = "ids"))]
|
||||
id: Vec<RegionId>,
|
||||
segment_range: Vec<std::ops::RangeInclusive<SegmentId>>,
|
||||
fill: Vec<FillId>,
|
||||
|
|
|
|||
|
|
@ -9,11 +9,12 @@ use std::collections::{HashMap, HashSet};
|
|||
use std::hash::BuildHasher;
|
||||
|
||||
/// Represents a procedural change to the [`PointDomain`] in [`Vector`].
|
||||
#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct PointModification {
|
||||
add: Vec<PointId>,
|
||||
remove: HashSet<PointId>,
|
||||
#[serde(serialize_with = "serialize_hashmap", deserialize_with = "deserialize_hashmap")]
|
||||
#[cfg_attr(feature = "serde", serde(serialize_with = "serialize_hashmap", deserialize_with = "deserialize_hashmap"))]
|
||||
delta: HashMap<PointId, DVec2>,
|
||||
}
|
||||
|
||||
|
|
@ -74,19 +75,20 @@ impl PointModification {
|
|||
}
|
||||
|
||||
/// Represents a procedural change to the [`SegmentDomain`] in [`Vector`].
|
||||
#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct SegmentModification {
|
||||
add: Vec<SegmentId>,
|
||||
remove: HashSet<SegmentId>,
|
||||
#[serde(serialize_with = "serialize_hashmap", deserialize_with = "deserialize_hashmap")]
|
||||
#[cfg_attr(feature = "serde", serde(serialize_with = "serialize_hashmap", deserialize_with = "deserialize_hashmap"))]
|
||||
start_point: HashMap<SegmentId, PointId>,
|
||||
#[serde(serialize_with = "serialize_hashmap", deserialize_with = "deserialize_hashmap")]
|
||||
#[cfg_attr(feature = "serde", serde(serialize_with = "serialize_hashmap", deserialize_with = "deserialize_hashmap"))]
|
||||
end_point: HashMap<SegmentId, PointId>,
|
||||
#[serde(serialize_with = "serialize_hashmap", deserialize_with = "deserialize_hashmap")]
|
||||
#[cfg_attr(feature = "serde", serde(serialize_with = "serialize_hashmap", deserialize_with = "deserialize_hashmap"))]
|
||||
handle_primary: HashMap<SegmentId, Option<DVec2>>,
|
||||
#[serde(serialize_with = "serialize_hashmap", deserialize_with = "deserialize_hashmap")]
|
||||
#[cfg_attr(feature = "serde", serde(serialize_with = "serialize_hashmap", deserialize_with = "deserialize_hashmap"))]
|
||||
handle_end: HashMap<SegmentId, Option<DVec2>>,
|
||||
#[serde(serialize_with = "serialize_hashmap", deserialize_with = "deserialize_hashmap")]
|
||||
#[cfg_attr(feature = "serde", serde(serialize_with = "serialize_hashmap", deserialize_with = "deserialize_hashmap"))]
|
||||
stroke: HashMap<SegmentId, StrokeId>,
|
||||
}
|
||||
|
||||
|
|
@ -244,13 +246,14 @@ impl SegmentModification {
|
|||
}
|
||||
|
||||
/// Represents a procedural change to the [`RegionDomain`] in [`Vector`].
|
||||
#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct RegionModification {
|
||||
add: Vec<RegionId>,
|
||||
remove: HashSet<RegionId>,
|
||||
#[serde(serialize_with = "serialize_hashmap", deserialize_with = "deserialize_hashmap")]
|
||||
#[cfg_attr(feature = "serde", serde(serialize_with = "serialize_hashmap", deserialize_with = "deserialize_hashmap"))]
|
||||
segment_range: HashMap<RegionId, std::ops::RangeInclusive<SegmentId>>,
|
||||
#[serde(serialize_with = "serialize_hashmap", deserialize_with = "deserialize_hashmap")]
|
||||
#[cfg_attr(feature = "serde", serde(serialize_with = "serialize_hashmap", deserialize_with = "deserialize_hashmap"))]
|
||||
fill: HashMap<RegionId, FillId>,
|
||||
}
|
||||
|
||||
|
|
@ -288,7 +291,8 @@ impl RegionModification {
|
|||
}
|
||||
|
||||
/// Represents a procedural change to the [`Vector`].
|
||||
#[derive(Clone, Debug, Default, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Debug, Default, PartialEq, DynAny)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct VectorModification {
|
||||
points: PointModification,
|
||||
segments: SegmentModification,
|
||||
|
|
@ -298,7 +302,8 @@ pub struct VectorModification {
|
|||
}
|
||||
|
||||
/// A modification type that can be added to a [`VectorModification`].
|
||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum VectorModificationType {
|
||||
InsertSegment { id: SegmentId, points: [PointId; 2], handles: [Option<DVec2>; 2] },
|
||||
InsertPoint { id: PointId, position: DVec2 },
|
||||
|
|
|
|||
|
|
@ -20,7 +20,8 @@ use std::collections::HashMap;
|
|||
/// Generic over `Upstream` to avoid circular dependency with the Graphic type.
|
||||
/// - Use `Vector<()>` for basic vectors without upstream tracking
|
||||
/// - Use `Vector<Option<Table<Graphic>>>` in the graphic crate for vectors with upstream layers
|
||||
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct Vector<Upstream> {
|
||||
pub style: PathStyle,
|
||||
|
||||
|
|
@ -34,7 +35,7 @@ pub struct Vector<Upstream> {
|
|||
|
||||
/// Used to store the upstream group/folder of nested layers during destructive Boolean Operations (and other nodes with a similar effect) so that click targets can be preserved for the child layers.
|
||||
/// Without this, the tools would be working with a collapsed version of the data which has no reference to the original child layers that were booleaned together, resulting in the inner layers not being editable.
|
||||
#[serde(alias = "upstream_group")]
|
||||
#[cfg_attr(feature = "serde", serde(alias = "upstream_group"))]
|
||||
pub upstream_data: Upstream,
|
||||
}
|
||||
unsafe impl<Upstream: 'static> StaticType for Vector<Upstream> {
|
||||
|
|
|
|||
|
|
@ -168,10 +168,10 @@ impl PerPixelAdjustGraphicsPipeline {
|
|||
let mut cmd = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: Some(&format!("{name} cmd encoder")),
|
||||
});
|
||||
let out = textures
|
||||
.iter()
|
||||
.map(|instance| {
|
||||
let tex_in = &instance.element.texture;
|
||||
let out = (0..textures.len())
|
||||
.map(|index| {
|
||||
let element = textures.element(index).unwrap();
|
||||
let tex_in = &element.texture;
|
||||
let view_in = tex_in.create_view(&TextureViewDescriptor::default());
|
||||
let format = tex_in.format();
|
||||
|
||||
|
|
@ -233,12 +233,8 @@ impl PerPixelAdjustGraphicsPipeline {
|
|||
rp.set_bind_group(0, Some(&bind_group), &[]);
|
||||
rp.draw(0..3, 0..1);
|
||||
|
||||
TableRow {
|
||||
element: Raster::new(GPU { texture: tex_out }),
|
||||
transform: *instance.transform,
|
||||
alpha_blending: *instance.alpha_blending,
|
||||
source_node_id: *instance.source_node_id,
|
||||
}
|
||||
let attributes = textures.clone_row_attributes(index);
|
||||
TableRow::from_parts(Raster::new(GPU { texture: tex_out }), attributes)
|
||||
})
|
||||
.collect::<Table<_>>();
|
||||
context.queue.submit([cmd.finish()]);
|
||||
|
|
|
|||
|
|
@ -150,17 +150,12 @@ impl<'i> Convert<Table<Raster<GPU>>, &'i WgpuExecutor> for Table<Raster<CPU>> {
|
|||
let device = &executor.context.device;
|
||||
let queue = &executor.context.queue;
|
||||
let table = self
|
||||
.iter()
|
||||
.into_iter()
|
||||
.map(|row| {
|
||||
let image = row.element;
|
||||
let texture = upload_to_texture(device, queue, image);
|
||||
let (image, attributes) = row.into_parts();
|
||||
let texture = upload_to_texture(device, queue, &image);
|
||||
|
||||
TableRow {
|
||||
element: Raster::new_gpu(texture),
|
||||
transform: *row.transform,
|
||||
alpha_blending: *row.alpha_blending,
|
||||
source_node_id: *row.source_node_id,
|
||||
}
|
||||
TableRow::from_parts(Raster::new_gpu(texture), attributes)
|
||||
})
|
||||
.collect();
|
||||
|
||||
|
|
@ -204,14 +199,9 @@ impl<'i> Convert<Table<Raster<CPU>>, &'i WgpuExecutor> for Table<Raster<GPU>> {
|
|||
let mut rows_meta = Vec::new();
|
||||
|
||||
for row in self {
|
||||
let gpu_raster = row.element;
|
||||
converters.push(RasterGpuToRasterCpuConverter::new(device, &mut encoder, gpu_raster));
|
||||
rows_meta.push(TableRow {
|
||||
element: (),
|
||||
transform: row.transform,
|
||||
alpha_blending: row.alpha_blending,
|
||||
source_node_id: row.source_node_id,
|
||||
});
|
||||
let (element, attributes) = row.into_parts();
|
||||
converters.push(RasterGpuToRasterCpuConverter::new(device, &mut encoder, element));
|
||||
rows_meta.push(TableRow::from_parts((), attributes));
|
||||
}
|
||||
|
||||
queue.submit([encoder.finish()]);
|
||||
|
|
@ -229,11 +219,9 @@ impl<'i> Convert<Table<Raster<CPU>>, &'i WgpuExecutor> for Table<Raster<GPU>> {
|
|||
map_results
|
||||
.into_iter()
|
||||
.zip(rows_meta.into_iter())
|
||||
.map(|(element, row)| TableRow {
|
||||
element,
|
||||
transform: row.transform,
|
||||
alpha_blending: row.alpha_blending,
|
||||
source_node_id: row.source_node_id,
|
||||
.map(|(element, row)| {
|
||||
let (_, attributes) = row.into_parts();
|
||||
TableRow::from_parts(element, attributes)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
use core_types::AlphaBlending;
|
||||
use core_types::registry::types::Percentage;
|
||||
use core_types::table::Table;
|
||||
use core_types::{BlendMode, Color, Ctx};
|
||||
|
|
@ -17,36 +18,36 @@ impl MultiplyAlpha for Color {
|
|||
}
|
||||
impl MultiplyAlpha for Table<Vector> {
|
||||
fn multiply_alpha(&mut self, factor: f64) {
|
||||
for row in self.iter_mut() {
|
||||
row.alpha_blending.opacity *= factor as f32;
|
||||
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>("alpha_blending") {
|
||||
a.opacity *= factor as f32;
|
||||
}
|
||||
}
|
||||
}
|
||||
impl MultiplyAlpha for Table<Graphic> {
|
||||
fn multiply_alpha(&mut self, factor: f64) {
|
||||
for row in self.iter_mut() {
|
||||
row.alpha_blending.opacity *= factor as f32;
|
||||
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>("alpha_blending") {
|
||||
a.opacity *= factor as f32;
|
||||
}
|
||||
}
|
||||
}
|
||||
impl MultiplyAlpha for Table<Raster<CPU>> {
|
||||
fn multiply_alpha(&mut self, factor: f64) {
|
||||
for row in self.iter_mut() {
|
||||
row.alpha_blending.opacity *= factor as f32;
|
||||
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>("alpha_blending") {
|
||||
a.opacity *= factor as f32;
|
||||
}
|
||||
}
|
||||
}
|
||||
impl MultiplyAlpha for Table<Color> {
|
||||
fn multiply_alpha(&mut self, factor: f64) {
|
||||
for row in self.iter_mut() {
|
||||
row.alpha_blending.opacity *= factor as f32;
|
||||
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>("alpha_blending") {
|
||||
a.opacity *= factor as f32;
|
||||
}
|
||||
}
|
||||
}
|
||||
impl MultiplyAlpha for Table<GradientStops> {
|
||||
fn multiply_alpha(&mut self, factor: f64) {
|
||||
for row in self.iter_mut() {
|
||||
row.alpha_blending.opacity *= factor as f32;
|
||||
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>("alpha_blending") {
|
||||
a.opacity *= factor as f32;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -61,36 +62,36 @@ impl MultiplyFill for Color {
|
|||
}
|
||||
impl MultiplyFill for Table<Vector> {
|
||||
fn multiply_fill(&mut self, factor: f64) {
|
||||
for row in self.iter_mut() {
|
||||
row.alpha_blending.fill *= factor as f32;
|
||||
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>("alpha_blending") {
|
||||
a.fill *= factor as f32;
|
||||
}
|
||||
}
|
||||
}
|
||||
impl MultiplyFill for Table<Graphic> {
|
||||
fn multiply_fill(&mut self, factor: f64) {
|
||||
for row in self.iter_mut() {
|
||||
row.alpha_blending.fill *= factor as f32;
|
||||
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>("alpha_blending") {
|
||||
a.fill *= factor as f32;
|
||||
}
|
||||
}
|
||||
}
|
||||
impl MultiplyFill for Table<Raster<CPU>> {
|
||||
fn multiply_fill(&mut self, factor: f64) {
|
||||
for row in self.iter_mut() {
|
||||
row.alpha_blending.fill *= factor as f32;
|
||||
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>("alpha_blending") {
|
||||
a.fill *= factor as f32;
|
||||
}
|
||||
}
|
||||
}
|
||||
impl MultiplyFill for Table<Color> {
|
||||
fn multiply_fill(&mut self, factor: f64) {
|
||||
for row in self.iter_mut() {
|
||||
row.alpha_blending.fill *= factor as f32;
|
||||
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>("alpha_blending") {
|
||||
a.fill *= factor as f32;
|
||||
}
|
||||
}
|
||||
}
|
||||
impl MultiplyFill for Table<GradientStops> {
|
||||
fn multiply_fill(&mut self, factor: f64) {
|
||||
for row in self.iter_mut() {
|
||||
row.alpha_blending.fill *= factor as f32;
|
||||
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>("alpha_blending") {
|
||||
a.fill *= factor as f32;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -101,36 +102,36 @@ trait SetBlendMode {
|
|||
|
||||
impl SetBlendMode for Table<Vector> {
|
||||
fn set_blend_mode(&mut self, blend_mode: BlendMode) {
|
||||
for row in self.iter_mut() {
|
||||
row.alpha_blending.blend_mode = blend_mode;
|
||||
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>("alpha_blending") {
|
||||
a.blend_mode = blend_mode;
|
||||
}
|
||||
}
|
||||
}
|
||||
impl SetBlendMode for Table<Graphic> {
|
||||
fn set_blend_mode(&mut self, blend_mode: BlendMode) {
|
||||
for row in self.iter_mut() {
|
||||
row.alpha_blending.blend_mode = blend_mode;
|
||||
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>("alpha_blending") {
|
||||
a.blend_mode = blend_mode;
|
||||
}
|
||||
}
|
||||
}
|
||||
impl SetBlendMode for Table<Raster<CPU>> {
|
||||
fn set_blend_mode(&mut self, blend_mode: BlendMode) {
|
||||
for row in self.iter_mut() {
|
||||
row.alpha_blending.blend_mode = blend_mode;
|
||||
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>("alpha_blending") {
|
||||
a.blend_mode = blend_mode;
|
||||
}
|
||||
}
|
||||
}
|
||||
impl SetBlendMode for Table<Color> {
|
||||
fn set_blend_mode(&mut self, blend_mode: BlendMode) {
|
||||
for row in self.iter_mut() {
|
||||
row.alpha_blending.blend_mode = blend_mode;
|
||||
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>("alpha_blending") {
|
||||
a.blend_mode = blend_mode;
|
||||
}
|
||||
}
|
||||
}
|
||||
impl SetBlendMode for Table<GradientStops> {
|
||||
fn set_blend_mode(&mut self, blend_mode: BlendMode) {
|
||||
for row in self.iter_mut() {
|
||||
row.alpha_blending.blend_mode = blend_mode;
|
||||
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>("alpha_blending") {
|
||||
a.blend_mode = blend_mode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -141,36 +142,36 @@ trait SetClip {
|
|||
|
||||
impl SetClip for Table<Vector> {
|
||||
fn set_clip(&mut self, clip: bool) {
|
||||
for row in self.iter_mut() {
|
||||
row.alpha_blending.clip = clip;
|
||||
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>("alpha_blending") {
|
||||
a.clip = clip;
|
||||
}
|
||||
}
|
||||
}
|
||||
impl SetClip for Table<Graphic> {
|
||||
fn set_clip(&mut self, clip: bool) {
|
||||
for row in self.iter_mut() {
|
||||
row.alpha_blending.clip = clip;
|
||||
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>("alpha_blending") {
|
||||
a.clip = clip;
|
||||
}
|
||||
}
|
||||
}
|
||||
impl SetClip for Table<Raster<CPU>> {
|
||||
fn set_clip(&mut self, clip: bool) {
|
||||
for row in self.iter_mut() {
|
||||
row.alpha_blending.clip = clip;
|
||||
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>("alpha_blending") {
|
||||
a.clip = clip;
|
||||
}
|
||||
}
|
||||
}
|
||||
impl SetClip for Table<Color> {
|
||||
fn set_clip(&mut self, clip: bool) {
|
||||
for row in self.iter_mut() {
|
||||
row.alpha_blending.clip = clip;
|
||||
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>("alpha_blending") {
|
||||
a.clip = clip;
|
||||
}
|
||||
}
|
||||
}
|
||||
impl SetClip for Table<GradientStops> {
|
||||
fn set_clip(&mut self, clip: bool) {
|
||||
for row in self.iter_mut() {
|
||||
row.alpha_blending.clip = clip;
|
||||
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>("alpha_blending") {
|
||||
a.clip = clip;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0"
|
|||
|
||||
[features]
|
||||
default = ["serde"]
|
||||
serde = ["dep:serde"]
|
||||
serde = ["dep:serde", "core-types/serde", "raster-types/serde", "raster-nodes/serde"]
|
||||
|
||||
[dependencies]
|
||||
# Local dependencies
|
||||
|
|
|
|||
|
|
@ -8,8 +8,9 @@ use core_types::math::bbox::{AxisAlignedBbox, Bbox};
|
|||
use core_types::registry::FutureWrapperNode;
|
||||
use core_types::table::{Table, TableRow};
|
||||
use core_types::transform::Transform;
|
||||
use core_types::uuid::NodeId;
|
||||
use core_types::value::ClonedNode;
|
||||
use core_types::{Ctx, Node};
|
||||
use core_types::{AlphaBlending, Ctx, Node};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use raster_nodes::blending_nodes::blend_colors;
|
||||
use raster_nodes::std_nodes::{empty_image, extend_image_to_bounds};
|
||||
|
|
@ -89,14 +90,15 @@ where
|
|||
return target;
|
||||
}
|
||||
|
||||
for table_row in target.iter_mut() {
|
||||
let target_width = table_row.element.width;
|
||||
let target_height = table_row.element.height;
|
||||
let (elements, transforms) = target.element_and_attribute_slices_mut::<DAffine2>("transform");
|
||||
for (element, transform_attribute) in elements.iter_mut().zip(transforms.iter()) {
|
||||
let target_width = element.width;
|
||||
let target_height = element.height;
|
||||
let target_size = DVec2::new(target_width as f64, target_height as f64);
|
||||
|
||||
let texture_size = DVec2::new(texture.width as f64, texture.height as f64);
|
||||
|
||||
let document_to_target = DAffine2::from_translation(-texture_size / 2.) * DAffine2::from_scale(target_size) * table_row.transform.inverse();
|
||||
let document_to_target = DAffine2::from_translation(-texture_size / 2.) * DAffine2::from_scale(target_size) * transform_attribute.inverse();
|
||||
|
||||
for position in &positions {
|
||||
let start = document_to_target.transform_point2(*position).round();
|
||||
|
|
@ -116,12 +118,12 @@ where
|
|||
let max_y = (blit_area_offset.y + blit_area_dimensions.y).saturating_sub(1);
|
||||
let max_x = (blit_area_offset.x + blit_area_dimensions.x).saturating_sub(1);
|
||||
assert!(texture_index(max_x, max_y) < texture.data.len());
|
||||
assert!(target_index(max_x, max_y) < table_row.element.data.len());
|
||||
assert!(target_index(max_x, max_y) < element.data.len());
|
||||
|
||||
for y in blit_area_offset.y..blit_area_offset.y + blit_area_dimensions.y {
|
||||
for x in blit_area_offset.x..blit_area_offset.x + blit_area_dimensions.x {
|
||||
let src_pixel = texture.data[texture_index(x, y)];
|
||||
let dst_pixel = &mut table_row.element.data_mut().data[target_index(x + clamp_start.x, y + clamp_start.y)];
|
||||
let dst_pixel = &mut element.data_mut().data[target_index(x + clamp_start.x, y + clamp_start.y)];
|
||||
*dst_pixel = blend_mode.eval((src_pixel, *dst_pixel));
|
||||
}
|
||||
}
|
||||
|
|
@ -137,7 +139,7 @@ pub async fn create_brush_texture(brush_style: &BrushStyle) -> Raster<CPU> {
|
|||
let blank_texture = empty_image((), transform, Table::new_from_element(Color::TRANSPARENT)).into_iter().next().unwrap_or_default();
|
||||
let image = blend_stamp_closure(stamp, blank_texture, |a, b| blend_colors(a, b, BlendMode::Normal, 1.));
|
||||
|
||||
image.element
|
||||
image.into_element()
|
||||
}
|
||||
|
||||
pub fn blend_with_mode(background: TableRow<Raster<CPU>>, foreground: TableRow<Raster<CPU>>, blend_mode: BlendMode, opacity: f64) -> TableRow<Raster<CPU>> {
|
||||
|
|
@ -198,7 +200,7 @@ async fn brush(
|
|||
image.push(TableRow::default());
|
||||
}
|
||||
// TODO: Find a way to handle more than one row
|
||||
let table_row = image.iter().next().expect("Expected the one row we just pushed").into_cloned();
|
||||
let table_row = image.clone_row(0).expect("Expected the one row we just pushed");
|
||||
|
||||
let bounds = Table::new_from_row(table_row.clone()).bounding_box(DAffine2::IDENTITY, false);
|
||||
let [start, end] = if let RenderBoundingBox::Rectangle(rect) = bounds { rect } else { [DVec2::ZERO, DVec2::ZERO] };
|
||||
|
|
@ -274,11 +276,10 @@ async fn brush(
|
|||
let has_erase_or_restore_strokes = strokes.iter().any(|s| matches!(s.style.blend_mode, BlendMode::Erase | BlendMode::Restore));
|
||||
if has_erase_or_restore_strokes {
|
||||
let opaque_image = Image::new(bbox.size().x as u32, bbox.size().y as u32, Color::WHITE);
|
||||
let mut erase_restore_mask = TableRow {
|
||||
element: Raster::new_cpu(opaque_image),
|
||||
transform: background_bounds,
|
||||
..Default::default()
|
||||
};
|
||||
let mut erase_restore_mask = TableRow::new_from_element(Raster::new_cpu(opaque_image))
|
||||
.with_attribute("transform", background_bounds)
|
||||
.with_attribute("alpha_blending", AlphaBlending::default())
|
||||
.with_attribute("source_node_id", None::<NodeId>);
|
||||
|
||||
for stroke in strokes {
|
||||
let mut brush_texture = cache.get_cached_brush(&stroke.style);
|
||||
|
|
@ -310,24 +311,29 @@ async fn brush(
|
|||
actual_image = blend_image_closure(erase_restore_mask, actual_image, |a, b| blend_params.eval((a, b)));
|
||||
}
|
||||
|
||||
let first_row = image.iter_mut().next().unwrap();
|
||||
*first_row.element = actual_image.element;
|
||||
*first_row.transform = actual_image.transform;
|
||||
*first_row.alpha_blending = actual_image.alpha_blending;
|
||||
*first_row.source_node_id = actual_image.source_node_id;
|
||||
let transform: DAffine2 = actual_image.attribute_cloned_or_default("transform");
|
||||
let alpha_blending: AlphaBlending = actual_image.attribute_cloned_or_default("alpha_blending");
|
||||
let source_node_id: Option<NodeId> = actual_image.attribute_cloned_or_default("source_node_id");
|
||||
|
||||
*image.element_mut(0).unwrap() = actual_image.into_element();
|
||||
image.set_attribute("transform", 0, transform);
|
||||
image.set_attribute("alpha_blending", 0, alpha_blending);
|
||||
image.set_attribute("source_node_id", 0, source_node_id);
|
||||
|
||||
image
|
||||
}
|
||||
|
||||
pub fn blend_image_closure(foreground: TableRow<Raster<CPU>>, mut background: TableRow<Raster<CPU>>, map_fn: impl Fn(Color, Color) -> Color) -> TableRow<Raster<CPU>> {
|
||||
let foreground_size = DVec2::new(foreground.element.width as f64, foreground.element.height as f64);
|
||||
let background_size = DVec2::new(background.element.width as f64, background.element.height as f64);
|
||||
let foreground_size = DVec2::new(foreground.element().width as f64, foreground.element().height as f64);
|
||||
let background_size = DVec2::new(background.element().width as f64, background.element().height as f64);
|
||||
|
||||
// Transforms a point from the background image to the foreground image
|
||||
let background_to_foreground = DAffine2::from_scale(foreground_size) * foreground.transform.inverse() * background.transform * DAffine2::from_scale(1. / background_size);
|
||||
let foreground_transform: DAffine2 = foreground.attribute_cloned_or_default("transform");
|
||||
let background_transform: DAffine2 = background.attribute_cloned_or_default("transform");
|
||||
let background_to_foreground = DAffine2::from_scale(foreground_size) * foreground_transform.inverse() * background_transform * DAffine2::from_scale(1. / background_size);
|
||||
|
||||
// Footprint of the foreground image (0, 0)..(1, 1) in the background image space
|
||||
let background_aabb = Bbox::unit().affine_transform(background.transform.inverse() * foreground.transform).to_axis_aligned_bbox();
|
||||
let background_aabb = Bbox::unit().affine_transform(background_transform.inverse() * foreground_transform).to_axis_aligned_bbox();
|
||||
|
||||
// Clamp the foreground image to the background image
|
||||
let start = (background_aabb.start * background_size).max(DVec2::ZERO).as_uvec2();
|
||||
|
|
@ -338,8 +344,10 @@ pub fn blend_image_closure(foreground: TableRow<Raster<CPU>>, mut background: Ta
|
|||
let background_point = DVec2::new(x as f64, y as f64);
|
||||
let foreground_point = background_to_foreground.transform_point2(background_point);
|
||||
|
||||
let source_pixel = foreground.element.sample(foreground_point);
|
||||
let Some(destination_pixel) = background.element.data_mut().get_pixel_mut(x, y) else { continue };
|
||||
let source_pixel = foreground.element().sample(foreground_point);
|
||||
let Some(destination_pixel) = background.element_mut().data_mut().get_pixel_mut(x, y) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
*destination_pixel = map_fn(source_pixel, *destination_pixel);
|
||||
}
|
||||
|
|
@ -349,13 +357,14 @@ pub fn blend_image_closure(foreground: TableRow<Raster<CPU>>, mut background: Ta
|
|||
}
|
||||
|
||||
pub fn blend_stamp_closure(foreground: BrushStampGenerator<Color>, mut background: TableRow<Raster<CPU>>, map_fn: impl Fn(Color, Color) -> Color) -> TableRow<Raster<CPU>> {
|
||||
let background_size = DVec2::new(background.element.width as f64, background.element.height as f64);
|
||||
let background_size = DVec2::new(background.element().width as f64, background.element().height as f64);
|
||||
|
||||
// Transforms a point from the background image to the foreground image
|
||||
let background_to_foreground = background.transform * DAffine2::from_scale(1. / background_size);
|
||||
let background_transform: DAffine2 = background.attribute_cloned_or_default("transform");
|
||||
let background_to_foreground = background_transform * DAffine2::from_scale(1. / background_size);
|
||||
|
||||
// Footprint of the foreground image (0, 0)..(1, 1) in the background image space
|
||||
let background_aabb = Bbox::unit().affine_transform(background.transform.inverse() * foreground.transform).to_axis_aligned_bbox();
|
||||
let background_aabb = Bbox::unit().affine_transform(background_transform.inverse() * foreground.transform()).to_axis_aligned_bbox();
|
||||
|
||||
// Clamp the foreground image to the background image
|
||||
let start = (background_aabb.start * background_size).max(DVec2::ZERO).as_uvec2();
|
||||
|
|
@ -368,7 +377,9 @@ pub fn blend_stamp_closure(foreground: BrushStampGenerator<Color>, mut backgroun
|
|||
let foreground_point = background_to_foreground.transform_point2(background_point);
|
||||
|
||||
let Some(source_pixel) = foreground.sample(foreground_point, area) else { continue };
|
||||
let Some(destination_pixel) = background.element.data_mut().get_pixel_mut(x, y) else { continue };
|
||||
let Some(destination_pixel) = background.element_mut().data_mut().get_pixel_mut(x, y) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
*destination_pixel = map_fn(source_pixel, *destination_pixel);
|
||||
}
|
||||
|
|
@ -411,6 +422,6 @@ mod test {
|
|||
BrushCache::default(),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(image.iter().next().unwrap().element.width, 20);
|
||||
assert_eq!(image.element(0).unwrap().width, 20);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,24 +14,25 @@ use std::sync::{Arc, Mutex};
|
|||
// TODO: This is a temporary hack, be sure to not reuse this when the brush system is replaced/rewritten.
|
||||
static NEXT_BRUSH_CACHE_IMPL_ID: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
#[derive(Clone, Debug, DynAny, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Debug, DynAny)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
struct BrushCacheImpl {
|
||||
#[serde(default = "new_unique_id")]
|
||||
#[cfg_attr(feature = "serde", serde(default = "new_unique_id"))]
|
||||
unique_id: u64,
|
||||
// The full previous input that was cached.
|
||||
#[serde(default)]
|
||||
#[cfg_attr(feature = "serde", serde(default))]
|
||||
prev_input: Vec<BrushStroke>,
|
||||
|
||||
// The strokes that have been fully processed and blended into the background.
|
||||
#[serde(default, deserialize_with = "raster_types::image::migrate_image_frame_row")]
|
||||
#[cfg_attr(feature = "serde", serde(default, deserialize_with = "raster_types::image::migrate_image_frame_row"))]
|
||||
background: TableRow<Raster<CPU>>,
|
||||
#[serde(default, deserialize_with = "raster_types::image::migrate_image_frame_row")]
|
||||
#[cfg_attr(feature = "serde", serde(default, deserialize_with = "raster_types::image::migrate_image_frame_row"))]
|
||||
blended_image: TableRow<Raster<CPU>>,
|
||||
#[serde(default, deserialize_with = "raster_types::image::migrate_image_frame_row")]
|
||||
#[cfg_attr(feature = "serde", serde(default, deserialize_with = "raster_types::image::migrate_image_frame_row"))]
|
||||
last_stroke_texture: TableRow<Raster<CPU>>,
|
||||
|
||||
// A cache for brush textures.
|
||||
#[serde(skip)]
|
||||
#[cfg_attr(feature = "serde", serde(skip))]
|
||||
brush_texture_cache: HashMap<CacheHashWrapper<BrushStyle>, Raster<CPU>>,
|
||||
}
|
||||
|
||||
|
|
@ -63,11 +64,10 @@ impl BrushCacheImpl {
|
|||
background = std::mem::take(&mut self.blended_image);
|
||||
|
||||
// Check if the first non-blended stroke is an extension of the last one.
|
||||
let mut first_stroke_texture = TableRow {
|
||||
element: Raster::<CPU>::default(),
|
||||
transform: glam::DAffine2::ZERO,
|
||||
..Default::default()
|
||||
};
|
||||
let mut first_stroke_texture = TableRow::new_from_element(Raster::<CPU>::default())
|
||||
.with_attribute("transform", glam::DAffine2::ZERO)
|
||||
.with_attribute("alpha_blending", core_types::AlphaBlending::default())
|
||||
.with_attribute("source_node_id", None::<core_types::uuid::NodeId>);
|
||||
let mut first_stroke_point_skip = 0;
|
||||
let strokes = input[num_blended_strokes..].to_vec();
|
||||
if !strokes.is_empty() && self.prev_input.len() > num_blended_strokes {
|
||||
|
|
@ -135,7 +135,8 @@ pub struct BrushPlan {
|
|||
pub first_stroke_point_skip: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, DynAny, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Debug, Default, DynAny)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct BrushCache(Arc<Mutex<BrushCacheImpl>>);
|
||||
|
||||
// A bit of a cursed implementation to work around the current node system.
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ use core_types::math::bbox::AxisAlignedBbox;
|
|||
use dyn_any::DynAny;
|
||||
use glam::DVec2;
|
||||
/// The style of a brush.
|
||||
#[derive(Clone, Debug, CacheHash, DynAny, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Debug, CacheHash, DynAny)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct BrushStyle {
|
||||
pub color: Color,
|
||||
pub diameter: f64,
|
||||
|
|
@ -42,13 +43,15 @@ impl PartialEq for BrushStyle {
|
|||
}
|
||||
|
||||
/// A single sample of brush parameters across the brush stroke.
|
||||
#[derive(Clone, Debug, PartialEq, core_types::CacheHash, DynAny, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Debug, PartialEq, core_types::CacheHash, DynAny)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct BrushInputSample {
|
||||
pub position: DVec2,
|
||||
}
|
||||
|
||||
/// The parameters for a single stroke brush.
|
||||
#[derive(Clone, Debug, PartialEq, core_types::CacheHash, Default, DynAny, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Debug, PartialEq, core_types::CacheHash, Default, DynAny)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct BrushStroke {
|
||||
pub style: BrushStyle,
|
||||
pub trace: Vec<BrushInputSample>,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ license = "MIT OR Apache-2.0"
|
|||
|
||||
[features]
|
||||
default = ["serde"]
|
||||
serde = ["dep:serde", "core-types/serde", "raster-types/serde", "graphic-types/serde"]
|
||||
wasm = [
|
||||
"core-types/wasm",
|
||||
"raster-types/wasm",
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@ use raster_types::{CPU, GPU, Raster};
|
|||
|
||||
const DAY: f64 = 1000. * 3600. * 24.;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, dyn_any::DynAny, Default, Hash, CacheHash, node_macro::ChoiceType, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, dyn_any::DynAny, Default, Hash, CacheHash, node_macro::ChoiceType)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum RealTimeMode {
|
||||
#[label("UTC")]
|
||||
Utc,
|
||||
|
|
|
|||
|
|
@ -15,7 +15,8 @@ fn extract_xy<T: Into<DVec2>>(_: impl Ctx, #[implementations(DVec2, IVec2, UVec2
|
|||
|
||||
/// The X or Y component of a vec2.
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, CacheHash, DynAny, node_macro::ChoiceType, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, CacheHash, DynAny, node_macro::ChoiceType)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[widget(Radio)]
|
||||
pub enum XY {
|
||||
#[default]
|
||||
|
|
|
|||
|
|
@ -50,8 +50,7 @@ pub async fn create_artboard<T: IntoGraphicTable + 'n>(
|
|||
|
||||
let dimensions = dimensions.abs();
|
||||
|
||||
let background: Option<Color> = background.into();
|
||||
let background = background.unwrap_or(Color::WHITE);
|
||||
let background = background.element(0).copied().unwrap_or(Color::WHITE);
|
||||
|
||||
Table::new_from_element(Artboard {
|
||||
content,
|
||||
|
|
|
|||
|
|
@ -169,7 +169,8 @@ where
|
|||
|
||||
// Create and add mirrored instance
|
||||
for mut row in content.into_iter() {
|
||||
row.transform = reflected_transform * row.transform;
|
||||
let current_transform: DAffine2 = row.attribute_cloned_or_default("transform");
|
||||
row.set_attribute("transform", reflected_transform * current_transform);
|
||||
result_table.push(row);
|
||||
}
|
||||
|
||||
|
|
@ -199,8 +200,8 @@ pub async fn source_node_id<T: 'n + Send + Clone>(
|
|||
let source_node_id = node_path.get(node_path.len().wrapping_sub(2)).copied();
|
||||
|
||||
let mut content = content;
|
||||
for row in content.iter_mut() {
|
||||
*row.source_node_id = source_node_id;
|
||||
for source_id in content.iter_attribute_values_mut_or_default::<Option<NodeId>>("source_node_id") {
|
||||
*source_id = source_node_id;
|
||||
}
|
||||
|
||||
content
|
||||
|
|
@ -241,8 +242,9 @@ pub async fn legacy_layer_extend<T: 'n + Send + Clone>(
|
|||
let source_node_id = nested_node_path.get(nested_node_path.len().wrapping_sub(2)).copied();
|
||||
|
||||
let mut base = base;
|
||||
for row in new.into_iter() {
|
||||
base.push(TableRow { source_node_id, ..row });
|
||||
for mut row in new.into_iter() {
|
||||
row.set_attribute("source_node_id", source_node_id);
|
||||
base.push(row);
|
||||
}
|
||||
|
||||
base
|
||||
|
|
@ -290,9 +292,10 @@ pub async fn to_graphic<T: IntoGraphicTable + 'n>(
|
|||
pub async fn flatten_graphic(_: impl Ctx, content: Table<Graphic>, fully_flatten: bool) -> Table<Graphic> {
|
||||
// TODO: Avoid mutable reference, instead return a new Table<Graphic>?
|
||||
fn flatten_table(output_graphic_table: &mut Table<Graphic>, current_graphic_table: Table<Graphic>, fully_flatten: bool, recursion_depth: usize) {
|
||||
for current_row in current_graphic_table.iter() {
|
||||
let current_element = current_row.element.clone();
|
||||
let reference = *current_row.source_node_id;
|
||||
for index in 0..current_graphic_table.len() {
|
||||
let Some(current_element) = current_graphic_table.element(index) else { continue };
|
||||
let current_element = current_element.clone();
|
||||
let current_transform: DAffine2 = current_graphic_table.attribute_cloned_or_default("transform", index);
|
||||
|
||||
let recurse = fully_flatten || recursion_depth == 0;
|
||||
|
||||
|
|
@ -300,20 +303,16 @@ pub async fn flatten_graphic(_: impl Ctx, content: Table<Graphic>, fully_flatten
|
|||
// If we're allowed to recurse, flatten any graphics we encounter
|
||||
Graphic::Graphic(mut current_element) if recurse => {
|
||||
// Apply the parent graphic's transform to all child elements
|
||||
for graphic in current_element.iter_mut() {
|
||||
*graphic.transform = *current_row.transform * *graphic.transform;
|
||||
for graphic_transform in current_element.iter_attribute_values_mut_or_default::<DAffine2>("transform") {
|
||||
*graphic_transform = current_transform * *graphic_transform;
|
||||
}
|
||||
|
||||
flatten_table(output_graphic_table, current_element, fully_flatten, recursion_depth + 1);
|
||||
}
|
||||
// Push any leaf Graphic elements we encounter, which can be either Graphic table elements beyond the recursion depth, or table elements other than Graphic tables
|
||||
_ => {
|
||||
output_graphic_table.push(TableRow {
|
||||
element: current_element,
|
||||
transform: *current_row.transform,
|
||||
alpha_blending: *current_row.alpha_blending,
|
||||
source_node_id: reference,
|
||||
});
|
||||
let attributes = current_graphic_table.clone_row_attributes(index);
|
||||
output_graphic_table.push(TableRow::from_parts(current_element, attributes));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -370,20 +369,17 @@ fn colors_to_gradient<T: IntoGraphicTable + 'n + Send + Clone>(_: impl Ctx, #[im
|
|||
]));
|
||||
}
|
||||
|
||||
if let (Some(color), None) = {
|
||||
let mut colors_iter = colors.iter();
|
||||
(colors_iter.next(), colors_iter.next())
|
||||
} {
|
||||
if let (1, Some(&single_color)) = (total_colors, colors.element(0)) {
|
||||
return Table::new_from_element(GradientStops::new(vec![
|
||||
GradientStop {
|
||||
position: 0.,
|
||||
midpoint: 0.5,
|
||||
color: *color.element,
|
||||
color: single_color,
|
||||
},
|
||||
GradientStop {
|
||||
position: 1.,
|
||||
midpoint: 0.5,
|
||||
color: *color.element,
|
||||
color: single_color,
|
||||
},
|
||||
]));
|
||||
}
|
||||
|
|
@ -391,7 +387,7 @@ fn colors_to_gradient<T: IntoGraphicTable + 'n + Send + Clone>(_: impl Ctx, #[im
|
|||
let colors = colors.into_iter().enumerate().map(|(index, row)| GradientStop {
|
||||
position: index as f64 / (total_colors - 1) as f64,
|
||||
midpoint: 0.5,
|
||||
color: row.element,
|
||||
color: row.into_element(),
|
||||
});
|
||||
Table::new_from_element(GradientStops::new(colors))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -119,8 +119,8 @@ fn string_to_bytes(_: impl Ctx, string: String) -> Vec<u8> {
|
|||
/// Converts extracted raw RGBA pixel data from an input image. Each pixel becomes 4 sequential bytes. Useful for transmission over HTTP or writing to files.
|
||||
#[node_macro::node(category("Web Request"), name("Image to Bytes"))]
|
||||
fn image_to_bytes(_: impl Ctx, image: Table<Raster<CPU>>) -> Vec<u8> {
|
||||
let Some(image) = image.iter().next() else { return vec![] };
|
||||
image.element.data.iter().flat_map(|color| color.to_rgba8_srgb().into_iter()).collect::<Vec<u8>>()
|
||||
let Some(image) = image.element(0) else { return vec![] };
|
||||
image.data.iter().flat_map(|color| color.to_rgba8_srgb().into_iter()).collect::<Vec<u8>>()
|
||||
}
|
||||
|
||||
/// Loads binary from URLs and local asset paths. Returns a transparent placeholder if the resource fails to load, allowing rendering to continue.
|
||||
|
|
@ -187,6 +187,7 @@ where
|
|||
Table<T>: Render,
|
||||
{
|
||||
use core_types::table::TableRow;
|
||||
use glam::{DAffine2, DVec2};
|
||||
|
||||
if footprint.transform.matrix2.determinant() == 0. {
|
||||
log::trace!("Invalid footprint received for rasterization");
|
||||
|
|
@ -203,11 +204,11 @@ where
|
|||
..Default::default()
|
||||
};
|
||||
|
||||
for row in data.iter_mut() {
|
||||
*row.transform = glam::DAffine2::from_translation(-aabb.start) * *row.transform;
|
||||
for transform in data.iter_attribute_values_mut_or_default::<DAffine2>("transform") {
|
||||
*transform = DAffine2::from_translation(-aabb.start) * *transform;
|
||||
}
|
||||
data.render_svg(&mut render, &render_params);
|
||||
render.format_svg(glam::DVec2::ZERO, size);
|
||||
render.format_svg(DVec2::ZERO, size);
|
||||
let svg_string = render.svg.to_svg_string();
|
||||
|
||||
canvas.set_resolution(resolution);
|
||||
|
|
@ -228,9 +229,5 @@ where
|
|||
let rasterized = context.get_image_data(0., 0., resolution.x as f64, resolution.y as f64).unwrap();
|
||||
|
||||
let image = Image::from_image_data(&rasterized.data().0, resolution.x as u32, resolution.y as u32);
|
||||
Table::new_from_row(TableRow {
|
||||
element: Raster::new_cpu(image),
|
||||
transform: footprint.transform,
|
||||
..Default::default()
|
||||
})
|
||||
Table::new_from_row(TableRow::new_from_element(Raster::new_cpu(image)).with_attribute("transform", footprint.transform))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -866,10 +866,10 @@ fn gradient_value(_: impl Ctx, _primary: (), gradient: Table<GradientStops>) ->
|
|||
/// Gets the color at the specified position along the gradient, given a position from 0 (left) to 1 (right).
|
||||
#[node_macro::node(category("Color"))]
|
||||
fn sample_gradient(_: impl Ctx, _primary: (), gradient: Table<GradientStops>, position: Fraction) -> Table<Color> {
|
||||
let Some(row) = gradient.get(0) else { return Table::new() };
|
||||
let Some(gradient) = gradient.element(0) else { return Table::new() };
|
||||
|
||||
let position = position.clamp(0., 1.);
|
||||
let color = row.element.evaluate(position);
|
||||
let color = gradient.evaluate(position);
|
||||
Table::new_from_element(color)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use core_types::table::{Table, TableRow, TableRowRef};
|
||||
use core_types::{Color, Ctx};
|
||||
use core_types::table::{Table, TableRow};
|
||||
use core_types::uuid::NodeId;
|
||||
use core_types::{AlphaBlending, Color, Ctx};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphic_types::vector_types::subpath::{ManipulatorGroup, Subpath};
|
||||
use graphic_types::vector_types::vector::PointId;
|
||||
|
|
@ -34,19 +35,22 @@ async fn boolean_operation<I: graphic_types::IntoGraphicTable + 'n + Send + Clon
|
|||
let content = content.into_graphic_table();
|
||||
|
||||
// The first index is the bottom of the stack
|
||||
let mut result_vector_table = boolean_operation_on_vector_table(flatten_vector(&content).iter(), operation);
|
||||
let flattened = flatten_vector(&content);
|
||||
let mut result_vector_table = boolean_operation_on_vector_table(&flattened, operation);
|
||||
|
||||
// Replace the transformation matrix with a mutation of the vector points themselves
|
||||
if let Some(result_vector) = result_vector_table.iter_mut().next() {
|
||||
let transform = *result_vector.transform;
|
||||
*result_vector.transform = DAffine2::IDENTITY;
|
||||
if result_vector_table.element_mut(0).is_some() {
|
||||
let transform: DAffine2 = result_vector_table.attribute_cloned_or_default("transform", 0);
|
||||
result_vector_table.set_attribute("transform", 0, DAffine2::IDENTITY);
|
||||
|
||||
Vector::transform(result_vector.element, transform);
|
||||
result_vector.element.style.set_stroke_transform(DAffine2::IDENTITY);
|
||||
result_vector.element.upstream_data = Some(content.clone());
|
||||
let result_vector = result_vector_table.element_mut(0).unwrap();
|
||||
Vector::transform(result_vector, transform);
|
||||
result_vector.style.set_stroke_transform(DAffine2::IDENTITY);
|
||||
result_vector.upstream_data = Some(content.clone());
|
||||
|
||||
// Clean up the boolean operation result by merging duplicated points
|
||||
result_vector.element.merge_by_distance_spatial(*result_vector.transform, 0.0001);
|
||||
let merge_transform: DAffine2 = result_vector_table.attribute_cloned_or_default("transform", 0);
|
||||
result_vector_table.element_mut(0).unwrap().merge_by_distance_spatial(merge_transform, 0.0001);
|
||||
}
|
||||
|
||||
result_vector_table
|
||||
|
|
@ -106,26 +110,34 @@ impl WindingNumber {
|
|||
}
|
||||
}
|
||||
|
||||
fn boolean_operation_on_vector_table<'a>(vector: impl DoubleEndedIterator<Item = TableRowRef<'a, Vector>> + Clone, boolean_operation: BooleanOperation) -> Table<Vector> {
|
||||
fn boolean_operation_on_vector_table(vector: &Table<Vector>, boolean_operation: BooleanOperation) -> Table<Vector> {
|
||||
const EPSILON: f64 = 1e-5;
|
||||
let mut table = Table::new();
|
||||
let mut paths = Vec::new();
|
||||
let mut row = TableRow::<Vector>::default();
|
||||
|
||||
let copy_from = if matches!(boolean_operation, BooleanOperation::SubtractFront) {
|
||||
vector.clone().next()
|
||||
let copy_from_index = if matches!(boolean_operation, BooleanOperation::SubtractFront) {
|
||||
if !vector.is_empty() { Some(0) } else { None }
|
||||
} else {
|
||||
vector.clone().next_back()
|
||||
if !vector.is_empty() { Some(vector.len() - 1) } else { None }
|
||||
};
|
||||
let mut row = if let Some(index) = copy_from_index {
|
||||
let mut attributes = vector.clone_row_attributes(index);
|
||||
// The boolean op bakes input transforms into the output geometry, so the result row carries no transform of its own
|
||||
attributes.insert("transform", DAffine2::IDENTITY);
|
||||
let copy_from = vector.element(index).unwrap();
|
||||
let element = Vector {
|
||||
style: copy_from.style.clone(),
|
||||
upstream_data: copy_from.upstream_data.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
TableRow::from_parts(element, attributes)
|
||||
} else {
|
||||
TableRow::<Vector>::default()
|
||||
};
|
||||
if let Some(copy_from) = copy_from {
|
||||
row.alpha_blending = *copy_from.alpha_blending;
|
||||
row.source_node_id = *copy_from.source_node_id;
|
||||
row.element.style = copy_from.element.style.clone();
|
||||
row.element.upstream_data = copy_from.element.upstream_data.clone();
|
||||
}
|
||||
|
||||
for element in vector {
|
||||
paths.push(to_bez_path(element.element, *element.transform));
|
||||
for index in 0..vector.len() {
|
||||
let element = vector.element(index).unwrap();
|
||||
paths.push(to_bez_path(element, vector.attribute_cloned_or_default("transform", index)));
|
||||
}
|
||||
|
||||
let top = match Topology::<WindingNumber>::from_paths(paths.iter().enumerate().map(|(idx, path)| (path, (idx, paths.len()))), EPSILON) {
|
||||
|
|
@ -139,7 +151,7 @@ fn boolean_operation_on_vector_table<'a>(vector: impl DoubleEndedIterator<Item =
|
|||
let contours = top.contours(|winding| winding.is_inside(boolean_operation));
|
||||
|
||||
for subpath in from_bez_paths(contours.contours().map(|c| &c.path)) {
|
||||
row.element.append_subpath(subpath, false);
|
||||
row.element_mut().append_subpath(subpath, false);
|
||||
}
|
||||
|
||||
table.push(row);
|
||||
|
|
@ -147,95 +159,107 @@ fn boolean_operation_on_vector_table<'a>(vector: impl DoubleEndedIterator<Item =
|
|||
}
|
||||
|
||||
fn flatten_vector(graphic_table: &Table<Graphic>) -> Table<Vector> {
|
||||
graphic_table
|
||||
.iter()
|
||||
.flat_map(|element| {
|
||||
match element.element.clone() {
|
||||
(0..graphic_table.len())
|
||||
.flat_map(|index| {
|
||||
let graphic = graphic_table.element(index).unwrap();
|
||||
match graphic.clone() {
|
||||
Graphic::Vector(vector) => {
|
||||
// Apply the parent graphic's transform to each element of the vector table
|
||||
let parent_transform: DAffine2 = graphic_table.attribute_cloned_or_default("transform", index);
|
||||
vector
|
||||
.into_iter()
|
||||
.map(|mut sub_vector| {
|
||||
sub_vector.transform = *element.transform * sub_vector.transform;
|
||||
|
||||
let current_transform: DAffine2 = sub_vector.attribute_cloned_or_default("transform");
|
||||
*sub_vector.attribute_mut_or_insert_default("transform") = parent_transform * current_transform;
|
||||
sub_vector
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
Graphic::RasterCPU(image) => {
|
||||
let make_row = |transform| {
|
||||
// Convert the image frame into a rectangular subpath with the image's transform
|
||||
let parent_transform: DAffine2 = graphic_table.attribute_cloned_or_default("transform", index);
|
||||
let make_row = |transform, source_node_id, alpha_blending| {
|
||||
let mut subpath = Subpath::new_rectangle(DVec2::ZERO, DVec2::ONE);
|
||||
subpath.apply_transform(transform);
|
||||
|
||||
// Create a vector table row from the rectangular subpath, with a default black fill
|
||||
let mut element = Vector::from_subpath(subpath);
|
||||
element.style.set_fill(Fill::Solid(Color::BLACK));
|
||||
|
||||
TableRow { element, ..Default::default() }
|
||||
TableRow::new_from_element(element)
|
||||
.with_attribute("alpha_blending", alpha_blending)
|
||||
.with_attribute("source_node_id", source_node_id)
|
||||
};
|
||||
|
||||
// Apply the parent graphic's transform to each raster element
|
||||
image.iter().map(|row| make_row(*element.transform * *row.transform)).collect::<Vec<_>>()
|
||||
// Apply the parent graphic's transform to each raster element, preserving each row's source_node_id
|
||||
// and alpha_blending so the boolean op downstream can route clicks (and inherit blending state)
|
||||
// back to the originating raster layer
|
||||
(0..image.len())
|
||||
.map(|i| {
|
||||
let row_transform: DAffine2 = image.attribute_cloned_or_default("transform", i);
|
||||
let source_node_id: Option<NodeId> = image.attribute_cloned_or_default("source_node_id", i);
|
||||
let alpha_blending: AlphaBlending = image.attribute_cloned_or_default("alpha_blending", i);
|
||||
make_row(parent_transform * row_transform, source_node_id, alpha_blending)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
Graphic::RasterGPU(image) => {
|
||||
let make_row = |transform| {
|
||||
// Convert the image frame into a rectangular subpath with the image's transform
|
||||
let parent_transform: DAffine2 = graphic_table.attribute_cloned_or_default("transform", index);
|
||||
let make_row = |transform, source_node_id, alpha_blending| {
|
||||
let mut subpath = Subpath::new_rectangle(DVec2::ZERO, DVec2::ONE);
|
||||
subpath.apply_transform(transform);
|
||||
|
||||
// Create a vector table row from the rectangular subpath, with a default black fill
|
||||
let mut element = Vector::from_subpath(subpath);
|
||||
element.style.set_fill(Fill::Solid(Color::BLACK));
|
||||
|
||||
TableRow { element, ..Default::default() }
|
||||
TableRow::new_from_element(element)
|
||||
.with_attribute("alpha_blending", alpha_blending)
|
||||
.with_attribute("source_node_id", source_node_id)
|
||||
};
|
||||
|
||||
// Apply the parent graphic's transform to each raster element
|
||||
image.iter().map(|row| make_row(*element.transform * *row.transform)).collect::<Vec<_>>()
|
||||
// Apply the parent graphic's transform to each raster element, preserving each row's source_node_id
|
||||
// and alpha_blending so the boolean op downstream can route clicks (and inherit blending state)
|
||||
// back to the originating raster layer
|
||||
(0..image.len())
|
||||
.map(|i| {
|
||||
let row_transform: DAffine2 = image.attribute_cloned_or_default("transform", i);
|
||||
let source_node_id: Option<NodeId> = image.attribute_cloned_or_default("source_node_id", i);
|
||||
let alpha_blending: AlphaBlending = image.attribute_cloned_or_default("alpha_blending", i);
|
||||
make_row(parent_transform * row_transform, source_node_id, alpha_blending)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
Graphic::Graphic(mut graphic) => {
|
||||
let parent_transform: DAffine2 = graphic_table.attribute_cloned_or_default("transform", index);
|
||||
// Apply the parent graphic's transform to each element of inner table
|
||||
for sub_element in graphic.iter_mut() {
|
||||
*sub_element.transform = *element.transform * *sub_element.transform;
|
||||
for transform in graphic.iter_attribute_values_mut_or_default::<DAffine2>("transform") {
|
||||
*transform = parent_transform * *transform;
|
||||
}
|
||||
|
||||
// Recursively flatten the inner table into the output vector table
|
||||
let unioned = boolean_operation_on_vector_table(flatten_vector(&graphic).iter(), BooleanOperation::Union);
|
||||
let flattened = flatten_vector(&graphic);
|
||||
let unioned = boolean_operation_on_vector_table(&flattened, BooleanOperation::Union);
|
||||
|
||||
unioned.into_iter().collect::<Vec<_>>()
|
||||
}
|
||||
Graphic::Color(color) => color
|
||||
.into_iter()
|
||||
.map(|row| {
|
||||
let (color, attributes) = row.into_parts();
|
||||
let mut element = Vector::default();
|
||||
element.style.set_fill(Fill::Solid(row.element));
|
||||
element.style.set_fill(Fill::Solid(color));
|
||||
element.style.set_stroke_transform(DAffine2::IDENTITY);
|
||||
|
||||
TableRow {
|
||||
element,
|
||||
transform: row.transform,
|
||||
alpha_blending: row.alpha_blending,
|
||||
source_node_id: row.source_node_id,
|
||||
}
|
||||
TableRow::from_parts(element, attributes)
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
Graphic::Gradient(gradient) => gradient
|
||||
.into_iter()
|
||||
.map(|row| {
|
||||
let (stops, attributes) = row.into_parts();
|
||||
let mut element = Vector::default();
|
||||
element.style.set_fill(Fill::Gradient(graphic_types::vector_types::gradient::Gradient {
|
||||
stops: row.element,
|
||||
..Default::default()
|
||||
}));
|
||||
element.style.set_fill(Fill::Gradient(graphic_types::vector_types::gradient::Gradient { stops, ..Default::default() }));
|
||||
element.style.set_stroke_transform(DAffine2::IDENTITY);
|
||||
|
||||
TableRow {
|
||||
element,
|
||||
transform: row.transform,
|
||||
alpha_blending: row.alpha_blending,
|
||||
source_node_id: row.source_node_id,
|
||||
}
|
||||
TableRow::from_parts(element, attributes)
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,8 +11,10 @@ workspace = true
|
|||
|
||||
[features]
|
||||
default = ["std"]
|
||||
serde = ["dep:serde", "core-types?/serde", "raster-types?/serde", "vector-types?/serde"]
|
||||
shader-nodes = ["std", "dep:raster-nodes-shaders", "dep:wgpu-executor"]
|
||||
std = [
|
||||
"serde",
|
||||
"dep:core-types",
|
||||
"dep:dyn-any",
|
||||
"dep:graphene-hash",
|
||||
|
|
@ -23,7 +25,6 @@ std = [
|
|||
"dep:rand",
|
||||
"dep:rand_chacha",
|
||||
"dep:fastnoise-lite",
|
||||
"dep:serde",
|
||||
"dep:kurbo",
|
||||
]
|
||||
wasm = [
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@ mod adjust_std {
|
|||
|
||||
impl Adjust<Color> for Table<Raster<CPU>> {
|
||||
fn adjust(&mut self, map_fn: impl Fn(&Color) -> Color) {
|
||||
for row in self.iter_mut() {
|
||||
for color in row.element.data_mut().data.iter_mut() {
|
||||
for element in self.iter_element_values_mut() {
|
||||
for color in element.data_mut().data.iter_mut() {
|
||||
*color = map_fn(color);
|
||||
}
|
||||
}
|
||||
|
|
@ -27,15 +27,15 @@ mod adjust_std {
|
|||
}
|
||||
impl Adjust<Color> for Table<Color> {
|
||||
fn adjust(&mut self, map_fn: impl Fn(&Color) -> Color) {
|
||||
for row in self.iter_mut() {
|
||||
*row.element = map_fn(row.element);
|
||||
for element in self.iter_element_values_mut() {
|
||||
*element = map_fn(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Adjust<Color> for Table<GradientStops> {
|
||||
fn adjust(&mut self, map_fn: impl Fn(&Color) -> Color) {
|
||||
for row in self.iter_mut() {
|
||||
row.element.adjust(&map_fn);
|
||||
for element in self.iter_element_values_mut() {
|
||||
element.adjust(&map_fn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,8 @@ use vector_types::GradientStops;
|
|||
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=Color%20Lookup%20(Photoshop%20CS6
|
||||
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[cfg_attr(feature = "std", derive(dyn_any::DynAny, serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "std", derive(dyn_any::DynAny))]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, node_macro::ChoiceType, bytemuck::NoUninit, BufferStruct, FromPrimitive, IntoPrimitive)]
|
||||
#[widget(Dropdown)]
|
||||
#[repr(u32)]
|
||||
|
|
@ -565,7 +566,8 @@ fn vibrance<T: Adjust<Color>>(
|
|||
|
||||
#[repr(u32)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[cfg_attr(feature = "std", derive(dyn_any::DynAny, serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "std", derive(dyn_any::DynAny))]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, node_macro::ChoiceType, BufferStruct, FromPrimitive, IntoPrimitive)]
|
||||
#[widget(Radio)]
|
||||
pub enum RedGreenBlue {
|
||||
|
|
@ -576,7 +578,8 @@ pub enum RedGreenBlue {
|
|||
}
|
||||
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[cfg_attr(feature = "std", derive(dyn_any::DynAny, serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "std", derive(dyn_any::DynAny))]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, node_macro::ChoiceType, bytemuck::NoUninit, BufferStruct, FromPrimitive, IntoPrimitive)]
|
||||
#[widget(Radio)]
|
||||
#[repr(u32)]
|
||||
|
|
@ -590,7 +593,8 @@ pub enum RedGreenBlueAlpha {
|
|||
|
||||
/// Style of noise pattern.
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[cfg_attr(feature = "std", derive(dyn_any::DynAny, serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "std", derive(dyn_any::DynAny))]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, node_macro::ChoiceType)]
|
||||
#[widget(Dropdown)]
|
||||
pub enum NoiseType {
|
||||
|
|
@ -608,7 +612,8 @@ pub enum NoiseType {
|
|||
|
||||
/// Style of layered levels of the noise pattern.
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[cfg_attr(feature = "std", derive(dyn_any::DynAny, serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "std", derive(dyn_any::DynAny))]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, node_macro::ChoiceType)]
|
||||
pub enum FractalType {
|
||||
#[default]
|
||||
|
|
@ -625,7 +630,8 @@ pub enum FractalType {
|
|||
|
||||
/// Distance function used by the cellular noise.
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[cfg_attr(feature = "std", derive(dyn_any::DynAny, serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "std", derive(dyn_any::DynAny))]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, node_macro::ChoiceType)]
|
||||
pub enum CellularDistanceFunction {
|
||||
#[default]
|
||||
|
|
@ -637,7 +643,8 @@ pub enum CellularDistanceFunction {
|
|||
}
|
||||
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[cfg_attr(feature = "std", derive(dyn_any::DynAny, serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "std", derive(dyn_any::DynAny))]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, node_macro::ChoiceType)]
|
||||
pub enum CellularReturnType {
|
||||
CellValue,
|
||||
|
|
@ -657,7 +664,8 @@ pub enum CellularReturnType {
|
|||
}
|
||||
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[cfg_attr(feature = "std", derive(dyn_any::DynAny, serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "std", derive(dyn_any::DynAny))]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, node_macro::ChoiceType)]
|
||||
#[widget(Dropdown)]
|
||||
pub enum DomainWarpType {
|
||||
|
|
@ -771,7 +779,8 @@ fn channel_mixer<T: Adjust<Color>>(
|
|||
|
||||
#[repr(u32)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[cfg_attr(feature = "std", derive(dyn_any::DynAny, serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "std", derive(dyn_any::DynAny))]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, node_macro::ChoiceType, BufferStruct, FromPrimitive, IntoPrimitive)]
|
||||
#[widget(Radio)]
|
||||
pub enum RelativeAbsolute {
|
||||
|
|
@ -782,7 +791,8 @@ pub enum RelativeAbsolute {
|
|||
|
||||
#[repr(u32)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[cfg_attr(feature = "std", derive(dyn_any::DynAny, serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "std", derive(dyn_any::DynAny))]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, node_macro::ChoiceType, BufferStruct, FromPrimitive, IntoPrimitive)]
|
||||
pub enum SelectiveColorChoice {
|
||||
#[default]
|
||||
|
|
|
|||
|
|
@ -30,13 +30,17 @@ mod blend_std {
|
|||
impl Blend<Color> for Table<Raster<CPU>> {
|
||||
fn blend(&self, under: &Self, blend_fn: impl Fn(Color, Color) -> Color) -> Self {
|
||||
let mut result_table = self.clone();
|
||||
for (over, under) in result_table.iter_mut().zip(under.iter()) {
|
||||
let data = over.element.data.iter().zip(under.element.data.iter()).map(|(a, b)| blend_fn(*a, *b)).collect();
|
||||
let pair_count = result_table.len().min(under.len());
|
||||
for index in 0..pair_count {
|
||||
let Some(over) = result_table.element(index) else { break };
|
||||
let Some(under_element) = under.element(index) else { break };
|
||||
let data = over.data.iter().zip(under_element.data.iter()).map(|(a, b)| blend_fn(*a, *b)).collect();
|
||||
let (width, height) = (over.width, over.height);
|
||||
|
||||
*over.element = Raster::new_cpu(Image {
|
||||
*result_table.element_mut(index).unwrap() = Raster::new_cpu(Image {
|
||||
data,
|
||||
width: over.element.width,
|
||||
height: over.element.height,
|
||||
width,
|
||||
height,
|
||||
base64_string: None,
|
||||
});
|
||||
}
|
||||
|
|
@ -46,8 +50,12 @@ mod blend_std {
|
|||
impl Blend<Color> for Table<Color> {
|
||||
fn blend(&self, under: &Self, blend_fn: impl Fn(Color, Color) -> Color) -> Self {
|
||||
let mut result_table = self.clone();
|
||||
for (over, under) in result_table.iter_mut().zip(under.iter()) {
|
||||
*over.element = blend_fn(*over.element, *under.element);
|
||||
let pair_count = result_table.len().min(under.len());
|
||||
for index in 0..pair_count {
|
||||
let Some(over) = result_table.element(index) else { break };
|
||||
let Some(under_element) = under.element(index) else { break };
|
||||
let new_val = blend_fn(*over, *under_element);
|
||||
*result_table.element_mut(index).unwrap() = new_val;
|
||||
}
|
||||
result_table
|
||||
}
|
||||
|
|
@ -55,8 +63,12 @@ mod blend_std {
|
|||
impl Blend<Color> for Table<GradientStops> {
|
||||
fn blend(&self, under: &Self, blend_fn: impl Fn(Color, Color) -> Color) -> Self {
|
||||
let mut result_table = self.clone();
|
||||
for (over, under) in result_table.iter_mut().zip(under.iter()) {
|
||||
*over.element = over.element.blend(under.element, &blend_fn);
|
||||
let pair_count = result_table.len().min(under.len());
|
||||
for index in 0..pair_count {
|
||||
let Some(over) = result_table.element(index) else { break };
|
||||
let Some(under_element) = under.element(index) else { break };
|
||||
let new_val = over.blend(under_element, &blend_fn);
|
||||
*result_table.element_mut(index).unwrap() = new_val;
|
||||
}
|
||||
result_table
|
||||
}
|
||||
|
|
@ -201,7 +213,7 @@ mod test {
|
|||
let opacity = 100.;
|
||||
|
||||
let result = super::color_overlay((), Table::new_from_element(Raster::new_cpu(image.clone())), overlay_color, BlendMode::Multiply, opacity);
|
||||
let result = result.iter().next().unwrap().element;
|
||||
let result = result.element(0).unwrap().clone();
|
||||
|
||||
// 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.data[0], Color::from_rgbaf32_unchecked(0., image_color.g(), 0., image_color.a()));
|
||||
|
|
|
|||
|
|
@ -4,13 +4,14 @@ use dyn_any::{DynAny, StaticType, StaticTypeSized};
|
|||
use std::ops::{Add, Mul, Sub};
|
||||
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Debug, Clone, PartialEq, core_types::CacheHash, DynAny, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Debug, Clone, PartialEq, core_types::CacheHash, DynAny)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct Curve {
|
||||
#[serde(rename = "manipulatorGroups")]
|
||||
#[cfg_attr(feature = "serde", serde(rename = "manipulatorGroups"))]
|
||||
pub manipulator_groups: Vec<CurveManipulatorGroup>,
|
||||
#[serde(rename = "firstHandle")]
|
||||
#[cfg_attr(feature = "serde", serde(rename = "firstHandle"))]
|
||||
pub first_handle: [f32; 2],
|
||||
#[serde(rename = "lastHandle")]
|
||||
#[cfg_attr(feature = "serde", serde(rename = "lastHandle"))]
|
||||
pub last_handle: [f32; 2],
|
||||
}
|
||||
|
||||
|
|
@ -25,7 +26,8 @@ impl Default for Curve {
|
|||
}
|
||||
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, core_types::CacheHash, DynAny, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, core_types::CacheHash, DynAny)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct CurveManipulatorGroup {
|
||||
pub anchor: [f32; 2],
|
||||
pub handles: [[f32; 2]; 2],
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ async fn dehaze(_: impl Ctx, image_frame: Table<Raster<CPU>>, strength: Percenta
|
|||
image_frame
|
||||
.into_iter()
|
||||
.map(|mut row| {
|
||||
let image = row.element;
|
||||
let image = std::mem::replace(row.element_mut(), Raster::new_cpu(Image::default()));
|
||||
// Prepare the image data for processing
|
||||
let image_data = bytemuck::cast_vec(image.data.clone());
|
||||
let image_buffer = image::Rgba32FImage::from_raw(image.width, image.height, image_data).expect("Failed to convert internal image format into image-rs data type.");
|
||||
|
|
@ -31,7 +31,7 @@ async fn dehaze(_: impl Ctx, image_frame: Table<Raster<CPU>>, strength: Percenta
|
|||
base64_string: None,
|
||||
};
|
||||
|
||||
row.element = Raster::new_cpu(dehazed_image);
|
||||
*row.element_mut() = Raster::new_cpu(dehazed_image);
|
||||
row
|
||||
})
|
||||
.collect()
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ async fn blur(
|
|||
image_frame
|
||||
.into_iter()
|
||||
.map(|mut row| {
|
||||
let image = row.element.clone();
|
||||
let image = row.element().clone();
|
||||
|
||||
// Run blur algorithm
|
||||
let blurred_image = if radius < 0.1 {
|
||||
|
|
@ -36,7 +36,7 @@ async fn blur(
|
|||
Raster::new_cpu(gaussian_blur_algorithm(image.into_data(), radius, gamma))
|
||||
};
|
||||
|
||||
row.element = blurred_image;
|
||||
*row.element_mut() = blurred_image;
|
||||
row
|
||||
})
|
||||
.collect()
|
||||
|
|
@ -56,7 +56,7 @@ async fn median_filter(
|
|||
image_frame
|
||||
.into_iter()
|
||||
.map(|mut row| {
|
||||
let image = row.element.clone();
|
||||
let image = row.element().clone();
|
||||
|
||||
// Apply median filter
|
||||
let filtered_image = if radius < 0.5 {
|
||||
|
|
@ -66,7 +66,7 @@ async fn median_filter(
|
|||
Raster::new_cpu(median_filter_algorithm(image.into_data(), radius as u32))
|
||||
};
|
||||
|
||||
row.element = filtered_image;
|
||||
*row.element_mut() = filtered_image;
|
||||
row
|
||||
})
|
||||
.collect()
|
||||
|
|
|
|||
|
|
@ -21,12 +21,12 @@ async fn gradient_map<T: Adjust<Color>>(
|
|||
gradient: Table<GradientStops>,
|
||||
reverse: bool,
|
||||
) -> T {
|
||||
let Some(row) = gradient.get(0) else { return image };
|
||||
let Some(gradient) = gradient.element(0) else { return image };
|
||||
|
||||
image.adjust(|color| {
|
||||
let intensity = color.luminance_srgb();
|
||||
let intensity = if reverse { 1. - intensity } else { intensity };
|
||||
row.element.evaluate(intensity as f64).to_linear_srgb()
|
||||
gradient.evaluate(intensity as f64).to_linear_srgb()
|
||||
});
|
||||
|
||||
image
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@ async fn image_color_palette(
|
|||
let mut histogram = vec![0; (bins + 1.) as usize];
|
||||
let mut color_bins = vec![Vec::new(); (bins + 1.) as usize];
|
||||
|
||||
for row in image.iter() {
|
||||
for pixel in row.element.data.iter() {
|
||||
for element in image.iter_element_values() {
|
||||
for pixel in element.data.iter() {
|
||||
let r = pixel.r() * GRID;
|
||||
let g = pixel.g() * GRID;
|
||||
let b = pixel.b() * GRID;
|
||||
|
|
|
|||
|
|
@ -33,9 +33,9 @@ impl From<std::io::Error> for Error {
|
|||
pub fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: Table<Raster<CPU>>) -> Table<Raster<CPU>> {
|
||||
image_frame
|
||||
.into_iter()
|
||||
.filter_map(|mut row| {
|
||||
let image_frame_transform = row.transform;
|
||||
let image = row.element;
|
||||
.filter_map(|row| {
|
||||
let image_frame_transform: DAffine2 = row.attribute_cloned_or_default("transform");
|
||||
let (image, mut attributes) = row.into_parts();
|
||||
|
||||
// Resize the image using the image crate
|
||||
let data = bytemuck::cast_vec(image.data.clone());
|
||||
|
|
@ -86,10 +86,9 @@ pub fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: Tabl
|
|||
// we need to adjust the offset if we truncate the offset calculation
|
||||
|
||||
let new_transform = image_frame_transform * DAffine2::from_translation(offset) * DAffine2::from_scale(size);
|
||||
attributes.insert("transform", new_transform);
|
||||
|
||||
row.transform = new_transform;
|
||||
row.element = Raster::new_cpu(image);
|
||||
Some(row)
|
||||
Some(TableRow::from_parts(Raster::new_cpu(image), attributes))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
|
@ -114,23 +113,20 @@ pub fn combine_channels(
|
|||
.zip(alpha)
|
||||
.filter_map(|(((red, green), blue), alpha)| {
|
||||
// Turn any default zero-sized image rows into None
|
||||
let red = red.filter(|i| i.element.width > 0 && i.element.height > 0);
|
||||
let green = green.filter(|i| i.element.width > 0 && i.element.height > 0);
|
||||
let blue = blue.filter(|i| i.element.width > 0 && i.element.height > 0);
|
||||
let alpha = alpha.filter(|i| i.element.width > 0 && i.element.height > 0);
|
||||
let red = red.filter(|i| i.element().width > 0 && i.element().height > 0);
|
||||
let green = green.filter(|i| i.element().width > 0 && i.element().height > 0);
|
||||
let blue = blue.filter(|i| i.element().width > 0 && i.element().height > 0);
|
||||
let alpha = alpha.filter(|i| i.element().width > 0 && i.element().height > 0);
|
||||
|
||||
// Get this row's transform and alpha blending mode from the first non-empty channel
|
||||
let (transform, alpha_blending, source_node_id) = [&red, &green, &blue, &alpha]
|
||||
.iter()
|
||||
.find_map(|i| i.as_ref())
|
||||
.map(|i| (i.transform, i.alpha_blending, i.source_node_id))?;
|
||||
let attributes = [&red, &green, &blue, &alpha].iter().find_map(|i| i.as_ref()).map(|i| i.attributes().clone())?;
|
||||
|
||||
// Get the common width and height of the channels, which must have equal dimensions
|
||||
let channel_dimensions = [
|
||||
red.as_ref().map(|r| (r.element.width, r.element.height)),
|
||||
green.as_ref().map(|g| (g.element.width, g.element.height)),
|
||||
blue.as_ref().map(|b| (b.element.width, b.element.height)),
|
||||
alpha.as_ref().map(|a| (a.element.width, a.element.height)),
|
||||
red.as_ref().map(|r| (r.element().width, r.element().height)),
|
||||
green.as_ref().map(|g| (g.element().width, g.element().height)),
|
||||
blue.as_ref().map(|b| (b.element().width, b.element().height)),
|
||||
alpha.as_ref().map(|a| (a.element().width, a.element().height)),
|
||||
];
|
||||
if channel_dimensions.iter().all(Option::is_none)
|
||||
|| channel_dimensions
|
||||
|
|
@ -150,22 +146,22 @@ pub fn combine_channels(
|
|||
for x in 0..image.width() {
|
||||
let image_pixel = image.get_pixel_mut(x, y).unwrap();
|
||||
|
||||
if let Some(r) = red.as_ref().and_then(|r| r.element.get_pixel(x, y)) {
|
||||
if let Some(r) = red.as_ref().and_then(|r| r.element().get_pixel(x, y)) {
|
||||
image_pixel.set_red(r.l().cast_linear_channel());
|
||||
} else {
|
||||
image_pixel.set_red(Channel::from_linear(0.));
|
||||
}
|
||||
if let Some(g) = green.as_ref().and_then(|g| g.element.get_pixel(x, y)) {
|
||||
if let Some(g) = green.as_ref().and_then(|g| g.element().get_pixel(x, y)) {
|
||||
image_pixel.set_green(g.l().cast_linear_channel());
|
||||
} else {
|
||||
image_pixel.set_green(Channel::from_linear(0.));
|
||||
}
|
||||
if let Some(b) = blue.as_ref().and_then(|b| b.element.get_pixel(x, y)) {
|
||||
if let Some(b) = blue.as_ref().and_then(|b| b.element().get_pixel(x, y)) {
|
||||
image_pixel.set_blue(b.l().cast_linear_channel());
|
||||
} else {
|
||||
image_pixel.set_blue(Channel::from_linear(0.));
|
||||
}
|
||||
if let Some(a) = alpha.as_ref().and_then(|a| a.element.get_pixel(x, y)) {
|
||||
if let Some(a) = alpha.as_ref().and_then(|a| a.element().get_pixel(x, y)) {
|
||||
image_pixel.set_alpha(a.l().cast_linear_channel());
|
||||
} else {
|
||||
image_pixel.set_alpha(Channel::from_linear(1.));
|
||||
|
|
@ -173,12 +169,7 @@ pub fn combine_channels(
|
|||
}
|
||||
}
|
||||
|
||||
Some(TableRow {
|
||||
element: Raster::new_cpu(image),
|
||||
transform,
|
||||
alpha_blending,
|
||||
source_node_id,
|
||||
})
|
||||
Some(TableRow::from_parts(Raster::new_cpu(image), attributes))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
|
@ -197,32 +188,34 @@ pub fn mask(
|
|||
// No stencil provided so we return the original image
|
||||
return image;
|
||||
};
|
||||
let stencil_size = DVec2::new(stencil.element.width as f64, stencil.element.height as f64);
|
||||
let stencil_size = DVec2::new(stencil.element().width as f64, stencil.element().height as f64);
|
||||
|
||||
image
|
||||
.into_iter()
|
||||
.filter_map(|mut row| {
|
||||
let image_size = DVec2::new(row.element.width as f64, row.element.height as f64);
|
||||
let mask_size = stencil.transform.scale_magnitudes();
|
||||
let image_size = DVec2::new(row.element().width as f64, row.element().height as f64);
|
||||
let stencil_transform: DAffine2 = stencil.attribute_cloned_or_default("transform");
|
||||
let mask_size = stencil_transform.scale_magnitudes();
|
||||
|
||||
if mask_size == DVec2::ZERO {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Transforms a point from the background image to the foreground image
|
||||
let bg_to_fg = row.transform * DAffine2::from_scale(1. / image_size);
|
||||
let stencil_transform_inverse = stencil.transform.inverse();
|
||||
let transform_attribute: DAffine2 = row.attribute_cloned_or_default("transform");
|
||||
let bg_to_fg = transform_attribute * DAffine2::from_scale(1. / image_size);
|
||||
let stencil_transform_inverse = stencil_transform.inverse();
|
||||
|
||||
for y in 0..row.element.height {
|
||||
for x in 0..row.element.width {
|
||||
for y in 0..row.element().height {
|
||||
for x in 0..row.element().width {
|
||||
let image_point = DVec2::new(x as f64, y as f64);
|
||||
let mask_point = bg_to_fg.transform_point2(image_point);
|
||||
let local_mask_point = stencil_transform_inverse.transform_point2(mask_point);
|
||||
let mask_point = stencil.transform.transform_point2(local_mask_point.clamp(DVec2::ZERO, DVec2::ONE));
|
||||
let mask_point = (DAffine2::from_scale(stencil_size) * stencil.transform.inverse()).transform_point2(mask_point);
|
||||
let mask_point = stencil_transform.transform_point2(local_mask_point.clamp(DVec2::ZERO, DVec2::ONE));
|
||||
let mask_point = (DAffine2::from_scale(stencil_size) * stencil_transform.inverse()).transform_point2(mask_point);
|
||||
|
||||
let image_pixel = row.element.data_mut().get_pixel_mut(x, y).unwrap();
|
||||
let mask_pixel = stencil.element.sample(mask_point);
|
||||
let image_pixel = row.element_mut().data_mut().get_pixel_mut(x, y).unwrap();
|
||||
let mask_pixel = stencil.element().sample(mask_point);
|
||||
*image_pixel = image_pixel.multiplied_alpha(mask_pixel.l().cast_linear_channel());
|
||||
}
|
||||
}
|
||||
|
|
@ -237,20 +230,21 @@ pub fn extend_image_to_bounds(_: impl Ctx, image: Table<Raster<CPU>>, bounds: DA
|
|||
image
|
||||
.into_iter()
|
||||
.map(|mut row| {
|
||||
let image_aabb = Bbox::unit().affine_transform(row.transform).to_axis_aligned_bbox();
|
||||
let row_transform: DAffine2 = row.attribute_cloned_or_default("transform");
|
||||
let image_aabb = Bbox::unit().affine_transform(row_transform).to_axis_aligned_bbox();
|
||||
let bounds_aabb = Bbox::unit().affine_transform(bounds.transform()).to_axis_aligned_bbox();
|
||||
if image_aabb.contains(bounds_aabb.start) && image_aabb.contains(bounds_aabb.end) {
|
||||
return row;
|
||||
}
|
||||
|
||||
let image_data = &row.element.data;
|
||||
let (image_width, image_height) = (row.element.width, row.element.height);
|
||||
let image_data = &row.element().data;
|
||||
let (image_width, image_height) = (row.element().width, row.element().height);
|
||||
if image_width == 0 || image_height == 0 {
|
||||
return empty_image((), bounds, Table::new_from_element(Color::TRANSPARENT)).into_iter().next().unwrap();
|
||||
}
|
||||
|
||||
let orig_image_scale = DVec2::new(image_width as f64, image_height as f64);
|
||||
let layer_to_image_space = DAffine2::from_scale(orig_image_scale) * row.transform.inverse();
|
||||
let layer_to_image_space = DAffine2::from_scale(orig_image_scale) * row_transform.inverse();
|
||||
let bounds_in_image_space = Bbox::unit().affine_transform(layer_to_image_space * bounds).to_axis_aligned_bbox();
|
||||
|
||||
let new_start = bounds_in_image_space.start.floor().min(DVec2::ZERO);
|
||||
|
|
@ -270,10 +264,10 @@ pub fn extend_image_to_bounds(_: impl Ctx, image: Table<Raster<CPU>>, bounds: DA
|
|||
|
||||
// Compute new transform.
|
||||
// let layer_to_new_texture_space = (DAffine2::from_scale(1. / new_scale) * DAffine2::from_translation(new_start) * layer_to_image_space).inverse();
|
||||
let new_texture_to_layer_space = row.transform * DAffine2::from_scale(1. / orig_image_scale) * DAffine2::from_translation(new_start) * DAffine2::from_scale(new_scale);
|
||||
let new_texture_to_layer_space = row_transform * DAffine2::from_scale(1. / orig_image_scale) * DAffine2::from_translation(new_start) * DAffine2::from_scale(new_scale);
|
||||
|
||||
row.element = Raster::new_cpu(new_image);
|
||||
row.transform = new_texture_to_layer_space;
|
||||
*row.element_mut() = Raster::new_cpu(new_image);
|
||||
row.set_attribute("transform", new_texture_to_layer_space);
|
||||
row
|
||||
})
|
||||
.collect()
|
||||
|
|
@ -284,13 +278,12 @@ pub fn empty_image(_: impl Ctx, transform: DAffine2, color: Table<Color>) -> Tab
|
|||
let width = transform.transform_vector2(DVec2::new(1., 0.)).length() as u32;
|
||||
let height = transform.transform_vector2(DVec2::new(0., 1.)).length() as u32;
|
||||
|
||||
let color: Option<Color> = color.into();
|
||||
let image = Image::new(width, height, color.unwrap_or(Color::WHITE));
|
||||
let color = color.element(0).copied().unwrap_or(Color::WHITE);
|
||||
let image = Image::new(width, height, color);
|
||||
|
||||
let mut result_table = Table::new_from_element(Raster::new_cpu(image));
|
||||
let row = result_table.get_mut(0).unwrap();
|
||||
*row.transform = transform;
|
||||
*row.alpha_blending = AlphaBlending::default();
|
||||
result_table.set_attribute("transform", 0, transform);
|
||||
result_table.set_attribute("alpha_blending", 0, AlphaBlending::default());
|
||||
|
||||
// Callers of empty_image can safely unwrap on returned table
|
||||
result_table
|
||||
|
|
@ -383,11 +376,12 @@ pub fn noise_pattern(
|
|||
}
|
||||
}
|
||||
|
||||
return Table::new_from_row(TableRow {
|
||||
element: Raster::new_cpu(image),
|
||||
transform: DAffine2::from_translation(offset) * DAffine2::from_scale(size),
|
||||
..Default::default()
|
||||
});
|
||||
return Table::new_from_row(
|
||||
TableRow::new_from_element(Raster::new_cpu(image))
|
||||
.with_attribute("transform", DAffine2::from_translation(offset) * DAffine2::from_scale(size))
|
||||
.with_attribute("alpha_blending", AlphaBlending::default())
|
||||
.with_attribute("source_node_id", None::<core_types::uuid::NodeId>),
|
||||
);
|
||||
}
|
||||
};
|
||||
noise.set_noise_type(Some(noise_type));
|
||||
|
|
@ -445,11 +439,12 @@ pub fn noise_pattern(
|
|||
}
|
||||
}
|
||||
|
||||
Table::new_from_row(TableRow {
|
||||
element: Raster::new_cpu(image),
|
||||
transform: DAffine2::from_translation(offset) * DAffine2::from_scale(size),
|
||||
..Default::default()
|
||||
})
|
||||
Table::new_from_row(
|
||||
TableRow::new_from_element(Raster::new_cpu(image))
|
||||
.with_attribute("transform", DAffine2::from_translation(offset) * DAffine2::from_scale(size))
|
||||
.with_attribute("alpha_blending", AlphaBlending::default())
|
||||
.with_attribute("source_node_id", None::<core_types::uuid::NodeId>),
|
||||
)
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Raster: Pattern"))]
|
||||
|
|
@ -487,16 +482,17 @@ pub fn mandelbrot(ctx: impl ExtractFootprint + Send) -> Table<Raster<CPU>> {
|
|||
}
|
||||
}
|
||||
|
||||
Table::new_from_row(TableRow {
|
||||
element: Raster::new_cpu(Image {
|
||||
Table::new_from_row(
|
||||
TableRow::new_from_element(Raster::new_cpu(Image {
|
||||
width,
|
||||
height,
|
||||
data,
|
||||
..Default::default()
|
||||
}),
|
||||
transform: DAffine2::from_translation(offset) * DAffine2::from_scale(size),
|
||||
..Default::default()
|
||||
})
|
||||
}))
|
||||
.with_attribute("transform", DAffine2::from_translation(offset) * DAffine2::from_scale(size))
|
||||
.with_attribute("alpha_blending", AlphaBlending::default())
|
||||
.with_attribute("source_node_id", None::<core_types::uuid::NodeId>),
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use crate::gcore::Context;
|
||||
use core::f64::consts::TAU;
|
||||
use core_types::registry::types::{Angle, PixelSize};
|
||||
use core_types::table::{Table, TableRowRef};
|
||||
use core_types::table::Table;
|
||||
use core_types::{CloneVarArgs, Color, Ctx, ExtractAll, InjectVarArgs, OwnedContextImpl};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphic_types::{Graphic, Vector};
|
||||
|
|
@ -77,12 +77,13 @@ pub async fn repeat_array<T: Into<Graphic> + Default + Send + Clone + 'static>(
|
|||
let new_ctx = OwnedContextImpl::from(ctx.clone()).with_index(index as usize);
|
||||
let generated_instance = instance.eval(new_ctx.into_context()).await;
|
||||
|
||||
for row in generated_instance.iter() {
|
||||
let mut row = row.into_cloned();
|
||||
for row_index in 0..generated_instance.len() {
|
||||
let Some(mut row) = generated_instance.clone_row(row_index) else { continue };
|
||||
|
||||
let local_translation = DAffine2::from_translation(row.transform.translation);
|
||||
let local_matrix = DAffine2::from_mat2(row.transform.matrix2);
|
||||
row.transform = local_translation * transform * local_matrix;
|
||||
let local_transform: DAffine2 = row.attribute_cloned_or_default("transform");
|
||||
let local_translation = DAffine2::from_translation(local_transform.translation);
|
||||
let local_matrix = DAffine2::from_mat2(local_transform.matrix2);
|
||||
*row.attribute_mut_or_insert_default("transform") = local_translation * transform * local_matrix;
|
||||
|
||||
result_table.push(row);
|
||||
}
|
||||
|
|
@ -122,12 +123,13 @@ async fn repeat_radial<T: Into<Graphic> + Default + Send + Clone + 'static>(
|
|||
let new_ctx = OwnedContextImpl::from(ctx.clone()).with_index(index as usize);
|
||||
let generated_instance = instance.eval(new_ctx.into_context()).await;
|
||||
|
||||
for row in generated_instance.iter() {
|
||||
let mut row = row.into_cloned();
|
||||
for row_index in 0..generated_instance.len() {
|
||||
let Some(mut row) = generated_instance.clone_row(row_index) else { continue };
|
||||
|
||||
let local_translation = DAffine2::from_translation(row.transform.translation);
|
||||
let local_matrix = DAffine2::from_mat2(row.transform.matrix2);
|
||||
row.transform = local_translation * transform * local_matrix;
|
||||
let local_transform: DAffine2 = row.attribute_cloned_or_default("transform");
|
||||
let local_translation = DAffine2::from_translation(local_transform.translation);
|
||||
let local_matrix = DAffine2::from_mat2(local_transform.matrix2);
|
||||
*row.attribute_mut_or_insert_default("transform") = local_translation * transform * local_matrix;
|
||||
|
||||
result_table.push(row);
|
||||
}
|
||||
|
|
@ -152,7 +154,10 @@ async fn repeat_on_points<T: Into<Graphic> + Default + Send + Clone + 'static>(
|
|||
) -> Table<T> {
|
||||
let mut result_table = Table::new();
|
||||
|
||||
for TableRowRef { element: points, transform, .. } in points.iter() {
|
||||
for points_index in 0..points.len() {
|
||||
let Some(points_element) = points.element(points_index) else { continue };
|
||||
let transform: DAffine2 = points.attribute_cloned_or_default("transform", points_index);
|
||||
|
||||
let mut iteration = async |index, point| {
|
||||
let transformed_point = transform.transform_point2(point);
|
||||
|
||||
|
|
@ -160,12 +165,12 @@ async fn repeat_on_points<T: Into<Graphic> + Default + Send + Clone + 'static>(
|
|||
let generated_instance = instance.eval(new_ctx.into_context()).await;
|
||||
|
||||
for mut generated_row in generated_instance.into_iter() {
|
||||
generated_row.transform.translation = transformed_point;
|
||||
generated_row.attribute_mut_or_insert_default::<DAffine2>("transform").translation = transformed_point;
|
||||
result_table.push(generated_row);
|
||||
}
|
||||
};
|
||||
|
||||
let range = points.point_domain.positions().iter().enumerate();
|
||||
let range = points_element.point_domain.positions().iter().enumerate();
|
||||
if reverse {
|
||||
for (index, &point) in range.rev() {
|
||||
iteration(index, point).await;
|
||||
|
|
@ -228,8 +233,12 @@ mod test {
|
|||
let points = Table::new_from_element(Vector::from_subpath(Subpath::from_anchors(positions, false)));
|
||||
let generated = super::repeat_on_points(context, points, &rect, false).await;
|
||||
assert_eq!(generated.len(), positions.len());
|
||||
for (position, generated_row) in positions.into_iter().zip(generated.iter()) {
|
||||
let bounds = generated_row.element.bounding_box_with_transform(*generated_row.transform).unwrap();
|
||||
for (position, index) in positions.into_iter().zip(0..generated.len()) {
|
||||
let bounds = generated
|
||||
.element(index)
|
||||
.unwrap()
|
||||
.bounding_box_with_transform(generated.attribute_cloned_or_default("transform", index))
|
||||
.unwrap();
|
||||
assert!(position.abs_diff_eq((bounds[0] + bounds[1]) / 2., 1e-10));
|
||||
assert_eq!((bounds[1] - bounds[0]).x, position.y);
|
||||
}
|
||||
|
|
@ -249,7 +258,7 @@ mod test {
|
|||
)
|
||||
.await;
|
||||
let vector_table = vector_nodes::flatten_path(Footprint::default(), repeated).await;
|
||||
let vector = vector_table.iter().next().unwrap().element;
|
||||
let vector = vector_table.element(0).unwrap();
|
||||
assert_eq!(vector.region_manipulator_groups().count(), 3);
|
||||
for (index, (_, manipulator_groups)) in vector.region_manipulator_groups().enumerate() {
|
||||
assert!((manipulator_groups[0].anchor - direction * index as f64 / (count - 1) as f64).length() < 1e-5);
|
||||
|
|
@ -270,7 +279,7 @@ mod test {
|
|||
)
|
||||
.await;
|
||||
let vector_table = vector_nodes::flatten_path(Footprint::default(), repeated).await;
|
||||
let vector = vector_table.iter().next().unwrap().element;
|
||||
let vector = vector_table.element(0).unwrap();
|
||||
assert_eq!(vector.region_manipulator_groups().count(), 8);
|
||||
for (index, (_, manipulator_groups)) in vector.region_manipulator_groups().enumerate() {
|
||||
assert!((manipulator_groups[0].anchor - direction * index as f64 / (count - 1) as f64).length() < 1e-5);
|
||||
|
|
@ -282,7 +291,7 @@ mod test {
|
|||
let context = OwnedContextImpl::default().into_context();
|
||||
let repeated = super::repeat_radial(context, &FutureWrapperNode(vector_node_from_bezpath(Rect::new(-1., -1., 1., 1.).to_path(DEFAULT_ACCURACY))), 45., 4., 8).await;
|
||||
let vector_table = vector_nodes::flatten_path(Footprint::default(), repeated).await;
|
||||
let vector = vector_table.iter().next().unwrap().element;
|
||||
let vector = vector_table.element(0).unwrap();
|
||||
assert_eq!(vector.region_manipulator_groups().count(), 8);
|
||||
|
||||
for (index, (_, manipulator_groups)) in vector.region_manipulator_groups().enumerate() {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ license = "MIT OR Apache-2.0"
|
|||
|
||||
[features]
|
||||
default = ["serde"]
|
||||
serde = ["dep:serde", "core-types/serde", "vector-types/serde"]
|
||||
wasm = ["core-types/wasm", "tsify", "wasm-bindgen"]
|
||||
|
||||
[dependencies]
|
||||
|
|
|
|||
|
|
@ -6,13 +6,14 @@ use std::sync::Arc;
|
|||
|
||||
/// A font type (storing font family and font style and an optional preview URL)
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Eq, DynAny)]
|
||||
#[derive(Debug, Clone, Eq, DynAny)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct Font {
|
||||
#[serde(rename = "fontFamily")]
|
||||
#[cfg_attr(feature = "serde", serde(rename = "fontFamily"))]
|
||||
pub font_family: String,
|
||||
#[serde(rename = "fontStyle", deserialize_with = "migrate_font_style")]
|
||||
#[cfg_attr(feature = "serde", serde(rename = "fontStyle", deserialize_with = "migrate_font_style"))]
|
||||
pub font_style: String,
|
||||
#[serde(skip)]
|
||||
#[cfg_attr(feature = "serde", serde(skip))]
|
||||
pub font_style_to_restore: Option<String>,
|
||||
}
|
||||
|
||||
|
|
@ -72,7 +73,8 @@ impl Default for Font {
|
|||
}
|
||||
|
||||
/// A cache of all loaded font data and preview urls along with the default font (send from `init_app` in `editor_api.rs`)
|
||||
#[derive(Clone, serde::Serialize, serde::Deserialize, Default, DynAny)]
|
||||
#[derive(Clone, Default, DynAny)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct FontCache {
|
||||
/// Actual font file data used for rendering a font
|
||||
font_file_data: HashMap<Font, Vec<u8>>,
|
||||
|
|
|
|||
|
|
@ -26,7 +26,8 @@ pub use vector_types;
|
|||
/// Alignment of lines of type within a text block.
|
||||
#[repr(C)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, CacheHash, DynAny, node_macro::ChoiceType)]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, CacheHash, DynAny, node_macro::ChoiceType)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[widget(Radio)]
|
||||
pub enum TextAlign {
|
||||
#[default]
|
||||
|
|
@ -49,7 +50,8 @@ impl From<TextAlign> for parley::Alignment {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(PartialEq, Clone, Copy, Debug)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct TypesettingConfig {
|
||||
pub font_size: f64,
|
||||
pub line_height_ratio: f64,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
use core_types::AlphaBlending;
|
||||
use core_types::table::{Table, TableRow};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use parley::GlyphRun;
|
||||
|
|
@ -51,15 +52,16 @@ impl<Upstream: Default + 'static> PathBuilder<Upstream> {
|
|||
}
|
||||
|
||||
if per_glyph_instances {
|
||||
self.vector_table.push(TableRow {
|
||||
element: Vector::from_subpaths(core::mem::take(&mut self.glyph_subpaths), false),
|
||||
transform: DAffine2::from_translation(glyph_offset),
|
||||
..Default::default()
|
||||
});
|
||||
self.vector_table.push(
|
||||
TableRow::new_from_element(Vector::from_subpaths(core::mem::take(&mut self.glyph_subpaths), false))
|
||||
.with_attribute("transform", DAffine2::from_translation(glyph_offset))
|
||||
.with_attribute("alpha_blending", AlphaBlending::default())
|
||||
.with_attribute("source_node_id", None::<core_types::uuid::NodeId>),
|
||||
);
|
||||
} else {
|
||||
for subpath in self.glyph_subpaths.drain(..) {
|
||||
// Unwrapping here is ok because `self.vector_table` is initialized with a single `Vector` table element
|
||||
self.vector_table.get_mut(0).unwrap().element.append_subpath(subpath, false);
|
||||
self.vector_table.element_mut(0).unwrap().append_subpath(subpath, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,24 +66,20 @@ fn reset_transform<T>(
|
|||
reset_rotation: bool,
|
||||
reset_scale: bool,
|
||||
) -> Table<T> {
|
||||
for row in content.iter_mut() {
|
||||
// Translation
|
||||
for row_transform in content.iter_attribute_values_mut_or_default::<DAffine2>("transform") {
|
||||
if reset_translation {
|
||||
row.transform.translation = DVec2::ZERO;
|
||||
row_transform.translation = DVec2::ZERO;
|
||||
}
|
||||
// (Rotation, Scale)
|
||||
|
||||
match (reset_rotation, reset_scale) {
|
||||
(true, true) => {
|
||||
row.transform.matrix2 = DMat2::IDENTITY;
|
||||
}
|
||||
(true, true) => row_transform.matrix2 = DMat2::IDENTITY,
|
||||
(true, false) => {
|
||||
let scale = row.transform.scale_magnitudes();
|
||||
row.transform.matrix2 = DMat2::from_diagonal(scale);
|
||||
let scale = row_transform.scale_magnitudes();
|
||||
row_transform.matrix2 = DMat2::from_diagonal(scale);
|
||||
}
|
||||
(false, true) => {
|
||||
let rotation = row.transform.decompose_rotation();
|
||||
let rotation_matrix = DMat2::from_angle(rotation);
|
||||
row.transform.matrix2 = rotation_matrix;
|
||||
let rotation = row_transform.decompose_rotation();
|
||||
row_transform.matrix2 = DMat2::from_angle(rotation);
|
||||
}
|
||||
(false, false) => {}
|
||||
}
|
||||
|
|
@ -106,8 +102,8 @@ fn replace_transform<T>(
|
|||
mut content: Table<T>,
|
||||
transform: DAffine2,
|
||||
) -> Table<T> {
|
||||
for row in content.iter_mut() {
|
||||
*row.transform = transform.transform();
|
||||
for row_transform in content.iter_attribute_values_mut_or_default::<DAffine2>("transform") {
|
||||
*row_transform = transform.transform();
|
||||
}
|
||||
content
|
||||
}
|
||||
|
|
@ -127,7 +123,7 @@ async fn extract_transform<T>(
|
|||
)]
|
||||
content: Table<T>,
|
||||
) -> DAffine2 {
|
||||
content.iter().next().map(|row| *row.transform).unwrap_or_default()
|
||||
content.attribute_cloned_or_default::<DAffine2>("transform", 0)
|
||||
}
|
||||
|
||||
/// Produces the inverse of the input transform, which is the transform that undoes the effect of the original transform.
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ license = "MIT OR Apache-2.0"
|
|||
|
||||
[features]
|
||||
default = ["serde"]
|
||||
serde = ["dep:serde", "core-types/serde", "vector-types/serde", "graphic-types/serde"]
|
||||
wasm = ["core-types/wasm", "tsify", "wasm-bindgen"]
|
||||
|
||||
[dependencies]
|
||||
|
|
|
|||
|
|
@ -188,7 +188,8 @@ fn star<T: AsU64>(
|
|||
}
|
||||
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, CacheHash, DynAny, node_macro::ChoiceType)]
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash, CacheHash, DynAny, node_macro::ChoiceType)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[widget(Radio)]
|
||||
pub enum QRCodeErrorCorrectionLevel {
|
||||
/// Allows recovery from up to 7% data loss.
|
||||
|
|
@ -399,9 +400,9 @@ mod tests {
|
|||
|
||||
// Works properly
|
||||
let grid = grid((), (), GridType::Isometric, 10., 5, 5, (30., 30.).into());
|
||||
assert_eq!(grid.iter().next().unwrap().element.point_domain.ids().len(), 5 * 5);
|
||||
assert_eq!(grid.iter().next().unwrap().element.segment_bezier_iter().count(), 4 * 5 + 4 * 9);
|
||||
for (_, bezier, _, _) in grid.iter().next().unwrap().element.segment_bezier_iter() {
|
||||
assert_eq!(grid.element(0).unwrap().point_domain.ids().len(), 5 * 5);
|
||||
assert_eq!(grid.element(0).unwrap().segment_bezier_iter().count(), 4 * 5 + 4 * 9);
|
||||
for (_, bezier, _, _) in grid.element(0).unwrap().segment_bezier_iter() {
|
||||
assert_eq!(bezier.handles, subpath::BezierHandles::Linear);
|
||||
assert!(
|
||||
((bezier.start - bezier.end).length() - 10.).abs() < 1e-5,
|
||||
|
|
@ -414,9 +415,9 @@ mod tests {
|
|||
#[test]
|
||||
fn skew_isometric_grid_test() {
|
||||
let grid = grid((), (), GridType::Isometric, 10., 5, 5, (40., 30.).into());
|
||||
assert_eq!(grid.iter().next().unwrap().element.point_domain.ids().len(), 5 * 5);
|
||||
assert_eq!(grid.iter().next().unwrap().element.segment_bezier_iter().count(), 4 * 5 + 4 * 9);
|
||||
for (_, bezier, _, _) in grid.iter().next().unwrap().element.segment_bezier_iter() {
|
||||
assert_eq!(grid.element(0).unwrap().point_domain.ids().len(), 5 * 5);
|
||||
assert_eq!(grid.element(0).unwrap().segment_bezier_iter().count(), 4 * 5 + 4 * 9);
|
||||
for (_, bezier, _, _) in grid.element(0).unwrap().segment_bezier_iter() {
|
||||
assert_eq!(bezier.handles, subpath::BezierHandles::Linear);
|
||||
let vector = bezier.start - bezier.end;
|
||||
let angle = (vector.angle_to(DVec2::X).to_degrees() + 180.) % 180.;
|
||||
|
|
@ -427,7 +428,7 @@ mod tests {
|
|||
#[test]
|
||||
fn qr_code_test() {
|
||||
let qr = qr_code((), (), "https://graphite.art".to_string(), false, 1., QRCodeErrorCorrectionLevel::Low, true);
|
||||
assert!(qr.iter().next().unwrap().element.point_domain.ids().len() > 0);
|
||||
assert!(qr.iter().next().unwrap().element.segment_domain.ids().len() > 0);
|
||||
assert!(qr.element(0).unwrap().point_domain.ids().len() > 0);
|
||||
assert!(qr.element(0).unwrap().segment_domain.ids().len() > 0);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,12 +13,12 @@ async fn path_modify(_ctx: impl Ctx, mut vector: Table<Vector>, modification: Bo
|
|||
if vector.is_empty() {
|
||||
vector.push(TableRow::default());
|
||||
}
|
||||
let row = vector.get_mut(0).expect("push should give one item");
|
||||
modification.apply(row.element);
|
||||
modification.apply(vector.element_mut(0).expect("push should give one item"));
|
||||
|
||||
// Update the source node id
|
||||
let this_node_path = node_path.iter().rev().nth(1).copied();
|
||||
*row.source_node_id = row.source_node_id.or(this_node_path);
|
||||
let existing: Option<NodeId> = vector.attribute_cloned_or_default("source_node_id", 0);
|
||||
vector.set_attribute("source_node_id", 0, existing.or(this_node_path));
|
||||
|
||||
if vector.len() > 1 {
|
||||
warn!("The path modify ran on {} vector rows. Only the first can be modified.", vector.len());
|
||||
|
|
@ -29,16 +29,14 @@ async fn path_modify(_ctx: impl Ctx, mut vector: Table<Vector>, modification: Bo
|
|||
/// Applies the vector path's local transformation to its geometry and resets the transform to the identity.
|
||||
#[node_macro::node(category("Vector"))]
|
||||
async fn apply_transform(_ctx: impl Ctx, mut vector: Table<Vector>) -> Table<Vector> {
|
||||
for row in vector.iter_mut() {
|
||||
let vector = row.element;
|
||||
let transform = *row.transform;
|
||||
|
||||
for (_, point) in vector.point_domain.positions_mut() {
|
||||
let (elements, transforms) = vector.element_and_attribute_slices_mut::<DAffine2>("transform");
|
||||
for (element, transform) in elements.iter_mut().zip(transforms.iter_mut()) {
|
||||
for (_, point) in element.point_domain.positions_mut() {
|
||||
*point = transform.transform_point2(*point);
|
||||
}
|
||||
vector.segment_domain.transform(transform);
|
||||
element.segment_domain.transform(*transform);
|
||||
|
||||
*row.transform = DAffine2::IDENTITY;
|
||||
*transform = DAffine2::IDENTITY;
|
||||
}
|
||||
|
||||
vector
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue