Replace the AlphaBlending struct with separate attributes (#4086)
* Replace the AlphaBlending struct with separate attributes * Fix bug * Fix bug
This commit is contained in:
parent
86134c26b4
commit
4474de4662
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -904,9 +904,7 @@ macro_rules! known_table_row_types {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Override hook for [`Table::attribute_display_value`] that prefers `Display` over `Debug` for select
|
/// Uses `Display` instead of `Debug` for attribute types that have a nicer human-readable format.
|
||||||
/// attribute types. The underlying storage is generic and can only see a `Debug` bound, so types whose
|
|
||||||
/// nicer `Display` rendering matters in the data panel are listed here explicitly.
|
|
||||||
fn display_value_override(any: &dyn Any) -> Option<String> {
|
fn display_value_override(any: &dyn Any) -> Option<String> {
|
||||||
if let Some(value) = any.downcast_ref::<BlendMode>() {
|
if let Some(value) = any.downcast_ref::<BlendMode>() {
|
||||||
return Some(value.to_string());
|
return Some(value.to_string());
|
||||||
|
|
|
||||||
|
|
@ -440,7 +440,7 @@ impl<'a> ModifyInputsContext<'a> {
|
||||||
let Some(blend_node_id) = self.existing_proto_node_id(graphene_std::blending_nodes::blend_mode::IDENTIFIER, true) else {
|
let Some(blend_node_id) = self.existing_proto_node_id(graphene_std::blending_nodes::blend_mode::IDENTIFIER, true) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let input_connector = InputConnector::node(blend_node_id, 1);
|
let input_connector = InputConnector::node(blend_node_id, graphene_std::blending_nodes::blend_mode::BlendModeInput::INDEX);
|
||||||
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::BlendMode(blend_mode), false), false);
|
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::BlendMode(blend_mode), false), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -449,26 +449,45 @@ impl<'a> ModifyInputsContext<'a> {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
// Enable the `has_opacity` checkbox so the value is applied
|
// Enable the `has_opacity` checkbox so the value is applied
|
||||||
self.set_input_with_refresh(InputConnector::node(opacity_node_id, 1), NodeInput::value(TaggedValue::Bool(true), false), false);
|
self.set_input_with_refresh(
|
||||||
self.set_input_with_refresh(InputConnector::node(opacity_node_id, 2), NodeInput::value(TaggedValue::F64(opacity * 100.), false), false);
|
InputConnector::node(opacity_node_id, graphene_std::blending_nodes::opacity::HasOpacityInput::INDEX),
|
||||||
|
NodeInput::value(TaggedValue::Bool(true), false),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
self.set_input_with_refresh(
|
||||||
|
InputConnector::node(opacity_node_id, graphene_std::blending_nodes::opacity::OpacityInput::INDEX),
|
||||||
|
NodeInput::value(TaggedValue::F64(opacity * 100.), false),
|
||||||
|
false,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn opacity_fill_set(&mut self, fill: f64) {
|
pub fn opacity_fill_set(&mut self, fill: f64) {
|
||||||
// Reuse the Opacity node if already present (saving a chain walk on slider drags), otherwise let the next call create it
|
// Reuse an existing Opacity node to avoid a redundant chain walk on slider drags
|
||||||
let identifier = graphene_std::blending_nodes::opacity::IDENTIFIER;
|
let identifier = graphene_std::blending_nodes::opacity::IDENTIFIER;
|
||||||
let existing = self.existing_proto_node_id(identifier.clone(), false);
|
let existing = self.existing_proto_node_id(identifier.clone(), false);
|
||||||
let existed = existing.is_some();
|
let existed = existing.is_some();
|
||||||
let Some(opacity_node_id) = existing.or_else(|| self.existing_proto_node_id(identifier, true)) else {
|
let Some(opacity_node_id) = existing.or_else(|| self.existing_proto_node_id(identifier, true)) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
// Disable the opacity component on a freshly-created node so the slider only affects fill, mirroring the opacity-slider case
|
// Freshly-created node defaults to opacity enabled; disable it so the fill slider works independently
|
||||||
// (where the node's default `has_fill = false` already keeps fill out of the picture)
|
|
||||||
if !existed {
|
if !existed {
|
||||||
self.set_input_with_refresh(InputConnector::node(opacity_node_id, 1), NodeInput::value(TaggedValue::Bool(false), false), false);
|
self.set_input_with_refresh(
|
||||||
|
InputConnector::node(opacity_node_id, graphene_std::blending_nodes::opacity::HasOpacityInput::INDEX),
|
||||||
|
NodeInput::value(TaggedValue::Bool(false), false),
|
||||||
|
false,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
// Enable the `has_fill` checkbox so the value is applied
|
// Enable the `has_fill` checkbox so the value is applied
|
||||||
self.set_input_with_refresh(InputConnector::node(opacity_node_id, 3), NodeInput::value(TaggedValue::Bool(true), false), false);
|
self.set_input_with_refresh(
|
||||||
self.set_input_with_refresh(InputConnector::node(opacity_node_id, 4), NodeInput::value(TaggedValue::F64(fill * 100.), false), false);
|
InputConnector::node(opacity_node_id, graphene_std::blending_nodes::opacity::HasFillInput::INDEX),
|
||||||
|
NodeInput::value(TaggedValue::Bool(true), false),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
self.set_input_with_refresh(
|
||||||
|
InputConnector::node(opacity_node_id, graphene_std::blending_nodes::opacity::FillInput::INDEX),
|
||||||
|
NodeInput::value(TaggedValue::F64(fill * 100.), false),
|
||||||
|
false,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the stops table on the 'Gradient Value' node, creating it if necessary.
|
/// Set the stops table on the 'Gradient Value' node, creating it if necessary.
|
||||||
|
|
@ -570,7 +589,7 @@ impl<'a> ModifyInputsContext<'a> {
|
||||||
let Some(clip_node_id) = self.existing_proto_node_id(graphene_std::blending_nodes::clipping_mask::IDENTIFIER, true) else {
|
let Some(clip_node_id) = self.existing_proto_node_id(graphene_std::blending_nodes::clipping_mask::IDENTIFIER, true) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let input_connector = InputConnector::node(clip_node_id, 1);
|
let input_connector = InputConnector::node(clip_node_id, graphene_std::blending_nodes::clipping_mask::ClipInput::INDEX);
|
||||||
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::Bool(clip), false), false);
|
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::Bool(clip), false), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,8 +34,8 @@ use std::any::TypeId;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
pub use table::{
|
pub use table::{
|
||||||
ATTR_ALPHA_BLENDING, ATTR_BACKGROUND, ATTR_CLIP, ATTR_DIMENSIONS, ATTR_EDITOR_LAYER_PATH, ATTR_EDITOR_MERGED_LAYERS, ATTR_END, ATTR_GRADIENT_TYPE, ATTR_LOCATION, ATTR_NAME, ATTR_SPREAD_METHOD,
|
ATTR_BACKGROUND, ATTR_BLEND_MODE, ATTR_CLIP, ATTR_CLIPPING_MASK, ATTR_DIMENSIONS, ATTR_EDITOR_LAYER_PATH, ATTR_EDITOR_MERGED_LAYERS, ATTR_END, ATTR_GRADIENT_TYPE, ATTR_LOCATION, ATTR_NAME,
|
||||||
ATTR_START, ATTR_TRANSFORM, ATTR_TYPE,
|
ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_SPREAD_METHOD, ATTR_START, ATTR_TRANSFORM, ATTR_TYPE,
|
||||||
};
|
};
|
||||||
#[cfg(feature = "wasm")]
|
#[cfg(feature = "wasm")]
|
||||||
pub use tsify;
|
pub use tsify;
|
||||||
|
|
|
||||||
|
|
@ -10,59 +10,60 @@ use std::fmt::Debug;
|
||||||
// Standard attribute keys used across the data flow
|
// Standard attribute keys used across the data flow
|
||||||
// =====================================================================
|
// =====================================================================
|
||||||
|
|
||||||
/// Attribute key for a row's `DAffine2` transformation, applied when rendering and when accumulating
|
/// Row's `DAffine2` transformation, composed multiplicatively through nested groups.
|
||||||
/// transforms through nested compositions.
|
|
||||||
pub const ATTR_TRANSFORM: &str = "transform";
|
pub const ATTR_TRANSFORM: &str = "transform";
|
||||||
|
|
||||||
/// Attribute key for a row's `AlphaBlending` (blend mode + opacity + fill + clip), composed
|
/// Row's `BlendMode`, controlling how it composites with content beneath it.
|
||||||
/// multiplicatively through nested compositions.
|
pub const ATTR_BLEND_MODE: &str = "blend_mode";
|
||||||
pub const ATTR_ALPHA_BLENDING: &str = "alpha_blending";
|
|
||||||
|
|
||||||
/// Attribute key under which each row of an editor-aware layer stores a `Table<NodeId>` describing the
|
/// Row's opacity multiplier (`f64`, implicit default `1.`).
|
||||||
/// path (from the root document network) to the layer node that owns the row. Editor tools use this to
|
/// Composed multiplicatively through nested groups. Affects content clipped to the row.
|
||||||
/// route clicks/selection back to the originating layer at any nesting depth.
|
pub const ATTR_OPACITY: &str = "opacity";
|
||||||
|
|
||||||
|
/// Row's fill opacity multiplier (`f64`, implicit default `1.`).
|
||||||
|
/// Like opacity but does not affect content clipped to the row.
|
||||||
|
pub const ATTR_OPACITY_FILL: &str = "opacity_fill";
|
||||||
|
|
||||||
|
/// Whether a row inherits the alpha of the content beneath it (clipping mask).
|
||||||
|
pub const ATTR_CLIPPING_MASK: &str = "clipping_mask";
|
||||||
|
|
||||||
|
/// `Table<NodeId>` path from the root network to the layer node owning this row.
|
||||||
|
/// Used by editor tools to route clicks/selection back to the originating layer.
|
||||||
pub const ATTR_EDITOR_LAYER_PATH: &str = "editor:layer_path";
|
pub const ATTR_EDITOR_LAYER_PATH: &str = "editor:layer_path";
|
||||||
|
|
||||||
/// Attribute key under which a row stores a `Table<Graphic>` snapshot of the upstream content that fed
|
/// `Table<Graphic>` snapshot of the upstream content that fed into a destructive merge
|
||||||
/// into a destructive merge (Boolean Operation, Flatten Path, Morph, Rasterize, etc.). The renderer
|
/// (Boolean Operation, Rasterize, etc.), so the editor can still surface click targets for
|
||||||
/// recurses into this snapshot during metadata collection so the editor can still surface click targets
|
/// the original child layers after their content has been collapsed.
|
||||||
/// for the original child layers after their content has been collapsed into a single output.
|
|
||||||
pub const ATTR_EDITOR_MERGED_LAYERS: &str = "editor:merged_layers";
|
pub const ATTR_EDITOR_MERGED_LAYERS: &str = "editor:merged_layers";
|
||||||
|
|
||||||
/// Attribute key for the byte offset where a regex match begins in the input string, set by the
|
/// Byte offset where a regex match begins ('Regex Find All', 'Regex Capture' text nodes).
|
||||||
/// `regex_find_all` and `regex_capture` text nodes.
|
|
||||||
pub const ATTR_START: &str = "start";
|
pub const ATTR_START: &str = "start";
|
||||||
|
|
||||||
/// Attribute key for the byte offset where a regex match ends in the input string, set by the
|
/// Byte offset where a regex match ends ('Regex Find All', 'Regex Capture' text nodes).
|
||||||
/// `regex_find_all` and `regex_capture` text nodes.
|
|
||||||
pub const ATTR_END: &str = "end";
|
pub const ATTR_END: &str = "end";
|
||||||
|
|
||||||
/// Attribute key for a regex named-capture-group's name (empty for unnamed groups), set by the
|
/// Regex named-capture-group's name, or empty for unnamed groups ('Regex Capture' text node).
|
||||||
/// `regex_capture` text node.
|
|
||||||
pub const ATTR_NAME: &str = "name";
|
pub const ATTR_NAME: &str = "name";
|
||||||
|
|
||||||
/// Attribute key for a JSON value's type (`"string"`, `"number"`, `"object"`, etc.), set by the
|
/// JSON value's type string (`"string"`, `"number"`, `"object"`, etc.) from 'JSON Query All'.
|
||||||
/// `json_query_all` text node alongside each extracted value.
|
|
||||||
pub const ATTR_TYPE: &str = "type";
|
pub const ATTR_TYPE: &str = "type";
|
||||||
|
|
||||||
/// Attribute key for an artboard row's `DVec2` top-left corner location in document coordinates.
|
/// Artboard's `DVec2` top-left corner in document coordinates.
|
||||||
pub const ATTR_LOCATION: &str = "location";
|
pub const ATTR_LOCATION: &str = "location";
|
||||||
|
|
||||||
/// Attribute key for an artboard row's `DVec2` width and height.
|
/// Artboard's `DVec2` width and height.
|
||||||
pub const ATTR_DIMENSIONS: &str = "dimensions";
|
pub const ATTR_DIMENSIONS: &str = "dimensions";
|
||||||
|
|
||||||
/// Attribute key for an artboard row's `Color` background fill.
|
/// Artboard's `Color` background fill.
|
||||||
pub const ATTR_BACKGROUND: &str = "background";
|
pub const ATTR_BACKGROUND: &str = "background";
|
||||||
|
|
||||||
/// Attribute key for an artboard row's `bool` flag indicating whether content is clipped to the artboard bounds.
|
/// Whether an artboard clips content to its bounds.
|
||||||
pub const ATTR_CLIP: &str = "clip";
|
pub const ATTR_CLIP: &str = "clip";
|
||||||
|
|
||||||
/// Attribute key for a `Table<GradientStops>` row's `GradientSpreadMethod`, controlling the gradient's behavior
|
/// Gradient's `GradientSpreadMethod` (`Pad`, `Reflect`, or `Repeat`).
|
||||||
/// outside the start/end stops (`Pad` clamps to the boundary colors, `Reflect` mirrors, `Repeat` tiles).
|
|
||||||
pub const ATTR_SPREAD_METHOD: &str = "spread_method";
|
pub const ATTR_SPREAD_METHOD: &str = "spread_method";
|
||||||
|
|
||||||
/// Attribute key for a `Table<GradientStops>` row's `GradientType`, choosing between a linear gradient (color
|
/// Gradient's `GradientType` (`Linear` or `Radial`).
|
||||||
/// transitions along the gradient line) or a radial gradient (color transitions outward from the line's start).
|
|
||||||
pub const ATTR_GRADIENT_TYPE: &str = "gradient_type";
|
pub const ATTR_GRADIENT_TYPE: &str = "gradient_type";
|
||||||
|
|
||||||
// =====================
|
// =====================
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::graphic::Graphic;
|
use crate::graphic::Graphic;
|
||||||
use core_types::blending::AlphaBlending;
|
use core_types::blending::BlendMode;
|
||||||
use core_types::table::{Table, TableRow};
|
use core_types::table::{Table, TableRow};
|
||||||
use core_types::uuid::NodeId;
|
use core_types::uuid::NodeId;
|
||||||
use core_types::{ATTR_BACKGROUND, ATTR_CLIP, ATTR_DIMENSIONS, ATTR_LOCATION, Color};
|
use core_types::{ATTR_BACKGROUND, ATTR_CLIP, ATTR_DIMENSIONS, ATTR_LOCATION, Color};
|
||||||
|
|
@ -22,6 +22,17 @@ use glam::{DAffine2, IVec2};
|
||||||
pub fn migrate_artboard<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Table<Table<Graphic>>, D::Error> {
|
pub fn migrate_artboard<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Table<Table<Graphic>>, D::Error> {
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
/// Mirrors the removed `AlphaBlending` struct for legacy document deserialization.
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
#[cfg_attr(feature = "serde", serde(default))]
|
||||||
|
pub struct LegacyAlphaBlending {
|
||||||
|
pub blend_mode: BlendMode,
|
||||||
|
pub opacity: f32,
|
||||||
|
pub fill: f32,
|
||||||
|
pub clip: bool,
|
||||||
|
}
|
||||||
|
|
||||||
/// Pre-migration shape of the artboard's stored data: the struct that used to live as the element
|
/// Pre-migration shape of the artboard's stored data: the struct that used to live as the element
|
||||||
/// of `Table<Artboard>`. Kept as a private type so we can deserialize legacy documents into the new
|
/// of `Table<Artboard>`. Kept as a private type so we can deserialize legacy documents into the new
|
||||||
/// `Table<Table<Graphic>>` (element = `content`, other fields → row attributes).
|
/// `Table<Table<Graphic>>` (element = `content`, other fields → row attributes).
|
||||||
|
|
@ -48,7 +59,7 @@ pub fn migrate_artboard<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Re
|
||||||
#[cfg_attr(feature = "serde", serde(alias = "instances", alias = "instance"))]
|
#[cfg_attr(feature = "serde", serde(alias = "instances", alias = "instance"))]
|
||||||
element: Vec<T>,
|
element: Vec<T>,
|
||||||
transform: Vec<DAffine2>,
|
transform: Vec<DAffine2>,
|
||||||
alpha_blending: Vec<AlphaBlending>,
|
alpha_blending: Vec<LegacyAlphaBlending>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
use core_types::blending::AlphaBlending;
|
use core_types::blending::BlendMode;
|
||||||
use core_types::bounds::{BoundingBox, RenderBoundingBox};
|
use core_types::bounds::{BoundingBox, RenderBoundingBox};
|
||||||
use core_types::graphene_hash::CacheHash;
|
use core_types::graphene_hash::CacheHash;
|
||||||
use core_types::ops::TableConvert;
|
use core_types::ops::TableConvert;
|
||||||
use core_types::render_complexity::RenderComplexity;
|
use core_types::render_complexity::RenderComplexity;
|
||||||
use core_types::table::{Table, TableRow};
|
use core_types::table::{Table, TableRow};
|
||||||
use core_types::uuid::NodeId;
|
use core_types::uuid::NodeId;
|
||||||
use core_types::{ATTR_ALPHA_BLENDING, ATTR_EDITOR_LAYER_PATH, ATTR_TRANSFORM, Color};
|
use core_types::{ATTR_CLIPPING_MASK, ATTR_EDITOR_LAYER_PATH, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_TRANSFORM, Color};
|
||||||
use dyn_any::DynAny;
|
use dyn_any::DynAny;
|
||||||
use glam::DAffine2;
|
use glam::DAffine2;
|
||||||
use raster_types::{CPU, GPU, Raster};
|
use raster_types::{CPU, GPU, Raster};
|
||||||
|
|
@ -130,47 +130,57 @@ impl From<Table<GradientStops>> for Graphic {
|
||||||
/// Deeply flattens a `Table<Graphic>`, collecting only elements matching a specific variant (extracted by `extract_variant`)
|
/// Deeply flattens a `Table<Graphic>`, collecting only elements matching a specific variant (extracted by `extract_variant`)
|
||||||
/// and discarding all other non-matching content. Recursion through `Graphic::Graphic` sub-`Table`s composes transforms and opacity.
|
/// and discarding all other non-matching content. Recursion through `Graphic::Graphic` sub-`Table`s composes transforms and opacity.
|
||||||
fn flatten_graphic_table<T>(content: Table<Graphic>, extract_variant: fn(Graphic) -> Option<Table<T>>) -> Table<T> {
|
fn flatten_graphic_table<T>(content: Table<Graphic>, extract_variant: fn(Graphic) -> Option<Table<T>>) -> Table<T> {
|
||||||
fn compose_alpha_blending(parent: AlphaBlending, child: AlphaBlending) -> AlphaBlending {
|
|
||||||
AlphaBlending {
|
|
||||||
blend_mode: child.blend_mode,
|
|
||||||
opacity: parent.opacity * child.opacity,
|
|
||||||
fill: child.fill,
|
|
||||||
clip: child.clip,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn flatten_recursive<T>(output: &mut Table<T>, current_graphic_table: Table<Graphic>, extract_variant: fn(Graphic) -> Option<Table<T>>) {
|
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() {
|
for current_graphic_row in current_graphic_table.into_iter() {
|
||||||
let layer_path: Table<NodeId> = current_graphic_row.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH);
|
let layer_path: Table<NodeId> = current_graphic_row.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH);
|
||||||
let current_transform: DAffine2 = current_graphic_row.attribute_cloned_or_default(ATTR_TRANSFORM);
|
let current_transform: DAffine2 = current_graphic_row.attribute_cloned_or_default(ATTR_TRANSFORM);
|
||||||
let current_alpha_blending: AlphaBlending = current_graphic_row.attribute_cloned_or_default(ATTR_ALPHA_BLENDING);
|
let current_opacity: f64 = current_graphic_row.attribute_cloned_or(ATTR_OPACITY, 1.);
|
||||||
|
let current_fill: f64 = current_graphic_row.attribute_cloned_or(ATTR_OPACITY_FILL, 1.);
|
||||||
|
|
||||||
match current_graphic_row.into_element() {
|
match current_graphic_row.into_element() {
|
||||||
// Recurse into nested `Table<Graphic>` items, composing the parent's transform onto each child
|
// Compose the parent's transform, opacity, and fill onto each child row
|
||||||
Graphic::Graphic(mut sub_table) => {
|
Graphic::Graphic(mut sub_table) => {
|
||||||
for index in 0..sub_table.len() {
|
// Identity default means a missing column still composes correctly
|
||||||
let child_transform: DAffine2 = sub_table.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
for v in sub_table.iter_attribute_values_mut_or_default::<DAffine2>(ATTR_TRANSFORM) {
|
||||||
let child_alpha_blending: AlphaBlending = sub_table.attribute_cloned_or_default(ATTR_ALPHA_BLENDING, index);
|
*v = current_transform * *v;
|
||||||
|
}
|
||||||
|
|
||||||
sub_table.set_attribute(ATTR_TRANSFORM, index, current_transform * child_transform);
|
// f64 defaults to 0, but opacity/fill default to 1, so missing columns must be set rather than multiplied
|
||||||
sub_table.set_attribute(ATTR_ALPHA_BLENDING, index, compose_alpha_blending(current_alpha_blending, child_alpha_blending));
|
if let Some(values) = sub_table.iter_attribute_values_mut::<f64>(ATTR_OPACITY) {
|
||||||
|
for v in values {
|
||||||
|
*v *= current_opacity;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for v in sub_table.iter_attribute_values_mut_or_default::<f64>(ATTR_OPACITY) {
|
||||||
|
*v = current_opacity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(values) = sub_table.iter_attribute_values_mut::<f64>(ATTR_OPACITY_FILL) {
|
||||||
|
for v in values {
|
||||||
|
*v *= current_fill;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for v in sub_table.iter_attribute_values_mut_or_default::<f64>(ATTR_OPACITY_FILL) {
|
||||||
|
*v = current_fill;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
flatten_recursive(output, sub_table, extract_variant);
|
flatten_recursive(output, sub_table, extract_variant);
|
||||||
}
|
}
|
||||||
// Try to extract the target variant; if it matches, push its items with composed transform and opacity
|
// Extract the target variant and push its items with composed transform, opacity, and fill
|
||||||
other => {
|
other => {
|
||||||
if let Some(typed_table) = extract_variant(other) {
|
if let Some(typed_table) = extract_variant(other) {
|
||||||
for row in typed_table.into_iter() {
|
for mut item in typed_table.into_iter() {
|
||||||
let row_transform: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM);
|
let row_transform: DAffine2 = item.attribute_cloned_or_default(ATTR_TRANSFORM);
|
||||||
let row_alpha_blending: AlphaBlending = row.attribute_cloned_or_default(ATTR_ALPHA_BLENDING);
|
let row_opacity: f64 = item.attribute_cloned_or(ATTR_OPACITY, 1.);
|
||||||
let (element, mut attributes) = row.into_parts();
|
let row_fill: f64 = item.attribute_cloned_or(ATTR_OPACITY_FILL, 1.);
|
||||||
|
|
||||||
attributes.insert(ATTR_TRANSFORM, current_transform * row_transform);
|
item.set_attribute(ATTR_TRANSFORM, current_transform * row_transform);
|
||||||
attributes.insert(ATTR_ALPHA_BLENDING, compose_alpha_blending(current_alpha_blending, row_alpha_blending));
|
item.set_attribute(ATTR_OPACITY, current_opacity * row_opacity);
|
||||||
attributes.insert(ATTR_EDITOR_LAYER_PATH, layer_path.clone());
|
item.set_attribute(ATTR_OPACITY_FILL, current_fill * row_fill);
|
||||||
|
item.set_attribute(ATTR_EDITOR_LAYER_PATH, layer_path.clone());
|
||||||
|
|
||||||
output.push(TableRow::from_parts(element, attributes));
|
output.push(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -321,8 +331,9 @@ impl Graphic {
|
||||||
|
|
||||||
pub fn had_clip_enabled(&self) -> bool {
|
pub fn had_clip_enabled(&self) -> bool {
|
||||||
fn all_clipped<T>(table: &Table<T>) -> bool {
|
fn all_clipped<T>(table: &Table<T>) -> bool {
|
||||||
table.iter_attribute_values_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING).all(|a| a.clip)
|
table.iter_attribute_values_or_default::<bool>(ATTR_CLIPPING_MASK).all(|clip| clip)
|
||||||
}
|
}
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
Graphic::Vector(table) => all_clipped(table),
|
Graphic::Vector(table) => all_clipped(table),
|
||||||
Graphic::Graphic(table) => all_clipped(table),
|
Graphic::Graphic(table) => all_clipped(table),
|
||||||
|
|
@ -335,11 +346,10 @@ impl Graphic {
|
||||||
|
|
||||||
pub fn can_reduce_to_clip_path(&self) -> bool {
|
pub fn can_reduce_to_clip_path(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Graphic::Vector(vector) => vector
|
Graphic::Vector(vector) => (0..vector.len()).all(|index| {
|
||||||
.iter_element_values()
|
let Some(element) = vector.element(index) else { return false };
|
||||||
.zip(vector.iter_attribute_values_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING))
|
let opacity: f64 = vector.attribute_cloned_or(ATTR_OPACITY, index, 1.);
|
||||||
.all(|(element, alpha_blending)| {
|
opacity > 1. - f64::EPSILON && element.style.fill().is_opaque() && element.style.stroke().is_none_or(|stroke| !stroke.has_renderable_stroke())
|
||||||
(alpha_blending.opacity > 1. - f32::EPSILON) && element.style.fill().is_opaque() && element.style.stroke().is_none_or(|stroke| !stroke.has_renderable_stroke())
|
|
||||||
}),
|
}),
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
|
|
@ -474,12 +484,23 @@ impl<T: Clone> OmitIndex for Table<T> {
|
||||||
pub fn migrate_graphic<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Table<Graphic>, D::Error> {
|
pub fn migrate_graphic<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Table<Graphic>, D::Error> {
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
/// Mirrors the removed `AlphaBlending` struct for legacy document deserialization.
|
||||||
|
#[derive(Clone, Debug, Default, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
#[cfg_attr(feature = "serde", serde(default))]
|
||||||
|
pub struct LegacyAlphaBlending {
|
||||||
|
pub blend_mode: BlendMode,
|
||||||
|
pub opacity: f32,
|
||||||
|
pub fill: f32,
|
||||||
|
pub clip: bool,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, DynAny, Default)]
|
#[derive(Clone, Debug, PartialEq, DynAny, Default)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub struct OldGraphicGroup {
|
pub struct OldGraphicGroup {
|
||||||
elements: Vec<(Graphic, Option<NodeId>)>,
|
elements: Vec<(Graphic, Option<NodeId>)>,
|
||||||
transform: DAffine2,
|
transform: DAffine2,
|
||||||
alpha_blending: AlphaBlending,
|
alpha_blending: LegacyAlphaBlending,
|
||||||
}
|
}
|
||||||
#[derive(Clone, Debug, PartialEq, DynAny, Default)]
|
#[derive(Clone, Debug, PartialEq, DynAny, Default)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
|
@ -502,7 +523,7 @@ pub fn migrate_graphic<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Res
|
||||||
#[cfg_attr(feature = "serde", serde(alias = "instances", alias = "instance"))]
|
#[cfg_attr(feature = "serde", serde(alias = "instances", alias = "instance"))]
|
||||||
element: Vec<T>,
|
element: Vec<T>,
|
||||||
transform: Vec<DAffine2>,
|
transform: Vec<DAffine2>,
|
||||||
alpha_blending: Vec<AlphaBlending>,
|
alpha_blending: Vec<LegacyAlphaBlending>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,8 @@ pub use vector_types;
|
||||||
pub use graphic::{Graphic, IntoGraphicTable, TryFromGraphic, Vector};
|
pub use graphic::{Graphic, IntoGraphicTable, TryFromGraphic, Vector};
|
||||||
|
|
||||||
pub mod migrations {
|
pub mod migrations {
|
||||||
use core_types::{
|
use core_types::blending::BlendMode;
|
||||||
AlphaBlending,
|
use core_types::table::{Table, TableRow};
|
||||||
table::{Table, TableRow},
|
|
||||||
};
|
|
||||||
use dyn_any::DynAny;
|
use dyn_any::DynAny;
|
||||||
use glam::DAffine2;
|
use glam::DAffine2;
|
||||||
use vector_types::vector::{PathStyle, PointDomain, RegionDomain, SegmentDomain, misc::HandleId};
|
use vector_types::vector::{PathStyle, PointDomain, RegionDomain, SegmentDomain, misc::HandleId};
|
||||||
|
|
@ -24,11 +22,22 @@ pub mod migrations {
|
||||||
pub fn migrate_vector<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Table<Vector>, D::Error> {
|
pub fn migrate_vector<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Table<Vector>, D::Error> {
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
/// Mirrors the removed `AlphaBlending` struct for legacy document deserialization.
|
||||||
|
#[derive(Clone, Debug, Default, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
#[cfg_attr(feature = "serde", serde(default))]
|
||||||
|
pub struct LegacyAlphaBlending {
|
||||||
|
pub blend_mode: BlendMode,
|
||||||
|
pub opacity: f32,
|
||||||
|
pub fill: f32,
|
||||||
|
pub clip: bool,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, DynAny)]
|
#[derive(Clone, Debug, PartialEq, DynAny)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub struct OldVectorData {
|
pub struct OldVectorData {
|
||||||
pub transform: DAffine2,
|
pub transform: DAffine2,
|
||||||
pub alpha_blending: AlphaBlending,
|
pub alpha_blending: LegacyAlphaBlending,
|
||||||
|
|
||||||
pub style: PathStyle,
|
pub style: PathStyle,
|
||||||
|
|
||||||
|
|
@ -47,7 +56,7 @@ pub mod migrations {
|
||||||
#[cfg_attr(feature = "serde", serde(alias = "instances", alias = "instance"))]
|
#[cfg_attr(feature = "serde", serde(alias = "instances", alias = "instance"))]
|
||||||
element: Vec<T>,
|
element: Vec<T>,
|
||||||
transform: Vec<DAffine2>,
|
transform: Vec<DAffine2>,
|
||||||
alpha_blending: Vec<AlphaBlending>,
|
alpha_blending: Vec<LegacyAlphaBlending>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
|
|
||||||
|
|
@ -1,63 +1,6 @@
|
||||||
use core::fmt::Display;
|
use core::fmt::Display;
|
||||||
use node_macro::BufferStruct;
|
use node_macro::BufferStruct;
|
||||||
use num_enum::{FromPrimitive, IntoPrimitive};
|
use num_enum::{FromPrimitive, IntoPrimitive};
|
||||||
#[cfg(not(feature = "std"))]
|
|
||||||
use num_traits::float::Float;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, BufferStruct)]
|
|
||||||
#[cfg_attr(feature = "std", derive(dyn_any::DynAny, serde::Serialize, serde::Deserialize, graphene_hash::CacheHash))]
|
|
||||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
|
||||||
#[cfg_attr(feature = "std", serde(default))]
|
|
||||||
pub struct AlphaBlending {
|
|
||||||
pub blend_mode: BlendMode,
|
|
||||||
pub opacity: f32,
|
|
||||||
pub fill: f32,
|
|
||||||
pub clip: bool,
|
|
||||||
}
|
|
||||||
impl Default for AlphaBlending {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Display for AlphaBlending {
|
|
||||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
|
||||||
let round = |x: f32| (x * 1e3).round() / 1e3;
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"Blend Mode: {} — Opacity: {}% — Fill: {}% — Clip: {}",
|
|
||||||
self.blend_mode,
|
|
||||||
round(self.opacity * 100.),
|
|
||||||
round(self.fill * 100.),
|
|
||||||
if self.clip { "Yes" } else { "No" }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AlphaBlending {
|
|
||||||
pub const fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
opacity: 1.,
|
|
||||||
fill: 1.,
|
|
||||||
blend_mode: BlendMode::Normal,
|
|
||||||
clip: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn lerp(&self, other: &Self, t: f32) -> Self {
|
|
||||||
let lerp = |a: f32, b: f32, t: f32| a + (b - a) * t;
|
|
||||||
|
|
||||||
AlphaBlending {
|
|
||||||
opacity: lerp(self.opacity, other.opacity, t),
|
|
||||||
fill: lerp(self.fill, other.fill, t),
|
|
||||||
blend_mode: if t < 0.5 { self.blend_mode } else { other.blend_mode },
|
|
||||||
clip: if t < 0.5 { self.clip } else { other.clip },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn opacity(&self, mask: bool) -> f32 {
|
|
||||||
self.opacity * if mask { 1. } else { self.fill }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(i32)]
|
#[repr(i32)]
|
||||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
use crate::raster_types::{CPU, Raster};
|
use crate::raster_types::{CPU, Raster};
|
||||||
use crate::{Bitmap, BitmapMut};
|
use crate::{Bitmap, BitmapMut};
|
||||||
|
use core_types::blending::BlendMode;
|
||||||
use core_types::color::float_to_srgb_u8;
|
use core_types::color::float_to_srgb_u8;
|
||||||
use core_types::table::{Table, TableRow};
|
use core_types::table::{Table, TableRow};
|
||||||
use core_types::{ATTR_ALPHA_BLENDING, ATTR_TRANSFORM, AlphaBlending, Color};
|
use core_types::{ATTR_BLEND_MODE, ATTR_CLIPPING_MASK, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_TRANSFORM, Color};
|
||||||
// use crate::vector::Vector; // TODO: Check if Vector is actually used, if so handle differently
|
// use crate::vector::Vector; // TODO: Check if Vector is actually used, if so handle differently
|
||||||
use core_types::color::*;
|
use core_types::color::*;
|
||||||
use dyn_any::{DynAny, StaticType};
|
use dyn_any::{DynAny, StaticType};
|
||||||
|
|
@ -222,6 +223,17 @@ impl<P: Pixel> IntoIterator for Image<P> {
|
||||||
pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Table<Raster<CPU>>, D::Error> {
|
pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Table<Raster<CPU>>, D::Error> {
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
/// Mirrors the removed `AlphaBlending` struct for legacy document deserialization.
|
||||||
|
#[derive(Clone, Debug, Default, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
#[cfg_attr(feature = "serde", serde(default))]
|
||||||
|
pub struct LegacyAlphaBlending {
|
||||||
|
pub blend_mode: BlendMode,
|
||||||
|
pub opacity: f32,
|
||||||
|
pub fill: f32,
|
||||||
|
pub clip: bool,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, core_types::CacheHash, PartialEq, DynAny)]
|
#[derive(Clone, Debug, core_types::CacheHash, PartialEq, DynAny)]
|
||||||
enum RasterFrame {
|
enum RasterFrame {
|
||||||
ImageFrame(Table<Image<Color>>),
|
ImageFrame(Table<Image<Color>>),
|
||||||
|
|
@ -280,7 +292,7 @@ pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
|
||||||
pub struct OldImageFrame<P: Pixel> {
|
pub struct OldImageFrame<P: Pixel> {
|
||||||
image: Image<P>,
|
image: Image<P>,
|
||||||
transform: DAffine2,
|
transform: DAffine2,
|
||||||
alpha_blending: AlphaBlending,
|
alpha_blending: LegacyAlphaBlending,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
|
@ -303,7 +315,7 @@ pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
|
||||||
#[cfg_attr(feature = "serde", serde(alias = "instances", alias = "instance"))]
|
#[cfg_attr(feature = "serde", serde(alias = "instances", alias = "instance"))]
|
||||||
element: Vec<T>,
|
element: Vec<T>,
|
||||||
transform: Vec<DAffine2>,
|
transform: Vec<DAffine2>,
|
||||||
alpha_blending: Vec<AlphaBlending>,
|
alpha_blending: Vec<LegacyAlphaBlending>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
|
@ -339,7 +351,10 @@ pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
|
||||||
FormatVersions::OldImageFrame(OldImageFrame { image, transform, alpha_blending }) => {
|
FormatVersions::OldImageFrame(OldImageFrame { image, transform, alpha_blending }) => {
|
||||||
let mut image_frame_table = Table::new_from_element(Raster::new_cpu(image));
|
let mut image_frame_table = Table::new_from_element(Raster::new_cpu(image));
|
||||||
image_frame_table.set_attribute(ATTR_TRANSFORM, 0, transform);
|
image_frame_table.set_attribute(ATTR_TRANSFORM, 0, transform);
|
||||||
image_frame_table.set_attribute(ATTR_ALPHA_BLENDING, 0, alpha_blending);
|
image_frame_table.set_attribute(ATTR_BLEND_MODE, 0, alpha_blending.blend_mode);
|
||||||
|
image_frame_table.set_attribute(ATTR_OPACITY, 0, alpha_blending.opacity as f64);
|
||||||
|
image_frame_table.set_attribute(ATTR_OPACITY_FILL, 0, alpha_blending.fill as f64);
|
||||||
|
image_frame_table.set_attribute(ATTR_CLIPPING_MASK, 0, alpha_blending.clip);
|
||||||
image_frame_table
|
image_frame_table
|
||||||
}
|
}
|
||||||
FormatVersions::OlderImageFrameTable(old_table) => from_image_frame_table(older_table_to_new_table(old_table)),
|
FormatVersions::OlderImageFrameTable(old_table) => from_image_frame_table(older_table_to_new_table(old_table)),
|
||||||
|
|
@ -356,6 +371,17 @@ pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
|
||||||
pub fn migrate_image_frame_row<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<TableRow<Raster<CPU>>, D::Error> {
|
pub fn migrate_image_frame_row<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<TableRow<Raster<CPU>>, D::Error> {
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
/// Mirrors the removed `AlphaBlending` struct for legacy document deserialization.
|
||||||
|
#[derive(Clone, Debug, Default, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
#[cfg_attr(feature = "serde", serde(default))]
|
||||||
|
pub struct LegacyAlphaBlending {
|
||||||
|
pub blend_mode: BlendMode,
|
||||||
|
pub opacity: f32,
|
||||||
|
pub fill: f32,
|
||||||
|
pub clip: bool,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, DynAny)]
|
#[derive(Clone, Debug, PartialEq, DynAny)]
|
||||||
enum RasterFrame {
|
enum RasterFrame {
|
||||||
/// A CPU-based bitmap image with a finite position and extent, equivalent to the SVG <image> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/image
|
/// A CPU-based bitmap image with a finite position and extent, equivalent to the SVG <image> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/image
|
||||||
|
|
@ -416,7 +442,7 @@ pub fn migrate_image_frame_row<'de, D: serde::Deserializer<'de>>(deserializer: D
|
||||||
pub struct OldImageFrame<P: Pixel> {
|
pub struct OldImageFrame<P: Pixel> {
|
||||||
image: Image<P>,
|
image: Image<P>,
|
||||||
transform: DAffine2,
|
transform: DAffine2,
|
||||||
alpha_blending: AlphaBlending,
|
alpha_blending: LegacyAlphaBlending,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
use crate::render_ext::RenderExt;
|
use crate::render_ext::RenderExt;
|
||||||
use crate::to_peniko::BlendModeExt;
|
use crate::to_peniko::BlendModeExt;
|
||||||
use core_types::AlphaBlending;
|
|
||||||
use core_types::CacheHash;
|
use core_types::CacheHash;
|
||||||
use core_types::blending::BlendMode;
|
use core_types::blending::BlendMode;
|
||||||
use core_types::bounds::{BoundingBox, RenderBoundingBox};
|
use core_types::bounds::{BoundingBox, RenderBoundingBox};
|
||||||
|
|
@ -11,7 +10,8 @@ use core_types::table::{Table, TableRow};
|
||||||
use core_types::transform::Footprint;
|
use core_types::transform::Footprint;
|
||||||
use core_types::uuid::{NodeId, generate_uuid};
|
use core_types::uuid::{NodeId, generate_uuid};
|
||||||
use core_types::{
|
use core_types::{
|
||||||
ATTR_ALPHA_BLENDING, ATTR_BACKGROUND, ATTR_CLIP, ATTR_DIMENSIONS, ATTR_EDITOR_LAYER_PATH, ATTR_EDITOR_MERGED_LAYERS, ATTR_GRADIENT_TYPE, ATTR_LOCATION, ATTR_SPREAD_METHOD, ATTR_TRANSFORM,
|
ATTR_BACKGROUND, ATTR_BLEND_MODE, ATTR_CLIP, ATTR_CLIPPING_MASK, ATTR_DIMENSIONS, ATTR_EDITOR_LAYER_PATH, ATTR_EDITOR_MERGED_LAYERS, ATTR_GRADIENT_TYPE, ATTR_LOCATION, ATTR_OPACITY,
|
||||||
|
ATTR_OPACITY_FILL, ATTR_SPREAD_METHOD, ATTR_TRANSFORM,
|
||||||
};
|
};
|
||||||
use dyn_any::DynAny;
|
use dyn_any::DynAny;
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
|
|
@ -686,7 +686,9 @@ impl Render for Table<Graphic> {
|
||||||
|
|
||||||
for index in 0..self.len() {
|
for index in 0..self.len() {
|
||||||
let transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
let transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||||
let alpha_blending: AlphaBlending = self.attribute_cloned_or_default(ATTR_ALPHA_BLENDING, index);
|
let blend_mode: BlendMode = self.attribute_cloned_or_default(ATTR_BLEND_MODE, index);
|
||||||
|
let opacity_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY, index, 1.);
|
||||||
|
let opacity_fill_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY_FILL, index, 1.);
|
||||||
let element = self.element(index).unwrap();
|
let element = self.element(index).unwrap();
|
||||||
|
|
||||||
render.parent_tag(
|
render.parent_tag(
|
||||||
|
|
@ -697,13 +699,13 @@ impl Render for Table<Graphic> {
|
||||||
attributes.push(ATTR_TRANSFORM, matrix);
|
attributes.push(ATTR_TRANSFORM, matrix);
|
||||||
}
|
}
|
||||||
|
|
||||||
let opacity = alpha_blending.opacity(render_params.for_mask);
|
let opacity = (opacity_attr * if render_params.for_mask { 1. } else { opacity_fill_attr }) as f32;
|
||||||
if opacity < 1. {
|
if opacity < 1. {
|
||||||
attributes.push("opacity", opacity.to_string());
|
attributes.push("opacity", opacity.to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
if alpha_blending.blend_mode != BlendMode::default() {
|
if blend_mode != BlendMode::default() {
|
||||||
attributes.push("style", alpha_blending.blend_mode.render());
|
attributes.push("style", blend_mode.render());
|
||||||
}
|
}
|
||||||
|
|
||||||
let next_clips = index + 1 < self.len() && self.element(index + 1).unwrap().had_clip_enabled();
|
let next_clips = index + 1 < self.len() && self.element(index + 1).unwrap().had_clip_enabled();
|
||||||
|
|
@ -741,19 +743,21 @@ impl Render for Table<Graphic> {
|
||||||
for index in 0..self.len() {
|
for index in 0..self.len() {
|
||||||
let row_transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
let row_transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||||
let transform = transform * row_transform;
|
let transform = transform * row_transform;
|
||||||
let alpha_blending: AlphaBlending = self.attribute_cloned_or_default(ATTR_ALPHA_BLENDING, index);
|
let blend_mode_attr: BlendMode = self.attribute_cloned_or_default(ATTR_BLEND_MODE, index);
|
||||||
|
let opacity_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY, index, 1.);
|
||||||
|
let opacity_fill_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY_FILL, index, 1.);
|
||||||
let element = self.element(index).unwrap();
|
let element = self.element(index).unwrap();
|
||||||
|
|
||||||
let mut layer = false;
|
let mut layer = false;
|
||||||
|
|
||||||
let blend_mode = match render_params.render_mode {
|
let blend_mode = match render_params.render_mode {
|
||||||
RenderMode::Outline => peniko::Mix::Normal,
|
RenderMode::Outline => peniko::Mix::Normal,
|
||||||
_ => alpha_blending.blend_mode.to_peniko(),
|
_ => blend_mode_attr.to_peniko(),
|
||||||
};
|
};
|
||||||
let mut bounds = RenderBoundingBox::None;
|
let mut bounds = RenderBoundingBox::None;
|
||||||
|
|
||||||
let opacity = alpha_blending.opacity(render_params.for_mask);
|
let opacity = (opacity_attr * if render_params.for_mask { 1. } else { opacity_fill_attr }) as f32;
|
||||||
if opacity < 1. || (render_params.render_mode != RenderMode::Outline && alpha_blending.blend_mode != BlendMode::default()) {
|
if opacity < 1. || (render_params.render_mode != RenderMode::Outline && blend_mode_attr != BlendMode::default()) {
|
||||||
bounds = element.bounding_box(transform, true);
|
bounds = element.bounding_box(transform, true);
|
||||||
|
|
||||||
if let RenderBoundingBox::Rectangle(bounds) = bounds {
|
if let RenderBoundingBox::Rectangle(bounds) = bounds {
|
||||||
|
|
@ -882,7 +886,10 @@ impl Render for Table<Vector> {
|
||||||
for index in 0..self.len() {
|
for index in 0..self.len() {
|
||||||
let Some(vector) = self.element(index) else { continue };
|
let Some(vector) = self.element(index) else { continue };
|
||||||
let multiplied_transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
let multiplied_transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||||
let alpha_blending: AlphaBlending = self.attribute_cloned_or_default(ATTR_ALPHA_BLENDING, index);
|
let blend_mode_attr: BlendMode = self.attribute_cloned_or_default(ATTR_BLEND_MODE, index);
|
||||||
|
let opacity_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY, index, 1.);
|
||||||
|
let opacity_fill_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY_FILL, index, 1.);
|
||||||
|
let clipping_mask_attr: bool = self.attribute_cloned_or_default(ATTR_CLIPPING_MASK, index);
|
||||||
|
|
||||||
// Only consider strokes with non-zero weight, since default strokes with zero weight would prevent assigning the correct stroke transform
|
// 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 has_real_stroke = vector.style.stroke().filter(|stroke| stroke.weight() > 0.);
|
||||||
|
|
@ -948,7 +955,10 @@ impl Render for Table<Vector> {
|
||||||
let vector_row = Table::new_from_row(
|
let vector_row = Table::new_from_row(
|
||||||
TableRow::new_from_element(cloned_vector)
|
TableRow::new_from_element(cloned_vector)
|
||||||
.with_attribute(ATTR_TRANSFORM, multiplied_transform)
|
.with_attribute(ATTR_TRANSFORM, multiplied_transform)
|
||||||
.with_attribute(ATTR_ALPHA_BLENDING, alpha_blending),
|
.with_attribute(ATTR_BLEND_MODE, blend_mode_attr)
|
||||||
|
.with_attribute(ATTR_OPACITY, opacity_attr)
|
||||||
|
.with_attribute(ATTR_OPACITY_FILL, opacity_fill_attr)
|
||||||
|
.with_attribute(ATTR_CLIPPING_MASK, clipping_mask_attr),
|
||||||
);
|
);
|
||||||
|
|
||||||
(id, mask_type, vector_row)
|
(id, mask_type, vector_row)
|
||||||
|
|
@ -1034,13 +1044,13 @@ impl Render for Table<Vector> {
|
||||||
attributes.push("fill-rule", "evenodd");
|
attributes.push("fill-rule", "evenodd");
|
||||||
}
|
}
|
||||||
|
|
||||||
let opacity = alpha_blending.opacity(render_params.for_mask);
|
let opacity = (opacity_attr * if render_params.for_mask { 1. } else { opacity_fill_attr }) as f32;
|
||||||
if opacity < 1. {
|
if opacity < 1. {
|
||||||
attributes.push("opacity", opacity.to_string());
|
attributes.push("opacity", opacity.to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
if alpha_blending.blend_mode != BlendMode::default() {
|
if blend_mode_attr != BlendMode::default() {
|
||||||
attributes.push("style", alpha_blending.blend_mode.render());
|
attributes.push("style", blend_mode_attr.render());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -1076,7 +1086,10 @@ impl Render for Table<Vector> {
|
||||||
|
|
||||||
let Some(element) = self.element(index) else { continue };
|
let Some(element) = self.element(index) else { continue };
|
||||||
let row_transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
let row_transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||||
let alpha_blending: AlphaBlending = self.attribute_cloned_or_default(ATTR_ALPHA_BLENDING, index);
|
let blend_mode_attr: BlendMode = self.attribute_cloned_or_default(ATTR_BLEND_MODE, index);
|
||||||
|
let opacity_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY, index, 1.);
|
||||||
|
let opacity_fill_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY_FILL, index, 1.);
|
||||||
|
let clip_attr: bool = self.attribute_cloned_or_default(ATTR_CLIPPING_MASK, index);
|
||||||
let multiplied_transform = parent_transform * row_transform;
|
let multiplied_transform = parent_transform * row_transform;
|
||||||
let has_real_stroke = element.style.stroke().filter(|stroke| stroke.weight() > 0.);
|
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 set_stroke_transform = has_real_stroke.map(|stroke| stroke.transform).filter(|transform| transform.matrix2.determinant() != 0.);
|
||||||
|
|
@ -1105,12 +1118,12 @@ impl Render for Table<Vector> {
|
||||||
// If we're using opacity or a blend mode, we need to push a layer
|
// If we're using opacity or a blend mode, we need to push a layer
|
||||||
let blend_mode = match render_params.render_mode {
|
let blend_mode = match render_params.render_mode {
|
||||||
RenderMode::Outline => peniko::Mix::Normal,
|
RenderMode::Outline => peniko::Mix::Normal,
|
||||||
_ => alpha_blending.blend_mode.to_peniko(),
|
_ => blend_mode_attr.to_peniko(),
|
||||||
};
|
};
|
||||||
let mut layer = false;
|
let mut layer = false;
|
||||||
|
|
||||||
let opacity = alpha_blending.opacity(render_params.for_mask);
|
let opacity = (opacity_attr * if render_params.for_mask { 1. } else { opacity_fill_attr }) as f32;
|
||||||
if opacity < 1. || alpha_blending.blend_mode != BlendMode::default() {
|
if opacity < 1. || blend_mode_attr != BlendMode::default() {
|
||||||
layer = true;
|
layer = true;
|
||||||
let weight = 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 quad = Quad::from_box(layer_bounds).inflate(weight * max_scale(applied_stroke_transform));
|
||||||
|
|
@ -1264,7 +1277,10 @@ impl Render for Table<Vector> {
|
||||||
let vector_table = Table::new_from_row(
|
let vector_table = Table::new_from_row(
|
||||||
TableRow::new_from_element(cloned_element)
|
TableRow::new_from_element(cloned_element)
|
||||||
.with_attribute(ATTR_TRANSFORM, row_transform)
|
.with_attribute(ATTR_TRANSFORM, row_transform)
|
||||||
.with_attribute(ATTR_ALPHA_BLENDING, alpha_blending),
|
.with_attribute(ATTR_BLEND_MODE, blend_mode_attr)
|
||||||
|
.with_attribute(ATTR_OPACITY, opacity_attr)
|
||||||
|
.with_attribute(ATTR_OPACITY_FILL, opacity_fill_attr)
|
||||||
|
.with_attribute(ATTR_CLIPPING_MASK, clip_attr),
|
||||||
);
|
);
|
||||||
|
|
||||||
let bounds = element.bounding_box_with_transform(multiplied_transform).unwrap_or(layer_bounds);
|
let bounds = element.bounding_box_with_transform(multiplied_transform).unwrap_or(layer_bounds);
|
||||||
|
|
@ -1442,7 +1458,9 @@ impl Render for Table<Raster<CPU>> {
|
||||||
let Some(image) = self.element(index) else { continue };
|
let Some(image) = self.element(index) else { continue };
|
||||||
|
|
||||||
let transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
let transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||||
let alpha_blending: AlphaBlending = self.attribute_cloned_or_default(ATTR_ALPHA_BLENDING, index);
|
let blend_mode_attr: BlendMode = self.attribute_cloned_or_default(ATTR_BLEND_MODE, index);
|
||||||
|
let opacity_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY, index, 1.);
|
||||||
|
let opacity_fill_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY_FILL, index, 1.);
|
||||||
|
|
||||||
if image.data.is_empty() {
|
if image.data.is_empty() {
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -1469,13 +1487,13 @@ impl Render for Table<Raster<CPU>> {
|
||||||
attributes.push("width", size.x.to_string());
|
attributes.push("width", size.x.to_string());
|
||||||
attributes.push("height", size.y.to_string());
|
attributes.push("height", size.y.to_string());
|
||||||
|
|
||||||
let opacity = alpha_blending.opacity(render_params.for_mask);
|
let opacity = (opacity_attr * if render_params.for_mask { 1. } else { opacity_fill_attr }) as f32;
|
||||||
if opacity < 1. {
|
if opacity < 1. {
|
||||||
attributes.push("opacity", opacity.to_string());
|
attributes.push("opacity", opacity.to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
if alpha_blending.blend_mode != BlendMode::default() {
|
if blend_mode_attr != BlendMode::default() {
|
||||||
attributes.push("style", alpha_blending.blend_mode.render());
|
attributes.push("style", blend_mode_attr.render());
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|render| {
|
|render| {
|
||||||
|
|
@ -1509,12 +1527,12 @@ impl Render for Table<Raster<CPU>> {
|
||||||
attributes.push(ATTR_TRANSFORM, matrix);
|
attributes.push(ATTR_TRANSFORM, matrix);
|
||||||
}
|
}
|
||||||
|
|
||||||
let opacity = alpha_blending.opacity(render_params.for_mask);
|
let opacity = (opacity_attr * if render_params.for_mask { 1. } else { opacity_fill_attr }) as f32;
|
||||||
if opacity < 1. {
|
if opacity < 1. {
|
||||||
attributes.push("opacity", opacity.to_string());
|
attributes.push("opacity", opacity.to_string());
|
||||||
}
|
}
|
||||||
if alpha_blending.blend_mode != BlendMode::default() {
|
if blend_mode_attr != BlendMode::default() {
|
||||||
attributes.push("style", alpha_blending.blend_mode.render());
|
attributes.push("style", blend_mode_attr.render());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -1528,13 +1546,15 @@ impl Render for Table<Raster<CPU>> {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let alpha_blending: AlphaBlending = self.attribute_cloned_or_default(ATTR_ALPHA_BLENDING, index);
|
let blend_mode_attr: BlendMode = self.attribute_cloned_or_default(ATTR_BLEND_MODE, index);
|
||||||
let blend_mode = alpha_blending.blend_mode.to_peniko();
|
let opacity_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY, index, 1.);
|
||||||
|
let opacity_fill_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY_FILL, index, 1.);
|
||||||
|
let blend_mode = blend_mode_attr.to_peniko();
|
||||||
|
|
||||||
let opacity = alpha_blending.opacity(render_params.for_mask);
|
let opacity = (opacity_attr * if render_params.for_mask { 1. } else { opacity_fill_attr }) as f32;
|
||||||
let mut layer = false;
|
let mut layer = false;
|
||||||
|
|
||||||
if (opacity < 1. || (render_params.render_mode != RenderMode::Outline && alpha_blending.blend_mode != BlendMode::default()))
|
if (opacity < 1. || (render_params.render_mode != RenderMode::Outline && blend_mode_attr != BlendMode::default()))
|
||||||
&& let RenderBoundingBox::Rectangle(bounds) = self.bounding_box(transform, false)
|
&& let RenderBoundingBox::Rectangle(bounds) = self.bounding_box(transform, false)
|
||||||
{
|
{
|
||||||
let blending = peniko::BlendMode::new(blend_mode, peniko::Compose::SrcOver);
|
let blending = peniko::BlendMode::new(blend_mode, peniko::Compose::SrcOver);
|
||||||
|
|
@ -1615,20 +1635,25 @@ impl Render for Table<Raster<GPU>> {
|
||||||
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) {
|
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) {
|
||||||
for index in 0..self.len() {
|
for index in 0..self.len() {
|
||||||
let Some(raster) = self.element(index) else { continue };
|
let Some(raster) = self.element(index) else { continue };
|
||||||
let alpha_blending: AlphaBlending = self.attribute_cloned_or_default(ATTR_ALPHA_BLENDING, index);
|
let blend_mode_attr: BlendMode = self.attribute_cloned_or_default(ATTR_BLEND_MODE, index);
|
||||||
|
let opacity_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY, index, 1.);
|
||||||
|
let opacity_fill_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY_FILL, index, 1.);
|
||||||
|
let clip_attr: bool = self.attribute_cloned_or_default(ATTR_CLIPPING_MASK, index);
|
||||||
let blend_mode = match render_params.render_mode {
|
let blend_mode = match render_params.render_mode {
|
||||||
RenderMode::Outline => peniko::Mix::Normal,
|
RenderMode::Outline => peniko::Mix::Normal,
|
||||||
_ => alpha_blending.blend_mode.to_peniko(),
|
_ => blend_mode_attr.to_peniko(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut layer = false;
|
let mut layer = false;
|
||||||
|
|
||||||
if (render_params.render_mode != RenderMode::Outline && alpha_blending != Default::default())
|
let opacity = (opacity_attr * if render_params.for_mask { 1. } else { opacity_fill_attr }) as f32;
|
||||||
|
let any_nondefault = blend_mode_attr != BlendMode::default() || opacity < 1. || clip_attr;
|
||||||
|
if (render_params.render_mode != RenderMode::Outline && any_nondefault)
|
||||||
&& let RenderBoundingBox::Rectangle(bounds) = self.bounding_box(transform, true)
|
&& let RenderBoundingBox::Rectangle(bounds) = self.bounding_box(transform, true)
|
||||||
{
|
{
|
||||||
let blending = peniko::BlendMode::new(blend_mode, peniko::Compose::SrcOver);
|
let blending = peniko::BlendMode::new(blend_mode, peniko::Compose::SrcOver);
|
||||||
let rect = kurbo::Rect::new(bounds[0].x, bounds[0].y, bounds[1].x, bounds[1].y);
|
let rect = kurbo::Rect::new(bounds[0].x, bounds[0].y, bounds[1].x, bounds[1].y);
|
||||||
scene.push_layer(peniko::Fill::NonZero, blending, alpha_blending.opacity, kurbo::Affine::IDENTITY, &rect);
|
scene.push_layer(peniko::Fill::NonZero, blending, opacity, kurbo::Affine::IDENTITY, &rect);
|
||||||
layer = true;
|
layer = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1703,7 +1728,10 @@ impl Render for Table<Raster<GPU>> {
|
||||||
// later replace with the current viewport transform before each render.
|
// later replace with the current viewport transform before each render.
|
||||||
impl Render for Table<Color> {
|
impl Render for Table<Color> {
|
||||||
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
|
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
|
||||||
for (color, alpha_blending) in self.iter_element_values().zip(self.iter_attribute_values_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING)) {
|
for (index, color) in self.iter_element_values().enumerate() {
|
||||||
|
let blend_mode: BlendMode = self.attribute_cloned_or_default(ATTR_BLEND_MODE, index);
|
||||||
|
let opacity_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY, index, 1.);
|
||||||
|
let opacity_fill_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY_FILL, index, 1.);
|
||||||
render.leaf_tag("polyline", |attributes| {
|
render.leaf_tag("polyline", |attributes| {
|
||||||
// Stand-in for an infinite background. Chrome's SVG renderer keeps internal coordinates in f32 and loses
|
// Stand-in for an infinite background. Chrome's SVG renderer keeps internal coordinates in f32 and loses
|
||||||
// precision past ~2^24 (~16.7 million), causing tile-boundary artifacts that pop in and out during panning.
|
// precision past ~2^24 (~16.7 million), causing tile-boundary artifacts that pop in and out during panning.
|
||||||
|
|
@ -1716,13 +1744,13 @@ impl Render for Table<Color> {
|
||||||
attributes.push("fill-opacity", ((color.a() * 1000.).round() / 1000.).to_string());
|
attributes.push("fill-opacity", ((color.a() * 1000.).round() / 1000.).to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
let opacity = alpha_blending.opacity(render_params.for_mask);
|
let opacity = (opacity_attr * if render_params.for_mask { 1. } else { opacity_fill_attr }) as f32;
|
||||||
if opacity < 1. {
|
if opacity < 1. {
|
||||||
attributes.push("opacity", opacity.to_string());
|
attributes.push("opacity", opacity.to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
if alpha_blending.blend_mode != BlendMode::default() {
|
if blend_mode != BlendMode::default() {
|
||||||
attributes.push("style", alpha_blending.blend_mode.render());
|
attributes.push("style", blend_mode.render());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -1731,16 +1759,19 @@ impl Render for Table<Color> {
|
||||||
fn render_to_vello(&self, scene: &mut Scene, _parent_transform: DAffine2, _context: &mut RenderContext, render_params: &RenderParams) {
|
fn render_to_vello(&self, scene: &mut Scene, _parent_transform: DAffine2, _context: &mut RenderContext, render_params: &RenderParams) {
|
||||||
use vello::peniko;
|
use vello::peniko;
|
||||||
|
|
||||||
for (color, alpha_blending) in self.iter_element_values().zip(self.iter_attribute_values_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING)) {
|
for (index, color) in self.iter_element_values().enumerate() {
|
||||||
let blend_mode = alpha_blending.blend_mode.to_peniko();
|
let blend_mode_attr: BlendMode = self.attribute_cloned_or_default(ATTR_BLEND_MODE, index);
|
||||||
let opacity = alpha_blending.opacity(render_params.for_mask);
|
let opacity_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY, index, 1.);
|
||||||
|
let opacity_fill_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY_FILL, index, 1.);
|
||||||
|
let blend_mode = blend_mode_attr.to_peniko();
|
||||||
|
let opacity = (opacity_attr * if render_params.for_mask { 1. } else { opacity_fill_attr }) as f32;
|
||||||
|
|
||||||
let vello_color = peniko::Color::new([color.r(), color.g(), color.b(), color.a()]);
|
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.));
|
let rect = kurbo::Rect::from_origin_size(kurbo::Point::ZERO, kurbo::Size::new(1., 1.));
|
||||||
|
|
||||||
let mut layer = false;
|
let mut layer = false;
|
||||||
if opacity < 1. || alpha_blending.blend_mode != BlendMode::default() {
|
if opacity < 1. || blend_mode_attr != BlendMode::default() {
|
||||||
let blending = peniko::BlendMode::new(blend_mode, peniko::Compose::SrcOver);
|
let blending = peniko::BlendMode::new(blend_mode, peniko::Compose::SrcOver);
|
||||||
scene.push_layer(peniko::Fill::NonZero, blending, opacity, kurbo::Affine::scale(f64::INFINITY), &rect);
|
scene.push_layer(peniko::Fill::NonZero, blending, opacity, kurbo::Affine::scale(f64::INFINITY), &rect);
|
||||||
layer = true;
|
layer = true;
|
||||||
|
|
@ -1770,7 +1801,9 @@ impl Render for Table<GradientStops> {
|
||||||
for index in 0..self.len() {
|
for index in 0..self.len() {
|
||||||
let Some(gradient) = self.element(index) else { continue };
|
let Some(gradient) = self.element(index) else { continue };
|
||||||
let transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
let transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||||
let alpha_blending: AlphaBlending = self.attribute_cloned_or_default(ATTR_ALPHA_BLENDING, index);
|
let blend_mode: BlendMode = self.attribute_cloned_or_default(ATTR_BLEND_MODE, index);
|
||||||
|
let opacity_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY, index, 1.);
|
||||||
|
let opacity_fill_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY_FILL, index, 1.);
|
||||||
let spread_method: GradientSpreadMethod = self.attribute_cloned_or_default(ATTR_SPREAD_METHOD, index);
|
let spread_method: GradientSpreadMethod = self.attribute_cloned_or_default(ATTR_SPREAD_METHOD, index);
|
||||||
let gradient_type: GradientType = self.attribute_cloned_or_default(ATTR_GRADIENT_TYPE, index);
|
let gradient_type: GradientType = self.attribute_cloned_or_default(ATTR_GRADIENT_TYPE, index);
|
||||||
let tag = if thumbnail_rect.is_some() { "rect" } else { "polyline" };
|
let tag = if thumbnail_rect.is_some() { "rect" } else { "polyline" };
|
||||||
|
|
@ -1834,13 +1867,13 @@ impl Render for Table<GradientStops> {
|
||||||
|
|
||||||
attributes.push("fill", format!("url('#{gradient_id}')"));
|
attributes.push("fill", format!("url('#{gradient_id}')"));
|
||||||
|
|
||||||
let opacity = alpha_blending.opacity(render_params.for_mask);
|
let opacity = (opacity_attr * if render_params.for_mask { 1. } else { opacity_fill_attr }) as f32;
|
||||||
if opacity < 1. {
|
if opacity < 1. {
|
||||||
attributes.push("opacity", opacity.to_string());
|
attributes.push("opacity", opacity.to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
if alpha_blending.blend_mode != BlendMode::default() {
|
if blend_mode != BlendMode::default() {
|
||||||
attributes.push("style", alpha_blending.blend_mode.render());
|
attributes.push("style", blend_mode.render());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -1853,17 +1886,20 @@ impl Render for Table<GradientStops> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for ((((gradient, transform), alpha_blending), spread_method), gradient_type) in self
|
for (((index, gradient), spread_method), gradient_type) in self
|
||||||
.iter_element_values()
|
.iter_element_values()
|
||||||
.zip(self.iter_attribute_values_or_default::<DAffine2>(ATTR_TRANSFORM))
|
.enumerate()
|
||||||
.zip(self.iter_attribute_values_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING))
|
|
||||||
.zip(self.iter_attribute_values_or_default::<GradientSpreadMethod>(ATTR_SPREAD_METHOD))
|
.zip(self.iter_attribute_values_or_default::<GradientSpreadMethod>(ATTR_SPREAD_METHOD))
|
||||||
.zip(self.iter_attribute_values_or_default::<GradientType>(ATTR_GRADIENT_TYPE))
|
.zip(self.iter_attribute_values_or_default::<GradientType>(ATTR_GRADIENT_TYPE))
|
||||||
{
|
{
|
||||||
|
let transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||||
|
let blend_mode_attr: BlendMode = self.attribute_cloned_or_default(ATTR_BLEND_MODE, index);
|
||||||
|
let opacity_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY, index, 1.);
|
||||||
|
let opacity_fill_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY_FILL, index, 1.);
|
||||||
let gradient_transform = parent_transform * transform;
|
let gradient_transform = parent_transform * transform;
|
||||||
|
|
||||||
let blend_mode = alpha_blending.blend_mode.to_peniko();
|
let blend_mode = blend_mode_attr.to_peniko();
|
||||||
let opacity = alpha_blending.opacity(render_params.for_mask);
|
let opacity = (opacity_attr * if render_params.for_mask { 1. } else { opacity_fill_attr }) as f32;
|
||||||
|
|
||||||
let mut stops: peniko::ColorStops = peniko::ColorStops::new();
|
let mut stops: peniko::ColorStops = peniko::ColorStops::new();
|
||||||
for (position, color, _) in gradient.interpolated_samples() {
|
for (position, color, _) in gradient.interpolated_samples() {
|
||||||
|
|
@ -1907,7 +1943,7 @@ impl Render for Table<GradientStops> {
|
||||||
let rect = kurbo::Rect::from_origin_size(kurbo::Point::ZERO, kurbo::Size::new(1., 1.));
|
let rect = kurbo::Rect::from_origin_size(kurbo::Point::ZERO, kurbo::Size::new(1., 1.));
|
||||||
|
|
||||||
let mut layer = false;
|
let mut layer = false;
|
||||||
if opacity < 1. || alpha_blending.blend_mode != BlendMode::default() {
|
if opacity < 1. || blend_mode_attr != BlendMode::default() {
|
||||||
let blending = peniko::BlendMode::new(blend_mode, peniko::Compose::SrcOver);
|
let blending = peniko::BlendMode::new(blend_mode, peniko::Compose::SrcOver);
|
||||||
// See implementation in `Table<Color>` for more detail
|
// See implementation in `Table<Color>` for more detail
|
||||||
scene.push_layer(peniko::Fill::NonZero, blending, opacity, kurbo::Affine::scale(f64::INFINITY), &rect);
|
scene.push_layer(peniko::Fill::NonZero, blending, opacity, kurbo::Affine::scale(f64::INFINITY), &rect);
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
//! Contains stylistic options for SVG elements.
|
//! Contains stylistic options for SVG elements.
|
||||||
|
|
||||||
pub use crate::gradient::*;
|
pub use crate::gradient::*;
|
||||||
use core_types::ATTR_ALPHA_BLENDING;
|
use core_types::ATTR_OPACITY;
|
||||||
use core_types::AlphaBlending;
|
|
||||||
use core_types::Color;
|
use core_types::Color;
|
||||||
use core_types::color::Alpha;
|
use core_types::color::Alpha;
|
||||||
use core_types::table::Table;
|
use core_types::table::Table;
|
||||||
|
|
@ -136,9 +135,9 @@ impl From<Option<Color>> for Fill {
|
||||||
|
|
||||||
impl From<Table<Color>> for Fill {
|
impl From<Table<Color>> for Fill {
|
||||||
fn from(color: Table<Color>) -> Fill {
|
fn from(color: Table<Color>) -> Fill {
|
||||||
let alpha = color.attribute_cloned_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING, 0).opacity;
|
let alpha: f64 = color.attribute_cloned_or(ATTR_OPACITY, 0, 1.);
|
||||||
let color = color.element(0).copied();
|
let color = color.element(0).copied();
|
||||||
Fill::solid_or_none(color.map(|c| c.with_alpha(c.alpha() * alpha)))
|
Fill::solid_or_none(color.map(|c| c.with_alpha(c.alpha() * alpha as f32)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
use core_types::AlphaBlending;
|
|
||||||
use core_types::registry::types::Percentage;
|
use core_types::registry::types::Percentage;
|
||||||
use core_types::table::Table;
|
use core_types::table::Table;
|
||||||
use core_types::{ATTR_ALPHA_BLENDING, BlendMode, Color, Ctx};
|
use core_types::{ATTR_BLEND_MODE, ATTR_CLIPPING_MASK, ATTR_OPACITY, ATTR_OPACITY_FILL, BlendMode, Color, Ctx};
|
||||||
use graphic_types::Graphic;
|
use graphic_types::Graphic;
|
||||||
use graphic_types::Vector;
|
use graphic_types::Vector;
|
||||||
use graphic_types::raster_types::{CPU, Raster};
|
use graphic_types::raster_types::{CPU, Raster};
|
||||||
|
|
@ -16,39 +15,42 @@ impl MultiplyAlpha for Color {
|
||||||
*self = Color::from_rgbaf32_unchecked(self.r(), self.g(), self.b(), (self.a() * factor as f32).clamp(0., 1.))
|
*self = Color::from_rgbaf32_unchecked(self.r(), self.g(), self.b(), (self.a() * factor as f32).clamp(0., 1.))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn multiply_table_attribute<T>(table: &mut Table<T>, key: &str, factor: f64) {
|
||||||
|
if let Some(values) = table.iter_attribute_values_mut::<f64>(key) {
|
||||||
|
for v in values {
|
||||||
|
*v *= factor;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for v in table.iter_attribute_values_mut_or_default::<f64>(key) {
|
||||||
|
*v = factor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl MultiplyAlpha for Table<Vector> {
|
impl MultiplyAlpha for Table<Vector> {
|
||||||
fn multiply_alpha(&mut self, factor: f64) {
|
fn multiply_alpha(&mut self, factor: f64) {
|
||||||
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING) {
|
multiply_table_attribute(self, ATTR_OPACITY, factor);
|
||||||
a.opacity *= factor as f32;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl MultiplyAlpha for Table<Graphic> {
|
impl MultiplyAlpha for Table<Graphic> {
|
||||||
fn multiply_alpha(&mut self, factor: f64) {
|
fn multiply_alpha(&mut self, factor: f64) {
|
||||||
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING) {
|
multiply_table_attribute(self, ATTR_OPACITY, factor);
|
||||||
a.opacity *= factor as f32;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl MultiplyAlpha for Table<Raster<CPU>> {
|
impl MultiplyAlpha for Table<Raster<CPU>> {
|
||||||
fn multiply_alpha(&mut self, factor: f64) {
|
fn multiply_alpha(&mut self, factor: f64) {
|
||||||
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING) {
|
multiply_table_attribute(self, ATTR_OPACITY, factor);
|
||||||
a.opacity *= factor as f32;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl MultiplyAlpha for Table<Color> {
|
impl MultiplyAlpha for Table<Color> {
|
||||||
fn multiply_alpha(&mut self, factor: f64) {
|
fn multiply_alpha(&mut self, factor: f64) {
|
||||||
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING) {
|
multiply_table_attribute(self, ATTR_OPACITY, factor);
|
||||||
a.opacity *= factor as f32;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl MultiplyAlpha for Table<GradientStops> {
|
impl MultiplyAlpha for Table<GradientStops> {
|
||||||
fn multiply_alpha(&mut self, factor: f64) {
|
fn multiply_alpha(&mut self, factor: f64) {
|
||||||
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING) {
|
multiply_table_attribute(self, ATTR_OPACITY, factor);
|
||||||
a.opacity *= factor as f32;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -62,37 +64,27 @@ impl MultiplyFill for Color {
|
||||||
}
|
}
|
||||||
impl MultiplyFill for Table<Vector> {
|
impl MultiplyFill for Table<Vector> {
|
||||||
fn multiply_fill(&mut self, factor: f64) {
|
fn multiply_fill(&mut self, factor: f64) {
|
||||||
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING) {
|
multiply_table_attribute(self, ATTR_OPACITY_FILL, factor);
|
||||||
a.fill *= factor as f32;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl MultiplyFill for Table<Graphic> {
|
impl MultiplyFill for Table<Graphic> {
|
||||||
fn multiply_fill(&mut self, factor: f64) {
|
fn multiply_fill(&mut self, factor: f64) {
|
||||||
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING) {
|
multiply_table_attribute(self, ATTR_OPACITY_FILL, factor);
|
||||||
a.fill *= factor as f32;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl MultiplyFill for Table<Raster<CPU>> {
|
impl MultiplyFill for Table<Raster<CPU>> {
|
||||||
fn multiply_fill(&mut self, factor: f64) {
|
fn multiply_fill(&mut self, factor: f64) {
|
||||||
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING) {
|
multiply_table_attribute(self, ATTR_OPACITY_FILL, factor);
|
||||||
a.fill *= factor as f32;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl MultiplyFill for Table<Color> {
|
impl MultiplyFill for Table<Color> {
|
||||||
fn multiply_fill(&mut self, factor: f64) {
|
fn multiply_fill(&mut self, factor: f64) {
|
||||||
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING) {
|
multiply_table_attribute(self, ATTR_OPACITY_FILL, factor);
|
||||||
a.fill *= factor as f32;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl MultiplyFill for Table<GradientStops> {
|
impl MultiplyFill for Table<GradientStops> {
|
||||||
fn multiply_fill(&mut self, factor: f64) {
|
fn multiply_fill(&mut self, factor: f64) {
|
||||||
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING) {
|
multiply_table_attribute(self, ATTR_OPACITY_FILL, factor);
|
||||||
a.fill *= factor as f32;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -100,39 +92,35 @@ trait SetBlendMode {
|
||||||
fn set_blend_mode(&mut self, blend_mode: BlendMode);
|
fn set_blend_mode(&mut self, blend_mode: BlendMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_table_blend_mode<T>(table: &mut Table<T>, blend_mode: BlendMode) {
|
||||||
|
for v in table.iter_attribute_values_mut_or_default::<BlendMode>(ATTR_BLEND_MODE) {
|
||||||
|
*v = blend_mode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl SetBlendMode for Table<Vector> {
|
impl SetBlendMode for Table<Vector> {
|
||||||
fn set_blend_mode(&mut self, blend_mode: BlendMode) {
|
fn set_blend_mode(&mut self, blend_mode: BlendMode) {
|
||||||
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING) {
|
set_table_blend_mode(self, blend_mode);
|
||||||
a.blend_mode = blend_mode;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl SetBlendMode for Table<Graphic> {
|
impl SetBlendMode for Table<Graphic> {
|
||||||
fn set_blend_mode(&mut self, blend_mode: BlendMode) {
|
fn set_blend_mode(&mut self, blend_mode: BlendMode) {
|
||||||
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING) {
|
set_table_blend_mode(self, blend_mode);
|
||||||
a.blend_mode = blend_mode;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl SetBlendMode for Table<Raster<CPU>> {
|
impl SetBlendMode for Table<Raster<CPU>> {
|
||||||
fn set_blend_mode(&mut self, blend_mode: BlendMode) {
|
fn set_blend_mode(&mut self, blend_mode: BlendMode) {
|
||||||
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING) {
|
set_table_blend_mode(self, blend_mode);
|
||||||
a.blend_mode = blend_mode;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl SetBlendMode for Table<Color> {
|
impl SetBlendMode for Table<Color> {
|
||||||
fn set_blend_mode(&mut self, blend_mode: BlendMode) {
|
fn set_blend_mode(&mut self, blend_mode: BlendMode) {
|
||||||
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING) {
|
set_table_blend_mode(self, blend_mode);
|
||||||
a.blend_mode = blend_mode;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl SetBlendMode for Table<GradientStops> {
|
impl SetBlendMode for Table<GradientStops> {
|
||||||
fn set_blend_mode(&mut self, blend_mode: BlendMode) {
|
fn set_blend_mode(&mut self, blend_mode: BlendMode) {
|
||||||
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING) {
|
set_table_blend_mode(self, blend_mode);
|
||||||
a.blend_mode = blend_mode;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -140,39 +128,35 @@ trait SetClip {
|
||||||
fn set_clip(&mut self, clip: bool);
|
fn set_clip(&mut self, clip: bool);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_table_clip<T>(table: &mut Table<T>, clip: bool) {
|
||||||
|
for v in table.iter_attribute_values_mut_or_default::<bool>(ATTR_CLIPPING_MASK) {
|
||||||
|
*v = clip;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl SetClip for Table<Vector> {
|
impl SetClip for Table<Vector> {
|
||||||
fn set_clip(&mut self, clip: bool) {
|
fn set_clip(&mut self, clip: bool) {
|
||||||
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING) {
|
set_table_clip(self, clip);
|
||||||
a.clip = clip;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl SetClip for Table<Graphic> {
|
impl SetClip for Table<Graphic> {
|
||||||
fn set_clip(&mut self, clip: bool) {
|
fn set_clip(&mut self, clip: bool) {
|
||||||
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING) {
|
set_table_clip(self, clip);
|
||||||
a.clip = clip;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl SetClip for Table<Raster<CPU>> {
|
impl SetClip for Table<Raster<CPU>> {
|
||||||
fn set_clip(&mut self, clip: bool) {
|
fn set_clip(&mut self, clip: bool) {
|
||||||
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING) {
|
set_table_clip(self, clip);
|
||||||
a.clip = clip;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl SetClip for Table<Color> {
|
impl SetClip for Table<Color> {
|
||||||
fn set_clip(&mut self, clip: bool) {
|
fn set_clip(&mut self, clip: bool) {
|
||||||
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING) {
|
set_table_clip(self, clip);
|
||||||
a.clip = clip;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl SetClip for Table<GradientStops> {
|
impl SetClip for Table<GradientStops> {
|
||||||
fn set_clip(&mut self, clip: bool) {
|
fn set_clip(&mut self, clip: bool) {
|
||||||
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING) {
|
set_table_clip(self, clip);
|
||||||
a.clip = clip;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,8 @@ use core_types::table::{Table, TableRow};
|
||||||
use core_types::transform::Transform;
|
use core_types::transform::Transform;
|
||||||
use core_types::uuid::NodeId;
|
use core_types::uuid::NodeId;
|
||||||
use core_types::value::ClonedNode;
|
use core_types::value::ClonedNode;
|
||||||
use core_types::{ATTR_ALPHA_BLENDING, ATTR_EDITOR_LAYER_PATH, ATTR_TRANSFORM};
|
use core_types::{ATTR_BLEND_MODE, ATTR_CLIPPING_MASK, ATTR_EDITOR_LAYER_PATH, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_TRANSFORM};
|
||||||
use core_types::{AlphaBlending, Ctx, Node};
|
use core_types::{Ctx, Node};
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
use raster_nodes::blending_nodes::blend_colors;
|
use raster_nodes::blending_nodes::blend_colors;
|
||||||
use raster_nodes::std_nodes::{empty_image, extend_image_to_bounds};
|
use raster_nodes::std_nodes::{empty_image, extend_image_to_bounds};
|
||||||
|
|
@ -314,12 +314,18 @@ async fn brush(
|
||||||
}
|
}
|
||||||
|
|
||||||
let transform: DAffine2 = actual_image.attribute_cloned_or_default(ATTR_TRANSFORM);
|
let transform: DAffine2 = actual_image.attribute_cloned_or_default(ATTR_TRANSFORM);
|
||||||
let alpha_blending: AlphaBlending = actual_image.attribute_cloned_or_default(ATTR_ALPHA_BLENDING);
|
let blend_mode: BlendMode = actual_image.attribute_cloned_or_default(ATTR_BLEND_MODE);
|
||||||
|
let opacity: f64 = actual_image.attribute_cloned_or(ATTR_OPACITY, 1.);
|
||||||
|
let fill: f64 = actual_image.attribute_cloned_or(ATTR_OPACITY_FILL, 1.);
|
||||||
|
let clip: bool = actual_image.attribute_cloned_or_default(ATTR_CLIPPING_MASK);
|
||||||
let layer: Table<NodeId> = actual_image.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH);
|
let layer: Table<NodeId> = actual_image.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH);
|
||||||
|
|
||||||
*image.element_mut(0).unwrap() = actual_image.into_element();
|
*image.element_mut(0).unwrap() = actual_image.into_element();
|
||||||
image.set_attribute(ATTR_TRANSFORM, 0, transform);
|
image.set_attribute(ATTR_TRANSFORM, 0, transform);
|
||||||
image.set_attribute(ATTR_ALPHA_BLENDING, 0, alpha_blending);
|
image.set_attribute(ATTR_BLEND_MODE, 0, blend_mode);
|
||||||
|
image.set_attribute(ATTR_OPACITY, 0, opacity);
|
||||||
|
image.set_attribute(ATTR_OPACITY_FILL, 0, fill);
|
||||||
|
image.set_attribute(ATTR_CLIPPING_MASK, 0, clip);
|
||||||
image.set_attribute(ATTR_EDITOR_LAYER_PATH, 0, layer);
|
image.set_attribute(ATTR_EDITOR_LAYER_PATH, 0, layer);
|
||||||
|
|
||||||
image
|
image
|
||||||
|
|
@ -410,7 +416,7 @@ mod test {
|
||||||
let image = brush(
|
let image = brush(
|
||||||
(),
|
(),
|
||||||
Table::new_from_element(Raster::new_cpu(Image::<Color>::default())),
|
Table::new_from_element(Raster::new_cpu(Image::<Color>::default())),
|
||||||
vec![BrushStroke {
|
Table::new_from_element(BrushStroke {
|
||||||
trace: vec![crate::brush_stroke::BrushInputSample { position: DVec2::ZERO }],
|
trace: vec![crate::brush_stroke::BrushInputSample { position: DVec2::ZERO }],
|
||||||
style: BrushStyle {
|
style: BrushStyle {
|
||||||
color: Color::BLACK,
|
color: Color::BLACK,
|
||||||
|
|
@ -420,7 +426,7 @@ mod test {
|
||||||
spacing: 20.,
|
spacing: 20.,
|
||||||
blend_mode: BlendMode::Normal,
|
blend_mode: BlendMode::Normal,
|
||||||
},
|
},
|
||||||
}],
|
}),
|
||||||
BrushCache::default(),
|
BrushCache::default(),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use core_types::table::{Table, TableRow};
|
use core_types::table::{Table, TableRow};
|
||||||
use core_types::uuid::NodeId;
|
use core_types::uuid::NodeId;
|
||||||
use core_types::{ATTR_ALPHA_BLENDING, ATTR_EDITOR_LAYER_PATH, ATTR_EDITOR_MERGED_LAYERS, ATTR_TRANSFORM, AlphaBlending, Color, Ctx};
|
use core_types::{ATTR_BLEND_MODE, ATTR_CLIPPING_MASK, ATTR_EDITOR_LAYER_PATH, ATTR_EDITOR_MERGED_LAYERS, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_TRANSFORM, BlendMode, Color, Ctx};
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
use graphic_types::vector_types::subpath::{ManipulatorGroup, Subpath};
|
use graphic_types::vector_types::subpath::{ManipulatorGroup, Subpath};
|
||||||
use graphic_types::vector_types::vector::PointId;
|
use graphic_types::vector_types::vector::PointId;
|
||||||
|
|
@ -179,7 +179,7 @@ fn flatten_vector(graphic_table: &Table<Graphic>) -> Table<Vector> {
|
||||||
}
|
}
|
||||||
Graphic::RasterCPU(image) => {
|
Graphic::RasterCPU(image) => {
|
||||||
let parent_transform: DAffine2 = graphic_table.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
let parent_transform: DAffine2 = graphic_table.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||||
let make_row = |transform, layer, alpha_blending| {
|
let make_item = |transform, layer, blend_mode: BlendMode, opacity: f64, fill: f64, clip: bool| {
|
||||||
let mut subpath = Subpath::new_rectangle(DVec2::ZERO, DVec2::ONE);
|
let mut subpath = Subpath::new_rectangle(DVec2::ZERO, DVec2::ONE);
|
||||||
subpath.apply_transform(transform);
|
subpath.apply_transform(transform);
|
||||||
|
|
||||||
|
|
@ -187,7 +187,10 @@ fn flatten_vector(graphic_table: &Table<Graphic>) -> Table<Vector> {
|
||||||
element.style.set_fill(Fill::Solid(Color::BLACK));
|
element.style.set_fill(Fill::Solid(Color::BLACK));
|
||||||
|
|
||||||
TableRow::new_from_element(element)
|
TableRow::new_from_element(element)
|
||||||
.with_attribute(ATTR_ALPHA_BLENDING, alpha_blending)
|
.with_attribute(ATTR_BLEND_MODE, blend_mode)
|
||||||
|
.with_attribute(ATTR_OPACITY, opacity)
|
||||||
|
.with_attribute(ATTR_OPACITY_FILL, fill)
|
||||||
|
.with_attribute(ATTR_CLIPPING_MASK, clip)
|
||||||
.with_attribute(ATTR_EDITOR_LAYER_PATH, layer)
|
.with_attribute(ATTR_EDITOR_LAYER_PATH, layer)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -198,14 +201,17 @@ fn flatten_vector(graphic_table: &Table<Graphic>) -> Table<Vector> {
|
||||||
.map(|i| {
|
.map(|i| {
|
||||||
let row_transform: DAffine2 = image.attribute_cloned_or_default(ATTR_TRANSFORM, i);
|
let row_transform: DAffine2 = image.attribute_cloned_or_default(ATTR_TRANSFORM, i);
|
||||||
let layer: Table<NodeId> = image.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, i);
|
let layer: Table<NodeId> = image.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, i);
|
||||||
let alpha_blending: AlphaBlending = image.attribute_cloned_or_default(ATTR_ALPHA_BLENDING, i);
|
let blend_mode: BlendMode = image.attribute_cloned_or_default(ATTR_BLEND_MODE, i);
|
||||||
make_row(parent_transform * row_transform, layer, alpha_blending)
|
let opacity: f64 = image.attribute_cloned_or(ATTR_OPACITY, i, 1.);
|
||||||
|
let fill: f64 = image.attribute_cloned_or(ATTR_OPACITY_FILL, i, 1.);
|
||||||
|
let clip: bool = image.attribute_cloned_or_default(ATTR_CLIPPING_MASK, i);
|
||||||
|
make_item(parent_transform * row_transform, layer, blend_mode, opacity, fill, clip)
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
}
|
}
|
||||||
Graphic::RasterGPU(image) => {
|
Graphic::RasterGPU(image) => {
|
||||||
let parent_transform: DAffine2 = graphic_table.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
let parent_transform: DAffine2 = graphic_table.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||||
let make_row = |transform, layer, alpha_blending| {
|
let make_item = |transform, layer, blend_mode: BlendMode, opacity: f64, fill: f64, clip: bool| {
|
||||||
let mut subpath = Subpath::new_rectangle(DVec2::ZERO, DVec2::ONE);
|
let mut subpath = Subpath::new_rectangle(DVec2::ZERO, DVec2::ONE);
|
||||||
subpath.apply_transform(transform);
|
subpath.apply_transform(transform);
|
||||||
|
|
||||||
|
|
@ -213,7 +219,10 @@ fn flatten_vector(graphic_table: &Table<Graphic>) -> Table<Vector> {
|
||||||
element.style.set_fill(Fill::Solid(Color::BLACK));
|
element.style.set_fill(Fill::Solid(Color::BLACK));
|
||||||
|
|
||||||
TableRow::new_from_element(element)
|
TableRow::new_from_element(element)
|
||||||
.with_attribute(ATTR_ALPHA_BLENDING, alpha_blending)
|
.with_attribute(ATTR_BLEND_MODE, blend_mode)
|
||||||
|
.with_attribute(ATTR_OPACITY, opacity)
|
||||||
|
.with_attribute(ATTR_OPACITY_FILL, fill)
|
||||||
|
.with_attribute(ATTR_CLIPPING_MASK, clip)
|
||||||
.with_attribute(ATTR_EDITOR_LAYER_PATH, layer)
|
.with_attribute(ATTR_EDITOR_LAYER_PATH, layer)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -224,8 +233,11 @@ fn flatten_vector(graphic_table: &Table<Graphic>) -> Table<Vector> {
|
||||||
.map(|i| {
|
.map(|i| {
|
||||||
let row_transform: DAffine2 = image.attribute_cloned_or_default(ATTR_TRANSFORM, i);
|
let row_transform: DAffine2 = image.attribute_cloned_or_default(ATTR_TRANSFORM, i);
|
||||||
let layer: Table<NodeId> = image.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, i);
|
let layer: Table<NodeId> = image.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, i);
|
||||||
let alpha_blending: AlphaBlending = image.attribute_cloned_or_default(ATTR_ALPHA_BLENDING, i);
|
let blend_mode: BlendMode = image.attribute_cloned_or_default(ATTR_BLEND_MODE, i);
|
||||||
make_row(parent_transform * row_transform, layer, alpha_blending)
|
let opacity: f64 = image.attribute_cloned_or(ATTR_OPACITY, i, 1.);
|
||||||
|
let fill: f64 = image.attribute_cloned_or(ATTR_OPACITY_FILL, i, 1.);
|
||||||
|
let clip: bool = image.attribute_cloned_or_default(ATTR_CLIPPING_MASK, i);
|
||||||
|
make_item(parent_transform * row_transform, layer, blend_mode, opacity, fill, clip)
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
use crate::adjustments::{CellularDistanceFunction, CellularReturnType, DomainWarpType, FractalType, NoiseType};
|
use crate::adjustments::{CellularDistanceFunction, CellularReturnType, DomainWarpType, FractalType, NoiseType};
|
||||||
use core_types::blending::AlphaBlending;
|
use core_types::ATTR_TRANSFORM;
|
||||||
use core_types::color::Color;
|
use core_types::color::Color;
|
||||||
use core_types::color::{Alpha, AlphaMut, Channel, LinearChannel, Luminance, RGBMut};
|
use core_types::color::{Alpha, AlphaMut, Channel, LinearChannel, Luminance, RGBMut};
|
||||||
use core_types::context::{Ctx, ExtractFootprint};
|
use core_types::context::{Ctx, ExtractFootprint};
|
||||||
use core_types::math::bbox::Bbox;
|
use core_types::math::bbox::Bbox;
|
||||||
use core_types::table::{Table, TableRow};
|
use core_types::table::{Table, TableRow};
|
||||||
use core_types::transform::Transform;
|
use core_types::transform::Transform;
|
||||||
use core_types::{ATTR_ALPHA_BLENDING, ATTR_TRANSFORM};
|
|
||||||
use dyn_any::DynAny;
|
use dyn_any::DynAny;
|
||||||
use fastnoise_lite;
|
use fastnoise_lite;
|
||||||
use glam::{DAffine2, DVec2, Vec2};
|
use glam::{DAffine2, DVec2, Vec2};
|
||||||
|
|
@ -284,7 +283,6 @@ pub fn empty_image(_: impl Ctx, transform: DAffine2, color: Table<Color>) -> Tab
|
||||||
|
|
||||||
let mut result_table = Table::new_from_element(Raster::new_cpu(image));
|
let mut result_table = Table::new_from_element(Raster::new_cpu(image));
|
||||||
result_table.set_attribute(ATTR_TRANSFORM, 0, transform);
|
result_table.set_attribute(ATTR_TRANSFORM, 0, transform);
|
||||||
result_table.set_attribute(ATTR_ALPHA_BLENDING, 0, AlphaBlending::default());
|
|
||||||
|
|
||||||
// Callers of empty_image can safely unwrap on returned `Table`
|
// Callers of empty_image can safely unwrap on returned `Table`
|
||||||
result_table
|
result_table
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,16 @@
|
||||||
use core::cmp::Ordering;
|
use core::cmp::Ordering;
|
||||||
use core::f64::consts::{PI, TAU};
|
use core::f64::consts::{PI, TAU};
|
||||||
use core::hash::{Hash, Hasher};
|
use core::hash::{Hash, Hasher};
|
||||||
use core_types::AlphaBlending;
|
use core_types::blending::BlendMode;
|
||||||
use core_types::bounds::{BoundingBox, RenderBoundingBox};
|
use core_types::bounds::{BoundingBox, RenderBoundingBox};
|
||||||
use core_types::registry::types::{Angle, Length, Multiplier, Percentage, PixelLength, Progression, SeedValue};
|
use core_types::registry::types::{Angle, Length, Multiplier, Percentage, PixelLength, Progression, SeedValue};
|
||||||
use core_types::table::{Table, TableRow};
|
use core_types::table::{Table, TableRow};
|
||||||
use core_types::transform::{Footprint, Transform};
|
use core_types::transform::{Footprint, Transform};
|
||||||
use core_types::uuid::NodeId;
|
use core_types::uuid::NodeId;
|
||||||
use core_types::{ATTR_ALPHA_BLENDING, ATTR_EDITOR_LAYER_PATH, ATTR_EDITOR_MERGED_LAYERS, ATTR_TRANSFORM, CloneVarArgs, Color, Context, Ctx, ExtractAll, OwnedContextImpl};
|
use core_types::{
|
||||||
|
ATTR_BLEND_MODE, ATTR_CLIPPING_MASK, ATTR_EDITOR_LAYER_PATH, ATTR_EDITOR_MERGED_LAYERS, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_TRANSFORM, CloneVarArgs, Color, Context, Ctx, ExtractAll,
|
||||||
|
OwnedContextImpl,
|
||||||
|
};
|
||||||
use glam::{DAffine2, DMat2, DVec2};
|
use glam::{DAffine2, DMat2, DVec2};
|
||||||
use graphic_types::Vector;
|
use graphic_types::Vector;
|
||||||
use graphic_types::raster_types::{CPU, GPU, Raster};
|
use graphic_types::raster_types::{CPU, GPU, Raster};
|
||||||
|
|
@ -2310,10 +2313,20 @@ async fn morph<I: IntoGraphicTable + 'n + Send + Clone>(
|
||||||
return content;
|
return content;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Lerp styles
|
// Lerp blending attributes: opacity/fill interpolate, blend_mode/clip step at the midpoint
|
||||||
let source_alpha_blending: AlphaBlending = content.attribute_cloned_or_default(ATTR_ALPHA_BLENDING, source_index);
|
let source_blend_mode: BlendMode = content.attribute_cloned_or_default(ATTR_BLEND_MODE, source_index);
|
||||||
let target_alpha_blending: AlphaBlending = content.attribute_cloned_or_default(ATTR_ALPHA_BLENDING, target_index);
|
let target_blend_mode: BlendMode = content.attribute_cloned_or_default(ATTR_BLEND_MODE, target_index);
|
||||||
let vector_alpha_blending = source_alpha_blending.lerp(&target_alpha_blending, time as f32);
|
let source_opacity: f64 = content.attribute_cloned_or(ATTR_OPACITY, source_index, 1.);
|
||||||
|
let target_opacity: f64 = content.attribute_cloned_or(ATTR_OPACITY, target_index, 1.);
|
||||||
|
let source_fill: f64 = content.attribute_cloned_or(ATTR_OPACITY_FILL, source_index, 1.);
|
||||||
|
let target_fill: f64 = content.attribute_cloned_or(ATTR_OPACITY_FILL, target_index, 1.);
|
||||||
|
let source_clip: bool = content.attribute_cloned_or_default(ATTR_CLIPPING_MASK, source_index);
|
||||||
|
let target_clip: bool = content.attribute_cloned_or_default(ATTR_CLIPPING_MASK, target_index);
|
||||||
|
|
||||||
|
let lerped_blend_mode = if time < 0.5 { source_blend_mode } else { target_blend_mode };
|
||||||
|
let lerped_opacity = source_opacity + (target_opacity - source_opacity) * time;
|
||||||
|
let lerped_fill = source_fill + (target_fill - source_fill) * time;
|
||||||
|
let lerped_clip = if time < 0.5 { source_clip } else { target_clip };
|
||||||
|
|
||||||
// Evaluate the spatial position on the control path for the translation component.
|
// Evaluate the spatial position on the control path for the translation component.
|
||||||
// When the segment has zero arc length (e.g., two objects at the same position), inv_arclen
|
// When the segment has zero arc length (e.g., two objects at the same position), inv_arclen
|
||||||
|
|
@ -2534,7 +2547,10 @@ async fn morph<I: IntoGraphicTable + 'n + Send + Clone>(
|
||||||
Table::new_from_row(
|
Table::new_from_row(
|
||||||
TableRow::new_from_element(vector)
|
TableRow::new_from_element(vector)
|
||||||
.with_attribute(ATTR_TRANSFORM, lerped_transform)
|
.with_attribute(ATTR_TRANSFORM, lerped_transform)
|
||||||
.with_attribute(ATTR_ALPHA_BLENDING, vector_alpha_blending)
|
.with_attribute(ATTR_BLEND_MODE, lerped_blend_mode)
|
||||||
|
.with_attribute(ATTR_OPACITY, lerped_opacity)
|
||||||
|
.with_attribute(ATTR_OPACITY_FILL, lerped_fill)
|
||||||
|
.with_attribute(ATTR_CLIPPING_MASK, lerped_clip)
|
||||||
.with_attribute(ATTR_EDITOR_LAYER_PATH, layer_path)
|
.with_attribute(ATTR_EDITOR_LAYER_PATH, layer_path)
|
||||||
.with_attribute(ATTR_EDITOR_MERGED_LAYERS, graphic_table_content),
|
.with_attribute(ATTR_EDITOR_MERGED_LAYERS, graphic_table_content),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue