Wrap opacity/blend_mode in alpha_blending struct for graphic elements

This commit is contained in:
Keavon Chambers 2023-12-08 20:15:37 -08:00
parent 10f2fa92e5
commit e459e599b4
15 changed files with 111 additions and 96 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -577,11 +577,7 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
responses.add(DocumentMessage::StartTransaction); responses.add(DocumentMessage::StartTransaction);
let image_frame = ImageFrame { let image_frame = ImageFrame { image, ..Default::default() };
image,
transform: DAffine2::IDENTITY,
blend_mode: BlendMode::Normal,
};
use crate::messages::tool::common_functionality::graph_modification_utils; use crate::messages::tool::common_functionality::graph_modification_utils;
let layer = graph_modification_utils::new_image_layer(image_frame, generate_uuid(), self.new_layer_parent(), responses); let layer = graph_modification_utils::new_image_layer(image_frame, generate_uuid(), self.new_layer_parent(), responses);

View File

@ -13,38 +13,72 @@ use glam::{DAffine2, DVec2, IVec2, UVec2};
pub mod renderer; pub mod renderer;
#[derive(Copy, Clone, Debug, PartialEq, DynAny, specta::Type)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct AlphaBlending {
pub opacity: f32,
pub blend_mode: BlendMode,
}
impl Default for AlphaBlending {
fn default() -> Self {
Self::new()
}
}
impl core::hash::Hash for AlphaBlending {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.opacity.to_bits().hash(state);
self.blend_mode.hash(state);
}
}
impl AlphaBlending {
pub const fn new() -> Self {
Self {
opacity: 1.,
blend_mode: BlendMode::Normal,
}
}
}
/// A list of [`GraphicElement`]s /// A list of [`GraphicElement`]s
#[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 GraphicGroup { pub struct GraphicGroup {
elements: Vec<GraphicElement>, elements: Vec<GraphicElement>,
pub opacity: f32,
pub blend_mode: BlendMode,
pub transform: DAffine2, pub transform: DAffine2,
pub alpha_blending: AlphaBlending,
} }
impl core::hash::Hash for GraphicGroup { impl core::hash::Hash for GraphicGroup {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) { fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.transform.to_cols_array().iter().for_each(|element| element.to_bits().hash(state));
self.elements.hash(state); self.elements.hash(state);
self.opacity.to_bits().hash(state); self.alpha_blending.hash(state);
self.transform.to_cols_array().iter().for_each(|element| element.to_bits().hash(state))
} }
} }
/// Internal data for a [`GraphicElement`]. Can be [`VectorData`], [`ImageFrame`], text, or a nested [`GraphicGroup`] /// The possible forms of graphical content held in a Vec by the `elements` field of [`GraphicElement`].
/// Can be another recursively nested [`GraphicGroup`], [`VectorData`], an [`ImageFrame`], text (not yet implemented), or an [`Artboard`].
#[derive(Clone, Debug, Hash, PartialEq, DynAny)] #[derive(Clone, Debug, Hash, PartialEq, DynAny)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum GraphicElement { pub enum GraphicElement {
VectorShape(Box<VectorData>), /// Equivalent to the SVG <g> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/g
ImageFrame(ImageFrame<Color>),
Text(String),
GraphicGroup(GraphicGroup), GraphicGroup(GraphicGroup),
/// A vector shape, equivalent to the SVG <path> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path
VectorData(Box<VectorData>),
/// A bitmap image with a finite position and extent, equivalent to the SVG <image> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/image
ImageFrame(ImageFrame<Color>),
// TODO: Switch from `String` to a proper formatted typography type
/// Text, equivalent to the SVG <text> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/text
/// (Not yet implemented.)
Text(String),
/// The bounds for displaying a page of contained content
Artboard(Artboard), Artboard(Artboard),
} }
// TODO: Can this be removed? It doesn't necessarily make that much sense to have a default when, instead, the entire GraphicElement just shouldn't exist if there's no specific content to assign it.
impl Default for GraphicElement { impl Default for GraphicElement {
fn default() -> Self { fn default() -> Self {
Self::VectorShape(Box::new(VectorData::empty())) Self::VectorData(Box::new(VectorData::empty()))
} }
} }
@ -131,7 +165,7 @@ impl From<ImageFrame<Color>> for GraphicElement {
} }
impl From<VectorData> for GraphicElement { impl From<VectorData> for GraphicElement {
fn from(vector_data: VectorData) -> Self { fn from(vector_data: VectorData) -> Self {
GraphicElement::VectorShape(Box::new(vector_data)) GraphicElement::VectorData(Box::new(vector_data))
} }
} }
impl From<GraphicGroup> for GraphicElement { impl From<GraphicGroup> for GraphicElement {
@ -173,9 +207,8 @@ where
fn from(value: T) -> Self { fn from(value: T) -> Self {
Self { Self {
elements: (vec![value.into()]), elements: (vec![value.into()]),
opacity: 1.,
blend_mode: BlendMode::Normal,
transform: DAffine2::IDENTITY, transform: DAffine2::IDENTITY,
alpha_blending: AlphaBlending::default(),
} }
} }
} }
@ -183,9 +216,8 @@ where
impl GraphicGroup { impl GraphicGroup {
pub const EMPTY: Self = Self { pub const EMPTY: Self = Self {
elements: Vec::new(), elements: Vec::new(),
opacity: 1.,
blend_mode: BlendMode::Normal,
transform: DAffine2::IDENTITY, transform: DAffine2::IDENTITY,
alpha_blending: AlphaBlending::new(),
}; };
pub fn to_usvg_tree(&self, resolution: UVec2, viewbox: [DVec2; 2]) -> usvg::Tree { pub fn to_usvg_tree(&self, resolution: UVec2, viewbox: [DVec2; 2]) -> usvg::Tree {
@ -214,7 +246,7 @@ impl GraphicElement {
} }
match self { match self {
GraphicElement::VectorShape(vector_data) => { GraphicElement::VectorData(vector_data) => {
use usvg::tiny_skia_path::PathBuilder; use usvg::tiny_skia_path::PathBuilder;
let mut builder = PathBuilder::new(); let mut builder = PathBuilder::new();

View File

@ -223,12 +223,12 @@ impl GraphicElementRendered for GraphicGroup {
|attributes| { |attributes| {
attributes.push("transform", format_transform_matrix(self.transform)); attributes.push("transform", format_transform_matrix(self.transform));
if self.opacity < 1. { if self.alpha_blending.opacity < 1. {
attributes.push("opacity", self.opacity.to_string()); attributes.push("opacity", self.alpha_blending.opacity.to_string());
} }
if self.blend_mode != BlendMode::default() { if self.alpha_blending.blend_mode != BlendMode::default() {
attributes.push("style", self.blend_mode.render()); attributes.push("style", self.alpha_blending.blend_mode.render());
} }
}, },
|render| { |render| {
@ -275,12 +275,12 @@ impl GraphicElementRendered for VectorData {
.render(render_params.view_mode, &mut attributes.0.svg_defs, multiplied_transform, layer_bounds, transformed_bounds); .render(render_params.view_mode, &mut attributes.0.svg_defs, multiplied_transform, layer_bounds, transformed_bounds);
attributes.push_val(fill_and_stroke); attributes.push_val(fill_and_stroke);
if self.style.opacity < 1. { if self.alpha_blending.opacity < 1. {
attributes.push("opacity", self.style.opacity.to_string()); attributes.push("opacity", self.alpha_blending.opacity.to_string());
} }
if self.style.blend_mode != BlendMode::default() { if self.alpha_blending.blend_mode != BlendMode::default() {
attributes.push("style", self.style.blend_mode.render()); attributes.push("style", self.alpha_blending.blend_mode.render());
} }
}); });
} }
@ -426,8 +426,8 @@ impl GraphicElementRendered for ImageFrame<Color> {
attributes.push("preserveAspectRatio", "none"); attributes.push("preserveAspectRatio", "none");
attributes.push("transform", transform); attributes.push("transform", transform);
attributes.push("href", SvgSegment::BlobUrl(uuid)); attributes.push("href", SvgSegment::BlobUrl(uuid));
if self.blend_mode != BlendMode::default() { if self.alpha_blending.blend_mode != BlendMode::default() {
attributes.push("style", self.blend_mode.render()); attributes.push("style", self.alpha_blending.blend_mode.render());
} }
}); });
render.image_data.push((uuid, self.image.clone())) render.image_data.push((uuid, self.image.clone()))
@ -449,8 +449,8 @@ impl GraphicElementRendered for ImageFrame<Color> {
attributes.push("preserveAspectRatio", "none"); attributes.push("preserveAspectRatio", "none");
attributes.push("transform", transform); attributes.push("transform", transform);
attributes.push("href", base64_string); attributes.push("href", base64_string);
if self.blend_mode != BlendMode::default() { if self.alpha_blending.blend_mode != BlendMode::default() {
attributes.push("style", self.blend_mode.render()); attributes.push("style", self.alpha_blending.blend_mode.render());
} }
}); });
} }
@ -493,7 +493,7 @@ impl GraphicElementRendered for ImageFrame<Color> {
impl GraphicElementRendered for GraphicElement { impl GraphicElementRendered for GraphicElement {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) { fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
match self { match self {
GraphicElement::VectorShape(vector_data) => vector_data.render_svg(render, render_params), GraphicElement::VectorData(vector_data) => vector_data.render_svg(render, render_params),
GraphicElement::ImageFrame(image_frame) => image_frame.render_svg(render, render_params), GraphicElement::ImageFrame(image_frame) => image_frame.render_svg(render, render_params),
GraphicElement::Text(_) => todo!("Render a text GraphicElement"), GraphicElement::Text(_) => todo!("Render a text GraphicElement"),
GraphicElement::GraphicGroup(graphic_group) => graphic_group.render_svg(render, render_params), GraphicElement::GraphicGroup(graphic_group) => graphic_group.render_svg(render, render_params),
@ -503,7 +503,7 @@ impl GraphicElementRendered for GraphicElement {
fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> { fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
match self { match self {
GraphicElement::VectorShape(vector_data) => GraphicElementRendered::bounding_box(&**vector_data, transform), GraphicElement::VectorData(vector_data) => GraphicElementRendered::bounding_box(&**vector_data, transform),
GraphicElement::ImageFrame(image_frame) => image_frame.bounding_box(transform), GraphicElement::ImageFrame(image_frame) => image_frame.bounding_box(transform),
GraphicElement::Text(_) => todo!("Bounds of a text GraphicElement"), GraphicElement::Text(_) => todo!("Bounds of a text GraphicElement"),
GraphicElement::GraphicGroup(graphic_group) => graphic_group.bounding_box(transform), GraphicElement::GraphicGroup(graphic_group) => graphic_group.bounding_box(transform),
@ -513,7 +513,7 @@ impl GraphicElementRendered for GraphicElement {
fn add_click_targets(&self, click_targets: &mut Vec<ClickTarget>) { fn add_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
match self { match self {
GraphicElement::VectorShape(vector_data) => vector_data.add_click_targets(click_targets), GraphicElement::VectorData(vector_data) => vector_data.add_click_targets(click_targets),
GraphicElement::ImageFrame(image_frame) => image_frame.add_click_targets(click_targets), GraphicElement::ImageFrame(image_frame) => image_frame.add_click_targets(click_targets),
GraphicElement::Text(_) => todo!("click target for text GraphicElement"), GraphicElement::Text(_) => todo!("click target for text GraphicElement"),
GraphicElement::GraphicGroup(graphic_group) => graphic_group.add_click_targets(click_targets), GraphicElement::GraphicGroup(graphic_group) => graphic_group.add_click_targets(click_targets),
@ -523,7 +523,7 @@ impl GraphicElementRendered for GraphicElement {
fn to_usvg_node(&self) -> usvg::Node { fn to_usvg_node(&self) -> usvg::Node {
match self { match self {
GraphicElement::VectorShape(vector_data) => vector_data.to_usvg_node(), GraphicElement::VectorData(vector_data) => vector_data.to_usvg_node(),
GraphicElement::ImageFrame(image_frame) => image_frame.to_usvg_node(), GraphicElement::ImageFrame(image_frame) => image_frame.to_usvg_node(),
GraphicElement::Text(text) => text.to_usvg_node(), GraphicElement::Text(text) => text.to_usvg_node(),
GraphicElement::GraphicGroup(graphic_group) => graphic_group.to_usvg_node(), GraphicElement::GraphicGroup(graphic_group) => graphic_group.to_usvg_node(),

View File

@ -912,14 +912,14 @@ fn opacity_node(color: Color, opacity_multiplier: f32) -> Color {
#[node_macro::node_impl(OpacityNode)] #[node_macro::node_impl(OpacityNode)]
fn opacity_node(mut vector_data: VectorData, opacity_multiplier: f32) -> VectorData { fn opacity_node(mut vector_data: VectorData, opacity_multiplier: f32) -> VectorData {
let opacity_multiplier = opacity_multiplier / 100.; let opacity_multiplier = opacity_multiplier / 100.;
vector_data.style.opacity *= opacity_multiplier; vector_data.alpha_blending.opacity *= opacity_multiplier;
vector_data vector_data
} }
#[node_macro::node_impl(OpacityNode)] #[node_macro::node_impl(OpacityNode)]
fn opacity_node(mut graphic_group: GraphicGroup, opacity_multiplier: f32) -> GraphicGroup { fn opacity_node(mut graphic_group: GraphicGroup, opacity_multiplier: f32) -> GraphicGroup {
let opacity_multiplier = opacity_multiplier / 100.; let opacity_multiplier = opacity_multiplier / 100.;
graphic_group.opacity *= opacity_multiplier; graphic_group.alpha_blending.opacity *= opacity_multiplier;
graphic_group graphic_group
} }
@ -930,19 +930,19 @@ pub struct BlendModeNode<BM> {
#[node_macro::node_fn(BlendModeNode)] #[node_macro::node_fn(BlendModeNode)]
fn blend_mode_node(mut vector_data: VectorData, blend_mode: BlendMode) -> VectorData { fn blend_mode_node(mut vector_data: VectorData, blend_mode: BlendMode) -> VectorData {
vector_data.style.blend_mode = blend_mode; vector_data.alpha_blending.blend_mode = blend_mode;
vector_data vector_data
} }
#[node_macro::node_impl(BlendModeNode)] #[node_macro::node_impl(BlendModeNode)]
fn blend_mode_node(mut graphic_group: GraphicGroup, blend_mode: BlendMode) -> GraphicGroup { fn blend_mode_node(mut graphic_group: GraphicGroup, blend_mode: BlendMode) -> GraphicGroup {
graphic_group.blend_mode = blend_mode; graphic_group.alpha_blending.blend_mode = blend_mode;
graphic_group graphic_group
} }
#[node_macro::node_impl(BlendModeNode)] #[node_macro::node_impl(BlendModeNode)]
fn blend_mode_node(mut image_frame: ImageFrame<Color>, blend_mode: BlendMode) -> ImageFrame<Color> { fn blend_mode_node(mut image_frame: ImageFrame<Color>, blend_mode: BlendMode) -> ImageFrame<Color> {
image_frame.blend_mode = blend_mode; image_frame.alpha_blending.blend_mode = blend_mode;
image_frame image_frame
} }

View File

@ -1,6 +1,6 @@
use super::discrete_srgb::float_to_srgb_u8; use super::discrete_srgb::float_to_srgb_u8;
use super::{Color, ImageSlice}; use super::{Color, ImageSlice};
use crate::Node; use crate::{AlphaBlending, Node};
use alloc::vec::Vec; use alloc::vec::Vec;
use core::hash::{Hash, Hasher}; use core::hash::{Hash, Hasher};
use dyn_any::StaticType; use dyn_any::StaticType;
@ -260,7 +260,7 @@ pub struct ImageFrame<P: Pixel> {
// positive going right and y axis positive going down, with the origin // positive going right and y axis positive going down, with the origin
// being an unspecified quantity. // being an unspecified quantity.
pub transform: DAffine2, pub transform: DAffine2,
pub blend_mode: BlendMode, pub alpha_blending: AlphaBlending,
} }
impl<P: Debug + Copy + Pixel> Sample for ImageFrame<P> { impl<P: Debug + Copy + Pixel> Sample for ImageFrame<P> {
@ -312,7 +312,7 @@ impl<P: Copy + Pixel> ImageFrame<P> {
Self { Self {
image: Image::empty(), image: Image::empty(),
transform: DAffine2::ZERO, transform: DAffine2::ZERO,
blend_mode: BlendMode::Normal, alpha_blending: AlphaBlending::new(),
} }
} }
@ -320,7 +320,7 @@ impl<P: Copy + Pixel> ImageFrame<P> {
Self { Self {
image: Image::empty(), image: Image::empty(),
transform: DAffine2::IDENTITY, transform: DAffine2::IDENTITY,
blend_mode: BlendMode::Normal, alpha_blending: AlphaBlending::new(),
} }
} }
@ -381,7 +381,7 @@ impl From<ImageFrame<Color>> for ImageFrame<SRGBA8> {
height: image.image.height, height: image.image.height,
}, },
transform: image.transform, transform: image.transform,
blend_mode: BlendMode::Normal, alpha_blending: image.alpha_blending,
} }
} }
} }
@ -396,7 +396,7 @@ impl From<ImageFrame<SRGBA8>> for ImageFrame<Color> {
height: image.image.height, height: image.image.height,
}, },
transform: image.transform, transform: image.transform,
blend_mode: BlendMode::Normal, alpha_blending: image.alpha_blending,
} }
} }
} }

View File

@ -73,7 +73,7 @@ impl TransformMut for GraphicGroup {
impl Transform for GraphicElement { impl Transform for GraphicElement {
fn transform(&self) -> DAffine2 { fn transform(&self) -> DAffine2 {
match self { match self {
GraphicElement::VectorShape(vector_shape) => vector_shape.transform(), GraphicElement::VectorData(vector_shape) => vector_shape.transform(),
GraphicElement::ImageFrame(image_frame) => image_frame.transform(), GraphicElement::ImageFrame(image_frame) => image_frame.transform(),
GraphicElement::Text(_) => todo!("Transform of text"), GraphicElement::Text(_) => todo!("Transform of text"),
GraphicElement::GraphicGroup(graphic_group) => graphic_group.transform(), GraphicElement::GraphicGroup(graphic_group) => graphic_group.transform(),
@ -82,7 +82,7 @@ impl Transform for GraphicElement {
} }
fn local_pivot(&self, pivot: DVec2) -> DVec2 { fn local_pivot(&self, pivot: DVec2) -> DVec2 {
match self { match self {
GraphicElement::VectorShape(vector_shape) => vector_shape.local_pivot(pivot), GraphicElement::VectorData(vector_shape) => vector_shape.local_pivot(pivot),
GraphicElement::ImageFrame(image_frame) => image_frame.local_pivot(pivot), GraphicElement::ImageFrame(image_frame) => image_frame.local_pivot(pivot),
GraphicElement::Text(_) => todo!("Transform of text"), GraphicElement::Text(_) => todo!("Transform of text"),
GraphicElement::GraphicGroup(graphic_group) => graphic_group.local_pivot(pivot), GraphicElement::GraphicGroup(graphic_group) => graphic_group.local_pivot(pivot),
@ -91,7 +91,7 @@ impl Transform for GraphicElement {
} }
fn decompose_scale(&self) -> DVec2 { fn decompose_scale(&self) -> DVec2 {
match self { match self {
GraphicElement::VectorShape(vector_shape) => vector_shape.decompose_scale(), GraphicElement::VectorData(vector_shape) => vector_shape.decompose_scale(),
GraphicElement::ImageFrame(image_frame) => image_frame.decompose_scale(), GraphicElement::ImageFrame(image_frame) => image_frame.decompose_scale(),
GraphicElement::Text(_) => todo!("Transform of text"), GraphicElement::Text(_) => todo!("Transform of text"),
GraphicElement::GraphicGroup(graphic_group) => graphic_group.decompose_scale(), GraphicElement::GraphicGroup(graphic_group) => graphic_group.decompose_scale(),
@ -102,7 +102,7 @@ impl Transform for GraphicElement {
impl TransformMut for GraphicElement { impl TransformMut for GraphicElement {
fn transform_mut(&mut self) -> &mut DAffine2 { fn transform_mut(&mut self) -> &mut DAffine2 {
match self { match self {
GraphicElement::VectorShape(vector_shape) => vector_shape.transform_mut(), GraphicElement::VectorData(vector_shape) => vector_shape.transform_mut(),
GraphicElement::ImageFrame(image_frame) => image_frame.transform_mut(), GraphicElement::ImageFrame(image_frame) => image_frame.transform_mut(),
GraphicElement::Text(_) => todo!("Transform of text"), GraphicElement::Text(_) => todo!("Transform of text"),
GraphicElement::GraphicGroup(graphic_group) => graphic_group.transform_mut(), GraphicElement::GraphicGroup(graphic_group) => graphic_group.transform_mut(),

View File

@ -1,7 +1,6 @@
//! Contains stylistic options for SVG elements. //! Contains stylistic options for SVG elements.
use crate::consts::{LAYER_OUTLINE_STROKE_COLOR, LAYER_OUTLINE_STROKE_WEIGHT}; use crate::consts::{LAYER_OUTLINE_STROKE_COLOR, LAYER_OUTLINE_STROKE_WEIGHT};
use crate::raster::BlendMode;
use crate::Color; use crate::Color;
use dyn_any::{DynAny, StaticType}; use dyn_any::{DynAny, StaticType};
@ -410,27 +409,18 @@ impl Default for Stroke {
pub struct PathStyle { pub struct PathStyle {
stroke: Option<Stroke>, stroke: Option<Stroke>,
fill: Fill, fill: Fill,
pub opacity: f32,
pub blend_mode: BlendMode,
} }
impl core::hash::Hash for PathStyle { impl core::hash::Hash for PathStyle {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) { fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.stroke.hash(state); self.stroke.hash(state);
self.fill.hash(state); self.fill.hash(state);
self.opacity.to_bits().hash(state);
self.blend_mode.hash(state);
} }
} }
impl PathStyle { impl PathStyle {
pub const fn new(stroke: Option<Stroke>, fill: Fill) -> Self { pub const fn new(stroke: Option<Stroke>, fill: Fill) -> Self {
Self { Self { stroke, fill }
stroke,
fill,
opacity: 1.,
blend_mode: BlendMode::Normal,
}
} }
/// Get the current path's [Fill]. /// Get the current path's [Fill].

View File

@ -1,6 +1,6 @@
use super::style::{PathStyle, Stroke}; use super::style::{PathStyle, Stroke};
use crate::uuid::ManipulatorGroupId;
use crate::Color; use crate::Color;
use crate::{uuid::ManipulatorGroupId, AlphaBlending};
use bezier_rs::ManipulatorGroup; use bezier_rs::ManipulatorGroup;
use dyn_any::{DynAny, StaticType}; use dyn_any::{DynAny, StaticType};
@ -8,13 +8,14 @@ use dyn_any::{DynAny, StaticType};
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
/// [VectorData] is passed between nodes. /// [VectorData] is passed between nodes.
/// It contains a list of subpaths (that may be open or closed), a transform and some style information. /// It contains a list of subpaths (that may be open or closed), a transform, and some style information.
#[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 VectorData { pub struct VectorData {
pub subpaths: Vec<bezier_rs::Subpath<ManipulatorGroupId>>, pub subpaths: Vec<bezier_rs::Subpath<ManipulatorGroupId>>,
pub transform: DAffine2, pub transform: DAffine2,
pub style: PathStyle, pub style: PathStyle,
pub alpha_blending: AlphaBlending,
// TODO: Keavon asks: what is this for? Is it dead code? It seems to only be set, never read. // TODO: Keavon asks: what is this for? Is it dead code? It seems to only be set, never read.
pub mirror_angle: Vec<ManipulatorGroupId>, pub mirror_angle: Vec<ManipulatorGroupId>,
} }
@ -24,6 +25,7 @@ impl core::hash::Hash for VectorData {
self.subpaths.hash(state); self.subpaths.hash(state);
self.transform.to_cols_array().iter().for_each(|x| x.to_bits().hash(state)); self.transform.to_cols_array().iter().for_each(|x| x.to_bits().hash(state));
self.style.hash(state); self.style.hash(state);
self.alpha_blending.hash(state);
self.mirror_angle.hash(state); self.mirror_angle.hash(state);
} }
} }
@ -35,6 +37,7 @@ impl VectorData {
subpaths: Vec::new(), subpaths: Vec::new(),
transform: DAffine2::IDENTITY, transform: DAffine2::IDENTITY,
style: PathStyle::new(Some(Stroke::new(Some(Color::BLACK), 0.)), super::style::Fill::None), style: PathStyle::new(Some(Stroke::new(Some(Color::BLACK), 0.)), super::style::Fill::None),
alpha_blending: AlphaBlending::new(),
mirror_angle: Vec::new(), mirror_angle: Vec::new(),
} }
} }

View File

@ -356,7 +356,7 @@ async fn brush(image: ImageFrame<Color>, bounds: ImageFrame<Color>, strokes: Vec
let opaque_image = ImageFrame { let opaque_image = ImageFrame {
image: Image::new(bbox.size().x as u32, bbox.size().y as u32, Color::WHITE), image: Image::new(bbox.size().x as u32, bbox.size().y as u32, Color::WHITE),
transform: background_bounds, transform: background_bounds,
blend_mode: BlendMode::Normal, ..Default::default()
}; };
let mut erase_restore_mask = opaque_image; let mut erase_restore_mask = opaque_image;
@ -410,11 +410,7 @@ mod test {
#[test] #[test]
fn test_translate_node() { fn test_translate_node() {
let image = Image::new(10, 10, Color::TRANSPARENT); let image = Image::new(10, 10, Color::TRANSPARENT);
let mut image = ImageFrame { let mut image = ImageFrame { image, ..Default::default() };
image,
transform: DAffine2::IDENTITY,
blend_mode: BlendMode::Normal,
};
image.translate(DVec2::new(1., 2.)); image.translate(DVec2::new(1., 2.));
let translate_node = TranslateNode::new(ClonedNode::new(image)); let translate_node = TranslateNode::new(ClonedNode::new(image));
let image = translate_node.eval(DVec2::new(1., 2.)); let image = translate_node.eval(DVec2::new(1., 2.));

View File

@ -90,7 +90,7 @@ async fn map_gpu<'a: 'input>(image: ImageFrame<Color>, node: DocumentNode, edito
height: image.image.height, height: image.image.height,
}, },
transform: image.transform, transform: image.transform,
blend_mode: image.blend_mode, alpha_blending: image.alpha_blending,
}; };
// TODO: The cache should be based on the network topology not the node name // TODO: The cache should be based on the network topology not the node name
@ -142,7 +142,7 @@ async fn map_gpu<'a: 'input>(image: ImageFrame<Color>, node: DocumentNode, edito
height: image.image.height, height: image.image.height,
}, },
transform: image.transform, transform: image.transform,
blend_mode: image.blend_mode, alpha_blending: image.alpha_blending,
} }
} }
@ -588,6 +588,6 @@ async fn blend_gpu_image(foreground: ImageFrame<Color>, background: ImageFrame<C
height: background.image.height, height: background.image.height,
}, },
transform: background.transform, transform: background.transform,
blend_mode: background.blend_mode, alpha_blending: background.alpha_blending,
} }
} }

View File

@ -8,7 +8,7 @@ use graphene_core::transform::{Footprint, Transform};
use crate::wasm_application_io::WasmEditorApi; use crate::wasm_application_io::WasmEditorApi;
use graphene_core::raster::bbox::{AxisAlignedBbox, Bbox}; use graphene_core::raster::bbox::{AxisAlignedBbox, Bbox};
use graphene_core::value::CopiedNode; use graphene_core::value::CopiedNode;
use graphene_core::{Color, Node}; use graphene_core::{AlphaBlending, Color, Node};
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt::Debug; use std::fmt::Debug;
@ -115,7 +115,7 @@ fn sample(footprint: Footprint, image_frame: ImageFrame<Color>) -> ImageFrame<Co
ImageFrame { ImageFrame {
image, image,
transform: new_transform, transform: new_transform,
blend_mode: image_frame.blend_mode, alpha_blending: image_frame.alpha_blending,
} }
} }
@ -309,7 +309,7 @@ where
let mut new_background = ImageFrame { let mut new_background = ImageFrame {
image: new_background, image: new_background,
transform: transfrom, transform: transfrom,
blend_mode: background.blend_mode, alpha_blending: background.alpha_blending,
}; };
new_background = blend_image(background, new_background, map_fn); new_background = blend_image(background, new_background, map_fn);
@ -422,7 +422,7 @@ fn extend_image_to_bounds_node(image: ImageFrame<Color>, bounds: DAffine2) -> Im
ImageFrame { ImageFrame {
image: new_img, image: new_img,
transform: new_texture_to_layer_space, transform: new_texture_to_layer_space,
blend_mode: image.blend_mode, alpha_blending: image.alpha_blending,
} }
} }
@ -457,8 +457,11 @@ fn empty_image<_P: Pixel>(transform: DAffine2, color: _P) -> ImageFrame<_P> {
let image = Image::new(width, height, color); let image = Image::new(width, height, color);
let blend_mode = BlendMode::Normal; ImageFrame {
ImageFrame { image, transform, blend_mode } image,
transform,
alpha_blending: AlphaBlending::default(),
}
} }
macro_rules! generate_imaginate_node { macro_rules! generate_imaginate_node {
@ -495,7 +498,7 @@ macro_rules! generate_imaginate_node {
use std::hash::Hasher; use std::hash::Hasher;
let mut hasher = rustc_hash::FxHasher::default(); let mut hasher = rustc_hash::FxHasher::default();
frame.image.hash(&mut hasher); frame.image.hash(&mut hasher);
let hash =hasher.finish(); let hash = hasher.finish();
Box::pin(async move { Box::pin(async move {
let controller: std::pin::Pin<Box<dyn std::future::Future<Output = ImaginateController>>> = controller; let controller: std::pin::Pin<Box<dyn std::future::Future<Output = ImaginateController>>> = controller;
@ -505,16 +508,12 @@ macro_rules! generate_imaginate_node {
let image = super::imaginate::imaginate(frame.image, editor_api, controller, $($val,)*).await; let image = super::imaginate::imaginate(frame.image, editor_api, controller, $($val,)*).await;
self.cache.lock().unwrap().insert(hash, image.clone()); self.cache.lock().unwrap().insert(hash, image.clone());
return ImageFrame {
image, return ImageFrame { image, ..frame }
..frame
}
} }
let image = self.cache.lock().unwrap().get(&hash).cloned().unwrap_or_default(); let image = self.cache.lock().unwrap().get(&hash).cloned().unwrap_or_default();
ImageFrame {
image, ImageFrame { image, ..frame }
..frame
}
}) })
} }
} }
@ -549,7 +548,7 @@ fn image_frame<_P: Pixel>(image: Image<_P>, transform: DAffine2) -> graphene_cor
graphene_core::raster::ImageFrame { graphene_core::raster::ImageFrame {
image, image,
transform, transform,
blend_mode: BlendMode::Normal, alpha_blending: AlphaBlending::default(),
} }
} }
@ -576,7 +575,7 @@ fn pixel_noise(width: u32, height: u32, seed: u32, noise_type: NoiseType) -> gra
ImageFrame::<Color> { ImageFrame::<Color> {
image, image,
transform: DAffine2::from_scale(DVec2::new(width as f64, height as f64)), transform: DAffine2::from_scale(DVec2::new(width as f64, height as f64)),
blend_mode: BlendMode::Normal, alpha_blending: AlphaBlending::default(),
} }
} }
@ -621,7 +620,7 @@ fn mandelbrot_node(footprint: Footprint) -> ImageFrame<Color> {
ImageFrame { ImageFrame {
image: Image { width, height, data }, image: Image { width, height, data },
transform: DAffine2::from_translation(offset) * DAffine2::from_scale(size), transform: DAffine2::from_translation(offset) * DAffine2::from_scale(size),
blend_mode: BlendMode::Normal, ..Default::default()
} }
} }

View File

@ -279,8 +279,7 @@ fn decode_image_node<'a: 'input>(data: Arc<[u8]>) -> ImageFrame<Color> {
width: image.width(), width: image.width(),
height: image.height(), height: image.height(),
}, },
transform: glam::DAffine2::IDENTITY, ..Default::default()
blend_mode: graphene_core::raster::BlendMode::Normal,
}; };
image image
} }

View File

@ -312,7 +312,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
let empty_image = ImageFrame { let empty_image = ImageFrame {
image: Image::new(bounds.x, bounds.y, Color::BLACK), image: Image::new(bounds.x, bounds.y, Color::BLACK),
transform, transform,
blend_mode: BlendMode::Normal, ..Default::default()
}; };
let final_image = ClonedNode::new(empty_image).then(complete_node); let final_image = ClonedNode::new(empty_image).then(complete_node);
let final_image = FutureWrapperNode::new(final_image); let final_image = FutureWrapperNode::new(final_image);