mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
Replace the AlphaBlending struct with separate attributes (#4086)
* Replace the AlphaBlending struct with separate attributes * Fix bug * Fix bug
This commit is contained in:
@@ -34,8 +34,8 @@ use std::any::TypeId;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
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_START, ATTR_TRANSFORM, ATTR_TYPE,
|
||||
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_OPACITY, ATTR_OPACITY_FILL, ATTR_SPREAD_METHOD, ATTR_START, ATTR_TRANSFORM, ATTR_TYPE,
|
||||
};
|
||||
#[cfg(feature = "wasm")]
|
||||
pub use tsify;
|
||||
|
||||
@@ -10,59 +10,60 @@ use std::fmt::Debug;
|
||||
// Standard attribute keys used across the data flow
|
||||
// =====================================================================
|
||||
|
||||
/// Attribute key for a row's `DAffine2` transformation, applied when rendering and when accumulating
|
||||
/// transforms through nested compositions.
|
||||
/// Row's `DAffine2` transformation, composed multiplicatively through nested groups.
|
||||
pub const ATTR_TRANSFORM: &str = "transform";
|
||||
|
||||
/// Attribute key for a row's `AlphaBlending` (blend mode + opacity + fill + clip), composed
|
||||
/// multiplicatively through nested compositions.
|
||||
pub const ATTR_ALPHA_BLENDING: &str = "alpha_blending";
|
||||
/// Row's `BlendMode`, controlling how it composites with content beneath it.
|
||||
pub const ATTR_BLEND_MODE: &str = "blend_mode";
|
||||
|
||||
/// Attribute key under which each row of an editor-aware layer stores a `Table<NodeId>` describing the
|
||||
/// path (from the root document network) to the layer node that owns the row. Editor tools use this to
|
||||
/// route clicks/selection back to the originating layer at any nesting depth.
|
||||
/// Row's opacity multiplier (`f64`, implicit default `1.`).
|
||||
/// Composed multiplicatively through nested groups. Affects content clipped to the row.
|
||||
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";
|
||||
|
||||
/// Attribute key under which a row stores a `Table<Graphic>` snapshot of the upstream content that fed
|
||||
/// into a destructive merge (Boolean Operation, Flatten Path, Morph, Rasterize, etc.). The renderer
|
||||
/// recurses into this snapshot during metadata collection so the editor can still surface click targets
|
||||
/// for the original child layers after their content has been collapsed into a single output.
|
||||
/// `Table<Graphic>` snapshot of the upstream content that fed into a destructive merge
|
||||
/// (Boolean Operation, Rasterize, etc.), so the editor can still surface click targets for
|
||||
/// the original child layers after their content has been collapsed.
|
||||
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
|
||||
/// `regex_find_all` and `regex_capture` text nodes.
|
||||
/// Byte offset where a regex match begins ('Regex Find All', 'Regex Capture' text nodes).
|
||||
pub const ATTR_START: &str = "start";
|
||||
|
||||
/// Attribute key for the byte offset where a regex match ends in the input string, set by the
|
||||
/// `regex_find_all` and `regex_capture` text nodes.
|
||||
/// Byte offset where a regex match ends ('Regex Find All', 'Regex Capture' text nodes).
|
||||
pub const ATTR_END: &str = "end";
|
||||
|
||||
/// Attribute key for a regex named-capture-group's name (empty for unnamed groups), set by the
|
||||
/// `regex_capture` text node.
|
||||
/// Regex named-capture-group's name, or empty for unnamed groups ('Regex Capture' text node).
|
||||
pub const ATTR_NAME: &str = "name";
|
||||
|
||||
/// Attribute key for a JSON value's type (`"string"`, `"number"`, `"object"`, etc.), set by the
|
||||
/// `json_query_all` text node alongside each extracted value.
|
||||
/// JSON value's type string (`"string"`, `"number"`, `"object"`, etc.) from 'JSON Query All'.
|
||||
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";
|
||||
|
||||
/// Attribute key for an artboard row's `DVec2` width and height.
|
||||
/// Artboard's `DVec2` width and height.
|
||||
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";
|
||||
|
||||
/// 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";
|
||||
|
||||
/// Attribute key for a `Table<GradientStops>` row's `GradientSpreadMethod`, controlling the gradient's behavior
|
||||
/// outside the start/end stops (`Pad` clamps to the boundary colors, `Reflect` mirrors, `Repeat` tiles).
|
||||
/// Gradient's `GradientSpreadMethod` (`Pad`, `Reflect`, or `Repeat`).
|
||||
pub const ATTR_SPREAD_METHOD: &str = "spread_method";
|
||||
|
||||
/// Attribute key for a `Table<GradientStops>` row's `GradientType`, choosing between a linear gradient (color
|
||||
/// transitions along the gradient line) or a radial gradient (color transitions outward from the line's start).
|
||||
/// Gradient's `GradientType` (`Linear` or `Radial`).
|
||||
pub const ATTR_GRADIENT_TYPE: &str = "gradient_type";
|
||||
|
||||
// =====================
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::graphic::Graphic;
|
||||
use core_types::blending::AlphaBlending;
|
||||
use core_types::blending::BlendMode;
|
||||
use core_types::table::{Table, TableRow};
|
||||
use core_types::uuid::NodeId;
|
||||
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> {
|
||||
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
|
||||
/// 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).
|
||||
@@ -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"))]
|
||||
element: Vec<T>,
|
||||
transform: Vec<DAffine2>,
|
||||
alpha_blending: Vec<AlphaBlending>,
|
||||
alpha_blending: Vec<LegacyAlphaBlending>,
|
||||
}
|
||||
|
||||
#[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::graphene_hash::CacheHash;
|
||||
use core_types::ops::TableConvert;
|
||||
use core_types::render_complexity::RenderComplexity;
|
||||
use core_types::table::{Table, TableRow};
|
||||
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 glam::DAffine2;
|
||||
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`)
|
||||
/// 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 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>>) {
|
||||
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 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() {
|
||||
// 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) => {
|
||||
for index in 0..sub_table.len() {
|
||||
let child_transform: DAffine2 = sub_table.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
let child_alpha_blending: AlphaBlending = sub_table.attribute_cloned_or_default(ATTR_ALPHA_BLENDING, index);
|
||||
// Identity default means a missing column still composes correctly
|
||||
for v in sub_table.iter_attribute_values_mut_or_default::<DAffine2>(ATTR_TRANSFORM) {
|
||||
*v = current_transform * *v;
|
||||
}
|
||||
|
||||
sub_table.set_attribute(ATTR_TRANSFORM, index, current_transform * child_transform);
|
||||
sub_table.set_attribute(ATTR_ALPHA_BLENDING, index, compose_alpha_blending(current_alpha_blending, child_alpha_blending));
|
||||
// f64 defaults to 0, but opacity/fill default to 1, so missing columns must be set rather than multiplied
|
||||
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);
|
||||
}
|
||||
// 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 => {
|
||||
if let Some(typed_table) = extract_variant(other) {
|
||||
for row in typed_table.into_iter() {
|
||||
let row_transform: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM);
|
||||
let row_alpha_blending: AlphaBlending = row.attribute_cloned_or_default(ATTR_ALPHA_BLENDING);
|
||||
let (element, mut attributes) = row.into_parts();
|
||||
for mut item in typed_table.into_iter() {
|
||||
let row_transform: DAffine2 = item.attribute_cloned_or_default(ATTR_TRANSFORM);
|
||||
let row_opacity: f64 = item.attribute_cloned_or(ATTR_OPACITY, 1.);
|
||||
let row_fill: f64 = item.attribute_cloned_or(ATTR_OPACITY_FILL, 1.);
|
||||
|
||||
attributes.insert(ATTR_TRANSFORM, current_transform * row_transform);
|
||||
attributes.insert(ATTR_ALPHA_BLENDING, compose_alpha_blending(current_alpha_blending, row_alpha_blending));
|
||||
attributes.insert(ATTR_EDITOR_LAYER_PATH, layer_path.clone());
|
||||
item.set_attribute(ATTR_TRANSFORM, current_transform * row_transform);
|
||||
item.set_attribute(ATTR_OPACITY, current_opacity * row_opacity);
|
||||
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 {
|
||||
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 {
|
||||
Graphic::Vector(table) => all_clipped(table),
|
||||
Graphic::Graphic(table) => all_clipped(table),
|
||||
@@ -335,12 +346,11 @@ impl Graphic {
|
||||
|
||||
pub fn can_reduce_to_clip_path(&self) -> bool {
|
||||
match self {
|
||||
Graphic::Vector(vector) => vector
|
||||
.iter_element_values()
|
||||
.zip(vector.iter_attribute_values_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING))
|
||||
.all(|(element, alpha_blending)| {
|
||||
(alpha_blending.opacity > 1. - f32::EPSILON) && element.style.fill().is_opaque() && element.style.stroke().is_none_or(|stroke| !stroke.has_renderable_stroke())
|
||||
}),
|
||||
Graphic::Vector(vector) => (0..vector.len()).all(|index| {
|
||||
let Some(element) = vector.element(index) else { return false };
|
||||
let opacity: f64 = vector.attribute_cloned_or(ATTR_OPACITY, index, 1.);
|
||||
opacity > 1. - f64::EPSILON && element.style.fill().is_opaque() && element.style.stroke().is_none_or(|stroke| !stroke.has_renderable_stroke())
|
||||
}),
|
||||
_ => 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> {
|
||||
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)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct OldGraphicGroup {
|
||||
elements: Vec<(Graphic, Option<NodeId>)>,
|
||||
transform: DAffine2,
|
||||
alpha_blending: AlphaBlending,
|
||||
alpha_blending: LegacyAlphaBlending,
|
||||
}
|
||||
#[derive(Clone, Debug, PartialEq, DynAny, Default)]
|
||||
#[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"))]
|
||||
element: Vec<T>,
|
||||
transform: Vec<DAffine2>,
|
||||
alpha_blending: Vec<AlphaBlending>,
|
||||
alpha_blending: Vec<LegacyAlphaBlending>,
|
||||
}
|
||||
|
||||
#[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 mod migrations {
|
||||
use core_types::{
|
||||
AlphaBlending,
|
||||
table::{Table, TableRow},
|
||||
};
|
||||
use core_types::blending::BlendMode;
|
||||
use core_types::table::{Table, TableRow};
|
||||
use dyn_any::DynAny;
|
||||
use glam::DAffine2;
|
||||
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> {
|
||||
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)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct OldVectorData {
|
||||
pub transform: DAffine2,
|
||||
pub alpha_blending: AlphaBlending,
|
||||
pub alpha_blending: LegacyAlphaBlending,
|
||||
|
||||
pub style: PathStyle,
|
||||
|
||||
@@ -47,7 +56,7 @@ pub mod migrations {
|
||||
#[cfg_attr(feature = "serde", serde(alias = "instances", alias = "instance"))]
|
||||
element: Vec<T>,
|
||||
transform: Vec<DAffine2>,
|
||||
alpha_blending: Vec<AlphaBlending>,
|
||||
alpha_blending: Vec<LegacyAlphaBlending>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
||||
@@ -1,63 +1,6 @@
|
||||
use core::fmt::Display;
|
||||
use node_macro::BufferStruct;
|
||||
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)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use crate::raster_types::{CPU, Raster};
|
||||
use crate::{Bitmap, BitmapMut};
|
||||
use core_types::blending::BlendMode;
|
||||
use core_types::color::float_to_srgb_u8;
|
||||
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 core_types::color::*;
|
||||
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> {
|
||||
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)]
|
||||
enum RasterFrame {
|
||||
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> {
|
||||
image: Image<P>,
|
||||
transform: DAffine2,
|
||||
alpha_blending: AlphaBlending,
|
||||
alpha_blending: LegacyAlphaBlending,
|
||||
}
|
||||
|
||||
#[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"))]
|
||||
element: Vec<T>,
|
||||
transform: Vec<DAffine2>,
|
||||
alpha_blending: Vec<AlphaBlending>,
|
||||
alpha_blending: Vec<LegacyAlphaBlending>,
|
||||
}
|
||||
|
||||
#[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 }) => {
|
||||
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_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
|
||||
}
|
||||
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> {
|
||||
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)]
|
||||
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
|
||||
@@ -416,7 +442,7 @@ pub fn migrate_image_frame_row<'de, D: serde::Deserializer<'de>>(deserializer: D
|
||||
pub struct OldImageFrame<P: Pixel> {
|
||||
image: Image<P>,
|
||||
transform: DAffine2,
|
||||
alpha_blending: AlphaBlending,
|
||||
alpha_blending: LegacyAlphaBlending,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use crate::render_ext::RenderExt;
|
||||
use crate::to_peniko::BlendModeExt;
|
||||
use core_types::AlphaBlending;
|
||||
use core_types::CacheHash;
|
||||
use core_types::blending::BlendMode;
|
||||
use core_types::bounds::{BoundingBox, RenderBoundingBox};
|
||||
@@ -11,7 +10,8 @@ use core_types::table::{Table, TableRow};
|
||||
use core_types::transform::Footprint;
|
||||
use core_types::uuid::{NodeId, generate_uuid};
|
||||
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 glam::{DAffine2, DVec2};
|
||||
@@ -686,7 +686,9 @@ impl Render for Table<Graphic> {
|
||||
|
||||
for index in 0..self.len() {
|
||||
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();
|
||||
|
||||
render.parent_tag(
|
||||
@@ -697,13 +699,13 @@ impl Render for Table<Graphic> {
|
||||
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. {
|
||||
attributes.push("opacity", opacity.to_string());
|
||||
}
|
||||
|
||||
if alpha_blending.blend_mode != BlendMode::default() {
|
||||
attributes.push("style", alpha_blending.blend_mode.render());
|
||||
if blend_mode != BlendMode::default() {
|
||||
attributes.push("style", blend_mode.render());
|
||||
}
|
||||
|
||||
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() {
|
||||
let row_transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
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 mut layer = false;
|
||||
|
||||
let blend_mode = match render_params.render_mode {
|
||||
RenderMode::Outline => peniko::Mix::Normal,
|
||||
_ => alpha_blending.blend_mode.to_peniko(),
|
||||
_ => blend_mode_attr.to_peniko(),
|
||||
};
|
||||
let mut bounds = RenderBoundingBox::None;
|
||||
|
||||
let opacity = alpha_blending.opacity(render_params.for_mask);
|
||||
if opacity < 1. || (render_params.render_mode != RenderMode::Outline && alpha_blending.blend_mode != BlendMode::default()) {
|
||||
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 && blend_mode_attr != BlendMode::default()) {
|
||||
bounds = element.bounding_box(transform, true);
|
||||
|
||||
if let RenderBoundingBox::Rectangle(bounds) = bounds {
|
||||
@@ -882,7 +886,10 @@ impl Render for Table<Vector> {
|
||||
for index in 0..self.len() {
|
||||
let Some(vector) = self.element(index) else { continue };
|
||||
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
|
||||
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(
|
||||
TableRow::new_from_element(cloned_vector)
|
||||
.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)
|
||||
@@ -1034,13 +1044,13 @@ impl Render for Table<Vector> {
|
||||
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. {
|
||||
attributes.push("opacity", opacity.to_string());
|
||||
}
|
||||
|
||||
if alpha_blending.blend_mode != BlendMode::default() {
|
||||
attributes.push("style", alpha_blending.blend_mode.render());
|
||||
if blend_mode_attr != BlendMode::default() {
|
||||
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 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 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.);
|
||||
@@ -1105,12 +1118,12 @@ impl Render for Table<Vector> {
|
||||
// If we're using opacity or a blend mode, we need to push a layer
|
||||
let blend_mode = match render_params.render_mode {
|
||||
RenderMode::Outline => peniko::Mix::Normal,
|
||||
_ => alpha_blending.blend_mode.to_peniko(),
|
||||
_ => blend_mode_attr.to_peniko(),
|
||||
};
|
||||
let mut layer = false;
|
||||
|
||||
let opacity = alpha_blending.opacity(render_params.for_mask);
|
||||
if opacity < 1. || alpha_blending.blend_mode != BlendMode::default() {
|
||||
let opacity = (opacity_attr * if render_params.for_mask { 1. } else { opacity_fill_attr }) as f32;
|
||||
if opacity < 1. || blend_mode_attr != BlendMode::default() {
|
||||
layer = true;
|
||||
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));
|
||||
@@ -1264,7 +1277,10 @@ impl Render for Table<Vector> {
|
||||
let vector_table = Table::new_from_row(
|
||||
TableRow::new_from_element(cloned_element)
|
||||
.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);
|
||||
@@ -1442,7 +1458,9 @@ impl Render for Table<Raster<CPU>> {
|
||||
let Some(image) = self.element(index) else { continue };
|
||||
|
||||
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() {
|
||||
continue;
|
||||
@@ -1469,13 +1487,13 @@ impl Render for Table<Raster<CPU>> {
|
||||
attributes.push("width", size.x.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. {
|
||||
attributes.push("opacity", opacity.to_string());
|
||||
}
|
||||
|
||||
if alpha_blending.blend_mode != BlendMode::default() {
|
||||
attributes.push("style", alpha_blending.blend_mode.render());
|
||||
if blend_mode_attr != BlendMode::default() {
|
||||
attributes.push("style", blend_mode_attr.render());
|
||||
}
|
||||
},
|
||||
|render| {
|
||||
@@ -1509,12 +1527,12 @@ impl Render for Table<Raster<CPU>> {
|
||||
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. {
|
||||
attributes.push("opacity", opacity.to_string());
|
||||
}
|
||||
if alpha_blending.blend_mode != BlendMode::default() {
|
||||
attributes.push("style", alpha_blending.blend_mode.render());
|
||||
if blend_mode_attr != BlendMode::default() {
|
||||
attributes.push("style", blend_mode_attr.render());
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1528,13 +1546,15 @@ impl Render for Table<Raster<CPU>> {
|
||||
continue;
|
||||
}
|
||||
|
||||
let alpha_blending: AlphaBlending = self.attribute_cloned_or_default(ATTR_ALPHA_BLENDING, index);
|
||||
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_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;
|
||||
|
||||
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 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) {
|
||||
for index in 0..self.len() {
|
||||
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 {
|
||||
RenderMode::Outline => peniko::Mix::Normal,
|
||||
_ => alpha_blending.blend_mode.to_peniko(),
|
||||
_ => blend_mode_attr.to_peniko(),
|
||||
};
|
||||
|
||||
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 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);
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1703,7 +1728,10 @@ impl Render for Table<Raster<GPU>> {
|
||||
// later replace with the current viewport transform before each render.
|
||||
impl Render for Table<Color> {
|
||||
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
|
||||
for (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| {
|
||||
// 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.
|
||||
@@ -1716,13 +1744,13 @@ impl Render for Table<Color> {
|
||||
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. {
|
||||
attributes.push("opacity", opacity.to_string());
|
||||
}
|
||||
|
||||
if alpha_blending.blend_mode != BlendMode::default() {
|
||||
attributes.push("style", alpha_blending.blend_mode.render());
|
||||
if blend_mode != BlendMode::default() {
|
||||
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) {
|
||||
use vello::peniko;
|
||||
|
||||
for (color, alpha_blending) in self.iter_element_values().zip(self.iter_attribute_values_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING)) {
|
||||
let blend_mode = alpha_blending.blend_mode.to_peniko();
|
||||
let opacity = alpha_blending.opacity(render_params.for_mask);
|
||||
for (index, color) in self.iter_element_values().enumerate() {
|
||||
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 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 rect = kurbo::Rect::from_origin_size(kurbo::Point::ZERO, kurbo::Size::new(1., 1.));
|
||||
|
||||
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);
|
||||
scene.push_layer(peniko::Fill::NonZero, blending, opacity, kurbo::Affine::scale(f64::INFINITY), &rect);
|
||||
layer = true;
|
||||
@@ -1770,7 +1801,9 @@ impl Render for Table<GradientStops> {
|
||||
for index in 0..self.len() {
|
||||
let Some(gradient) = self.element(index) else { continue };
|
||||
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 gradient_type: GradientType = self.attribute_cloned_or_default(ATTR_GRADIENT_TYPE, index);
|
||||
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}')"));
|
||||
|
||||
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. {
|
||||
attributes.push("opacity", opacity.to_string());
|
||||
}
|
||||
|
||||
if alpha_blending.blend_mode != BlendMode::default() {
|
||||
attributes.push("style", alpha_blending.blend_mode.render());
|
||||
if blend_mode != BlendMode::default() {
|
||||
attributes.push("style", blend_mode.render());
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1853,17 +1886,20 @@ impl Render for Table<GradientStops> {
|
||||
return;
|
||||
}
|
||||
|
||||
for ((((gradient, transform), alpha_blending), spread_method), gradient_type) in self
|
||||
for (((index, gradient), spread_method), gradient_type) in self
|
||||
.iter_element_values()
|
||||
.zip(self.iter_attribute_values_or_default::<DAffine2>(ATTR_TRANSFORM))
|
||||
.zip(self.iter_attribute_values_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING))
|
||||
.enumerate()
|
||||
.zip(self.iter_attribute_values_or_default::<GradientSpreadMethod>(ATTR_SPREAD_METHOD))
|
||||
.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 blend_mode = alpha_blending.blend_mode.to_peniko();
|
||||
let opacity = alpha_blending.opacity(render_params.for_mask);
|
||||
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 mut stops: peniko::ColorStops = peniko::ColorStops::new();
|
||||
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 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);
|
||||
// See implementation in `Table<Color>` for more detail
|
||||
scene.push_layer(peniko::Fill::NonZero, blending, opacity, kurbo::Affine::scale(f64::INFINITY), &rect);
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
//! Contains stylistic options for SVG elements.
|
||||
|
||||
pub use crate::gradient::*;
|
||||
use core_types::ATTR_ALPHA_BLENDING;
|
||||
use core_types::AlphaBlending;
|
||||
use core_types::ATTR_OPACITY;
|
||||
use core_types::Color;
|
||||
use core_types::color::Alpha;
|
||||
use core_types::table::Table;
|
||||
@@ -136,9 +135,9 @@ impl From<Option<Color>> for Fill {
|
||||
|
||||
impl From<Table<Color>> for 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();
|
||||
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)))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user