mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-08-03 21:08:18 +00:00
Wrap opacity/blend_mode in alpha_blending struct for graphic elements
This commit is contained in:
parent
10f2fa92e5
commit
e459e599b4
15 changed files with 111 additions and 96 deletions
|
@ -13,38 +13,72 @@ use glam::{DAffine2, DVec2, IVec2, UVec2};
|
|||
|
||||
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
|
||||
#[derive(Clone, Debug, PartialEq, DynAny, Default)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct GraphicGroup {
|
||||
elements: Vec<GraphicElement>,
|
||||
pub opacity: f32,
|
||||
pub blend_mode: BlendMode,
|
||||
pub transform: DAffine2,
|
||||
pub alpha_blending: AlphaBlending,
|
||||
}
|
||||
|
||||
impl core::hash::Hash for GraphicGroup {
|
||||
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.opacity.to_bits().hash(state);
|
||||
self.transform.to_cols_array().iter().for_each(|element| element.to_bits().hash(state))
|
||||
self.alpha_blending.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)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum GraphicElement {
|
||||
VectorShape(Box<VectorData>),
|
||||
ImageFrame(ImageFrame<Color>),
|
||||
Text(String),
|
||||
/// Equivalent to the SVG <g> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/g
|
||||
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),
|
||||
}
|
||||
|
||||
// TODO: Can this be removed? It doesn't necessarily make that much sense to have a default when, instead, the entire GraphicElement just shouldn't exist if there's no specific content to assign it.
|
||||
impl Default for GraphicElement {
|
||||
fn default() -> Self {
|
||||
Self::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 {
|
||||
fn from(vector_data: VectorData) -> Self {
|
||||
GraphicElement::VectorShape(Box::new(vector_data))
|
||||
GraphicElement::VectorData(Box::new(vector_data))
|
||||
}
|
||||
}
|
||||
impl From<GraphicGroup> for GraphicElement {
|
||||
|
@ -173,9 +207,8 @@ where
|
|||
fn from(value: T) -> Self {
|
||||
Self {
|
||||
elements: (vec![value.into()]),
|
||||
opacity: 1.,
|
||||
blend_mode: BlendMode::Normal,
|
||||
transform: DAffine2::IDENTITY,
|
||||
alpha_blending: AlphaBlending::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -183,9 +216,8 @@ where
|
|||
impl GraphicGroup {
|
||||
pub const EMPTY: Self = Self {
|
||||
elements: Vec::new(),
|
||||
opacity: 1.,
|
||||
blend_mode: BlendMode::Normal,
|
||||
transform: DAffine2::IDENTITY,
|
||||
alpha_blending: AlphaBlending::new(),
|
||||
};
|
||||
|
||||
pub fn to_usvg_tree(&self, resolution: UVec2, viewbox: [DVec2; 2]) -> usvg::Tree {
|
||||
|
@ -214,7 +246,7 @@ impl GraphicElement {
|
|||
}
|
||||
|
||||
match self {
|
||||
GraphicElement::VectorShape(vector_data) => {
|
||||
GraphicElement::VectorData(vector_data) => {
|
||||
use usvg::tiny_skia_path::PathBuilder;
|
||||
let mut builder = PathBuilder::new();
|
||||
|
||||
|
|
|
@ -223,12 +223,12 @@ impl GraphicElementRendered for GraphicGroup {
|
|||
|attributes| {
|
||||
attributes.push("transform", format_transform_matrix(self.transform));
|
||||
|
||||
if self.opacity < 1. {
|
||||
attributes.push("opacity", self.opacity.to_string());
|
||||
if self.alpha_blending.opacity < 1. {
|
||||
attributes.push("opacity", self.alpha_blending.opacity.to_string());
|
||||
}
|
||||
|
||||
if self.blend_mode != BlendMode::default() {
|
||||
attributes.push("style", self.blend_mode.render());
|
||||
if self.alpha_blending.blend_mode != BlendMode::default() {
|
||||
attributes.push("style", self.alpha_blending.blend_mode.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);
|
||||
attributes.push_val(fill_and_stroke);
|
||||
|
||||
if self.style.opacity < 1. {
|
||||
attributes.push("opacity", self.style.opacity.to_string());
|
||||
if self.alpha_blending.opacity < 1. {
|
||||
attributes.push("opacity", self.alpha_blending.opacity.to_string());
|
||||
}
|
||||
|
||||
if self.style.blend_mode != BlendMode::default() {
|
||||
attributes.push("style", self.style.blend_mode.render());
|
||||
if self.alpha_blending.blend_mode != BlendMode::default() {
|
||||
attributes.push("style", self.alpha_blending.blend_mode.render());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -426,8 +426,8 @@ impl GraphicElementRendered for ImageFrame<Color> {
|
|||
attributes.push("preserveAspectRatio", "none");
|
||||
attributes.push("transform", transform);
|
||||
attributes.push("href", SvgSegment::BlobUrl(uuid));
|
||||
if self.blend_mode != BlendMode::default() {
|
||||
attributes.push("style", self.blend_mode.render());
|
||||
if self.alpha_blending.blend_mode != BlendMode::default() {
|
||||
attributes.push("style", self.alpha_blending.blend_mode.render());
|
||||
}
|
||||
});
|
||||
render.image_data.push((uuid, self.image.clone()))
|
||||
|
@ -449,8 +449,8 @@ impl GraphicElementRendered for ImageFrame<Color> {
|
|||
attributes.push("preserveAspectRatio", "none");
|
||||
attributes.push("transform", transform);
|
||||
attributes.push("href", base64_string);
|
||||
if self.blend_mode != BlendMode::default() {
|
||||
attributes.push("style", self.blend_mode.render());
|
||||
if self.alpha_blending.blend_mode != BlendMode::default() {
|
||||
attributes.push("style", self.alpha_blending.blend_mode.render());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -493,7 +493,7 @@ impl GraphicElementRendered for ImageFrame<Color> {
|
|||
impl GraphicElementRendered for GraphicElement {
|
||||
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
|
||||
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::Text(_) => todo!("Render a text GraphicElement"),
|
||||
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]> {
|
||||
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::Text(_) => todo!("Bounds of a text GraphicElement"),
|
||||
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>) {
|
||||
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::Text(_) => todo!("click target for text GraphicElement"),
|
||||
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 {
|
||||
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::Text(text) => text.to_usvg_node(),
|
||||
GraphicElement::GraphicGroup(graphic_group) => graphic_group.to_usvg_node(),
|
||||
|
|
|
@ -912,14 +912,14 @@ fn opacity_node(color: Color, opacity_multiplier: f32) -> Color {
|
|||
#[node_macro::node_impl(OpacityNode)]
|
||||
fn opacity_node(mut vector_data: VectorData, opacity_multiplier: f32) -> VectorData {
|
||||
let opacity_multiplier = opacity_multiplier / 100.;
|
||||
vector_data.style.opacity *= opacity_multiplier;
|
||||
vector_data.alpha_blending.opacity *= opacity_multiplier;
|
||||
vector_data
|
||||
}
|
||||
|
||||
#[node_macro::node_impl(OpacityNode)]
|
||||
fn opacity_node(mut graphic_group: GraphicGroup, opacity_multiplier: f32) -> GraphicGroup {
|
||||
let opacity_multiplier = opacity_multiplier / 100.;
|
||||
graphic_group.opacity *= opacity_multiplier;
|
||||
graphic_group.alpha_blending.opacity *= opacity_multiplier;
|
||||
graphic_group
|
||||
}
|
||||
|
||||
|
@ -930,19 +930,19 @@ pub struct BlendModeNode<BM> {
|
|||
|
||||
#[node_macro::node_fn(BlendModeNode)]
|
||||
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
|
||||
}
|
||||
|
||||
#[node_macro::node_impl(BlendModeNode)]
|
||||
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
|
||||
}
|
||||
|
||||
#[node_macro::node_impl(BlendModeNode)]
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use super::discrete_srgb::float_to_srgb_u8;
|
||||
use super::{Color, ImageSlice};
|
||||
use crate::Node;
|
||||
use crate::{AlphaBlending, Node};
|
||||
use alloc::vec::Vec;
|
||||
use core::hash::{Hash, Hasher};
|
||||
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
|
||||
// being an unspecified quantity.
|
||||
pub transform: DAffine2,
|
||||
pub blend_mode: BlendMode,
|
||||
pub alpha_blending: AlphaBlending,
|
||||
}
|
||||
|
||||
impl<P: Debug + Copy + Pixel> Sample for ImageFrame<P> {
|
||||
|
@ -312,7 +312,7 @@ impl<P: Copy + Pixel> ImageFrame<P> {
|
|||
Self {
|
||||
image: Image::empty(),
|
||||
transform: DAffine2::ZERO,
|
||||
blend_mode: BlendMode::Normal,
|
||||
alpha_blending: AlphaBlending::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -320,7 +320,7 @@ impl<P: Copy + Pixel> ImageFrame<P> {
|
|||
Self {
|
||||
image: Image::empty(),
|
||||
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,
|
||||
},
|
||||
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,
|
||||
},
|
||||
transform: image.transform,
|
||||
blend_mode: BlendMode::Normal,
|
||||
alpha_blending: image.alpha_blending,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -73,7 +73,7 @@ impl TransformMut for GraphicGroup {
|
|||
impl Transform for GraphicElement {
|
||||
fn transform(&self) -> DAffine2 {
|
||||
match self {
|
||||
GraphicElement::VectorShape(vector_shape) => vector_shape.transform(),
|
||||
GraphicElement::VectorData(vector_shape) => vector_shape.transform(),
|
||||
GraphicElement::ImageFrame(image_frame) => image_frame.transform(),
|
||||
GraphicElement::Text(_) => todo!("Transform of text"),
|
||||
GraphicElement::GraphicGroup(graphic_group) => graphic_group.transform(),
|
||||
|
@ -82,7 +82,7 @@ impl Transform for GraphicElement {
|
|||
}
|
||||
fn local_pivot(&self, pivot: DVec2) -> DVec2 {
|
||||
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::Text(_) => todo!("Transform of text"),
|
||||
GraphicElement::GraphicGroup(graphic_group) => graphic_group.local_pivot(pivot),
|
||||
|
@ -91,7 +91,7 @@ impl Transform for GraphicElement {
|
|||
}
|
||||
fn decompose_scale(&self) -> DVec2 {
|
||||
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::Text(_) => todo!("Transform of text"),
|
||||
GraphicElement::GraphicGroup(graphic_group) => graphic_group.decompose_scale(),
|
||||
|
@ -102,7 +102,7 @@ impl Transform for GraphicElement {
|
|||
impl TransformMut for GraphicElement {
|
||||
fn transform_mut(&mut self) -> &mut DAffine2 {
|
||||
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::Text(_) => todo!("Transform of text"),
|
||||
GraphicElement::GraphicGroup(graphic_group) => graphic_group.transform_mut(),
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
//! Contains stylistic options for SVG elements.
|
||||
|
||||
use crate::consts::{LAYER_OUTLINE_STROKE_COLOR, LAYER_OUTLINE_STROKE_WEIGHT};
|
||||
use crate::raster::BlendMode;
|
||||
use crate::Color;
|
||||
|
||||
use dyn_any::{DynAny, StaticType};
|
||||
|
@ -410,27 +409,18 @@ impl Default for Stroke {
|
|||
pub struct PathStyle {
|
||||
stroke: Option<Stroke>,
|
||||
fill: Fill,
|
||||
pub opacity: f32,
|
||||
pub blend_mode: BlendMode,
|
||||
}
|
||||
|
||||
impl core::hash::Hash for PathStyle {
|
||||
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||
self.stroke.hash(state);
|
||||
self.fill.hash(state);
|
||||
self.opacity.to_bits().hash(state);
|
||||
self.blend_mode.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl PathStyle {
|
||||
pub const fn new(stroke: Option<Stroke>, fill: Fill) -> Self {
|
||||
Self {
|
||||
stroke,
|
||||
fill,
|
||||
opacity: 1.,
|
||||
blend_mode: BlendMode::Normal,
|
||||
}
|
||||
Self { stroke, fill }
|
||||
}
|
||||
|
||||
/// Get the current path's [Fill].
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use super::style::{PathStyle, Stroke};
|
||||
use crate::uuid::ManipulatorGroupId;
|
||||
use crate::Color;
|
||||
use crate::{uuid::ManipulatorGroupId, AlphaBlending};
|
||||
|
||||
use bezier_rs::ManipulatorGroup;
|
||||
use dyn_any::{DynAny, StaticType};
|
||||
|
@ -8,13 +8,14 @@ use dyn_any::{DynAny, StaticType};
|
|||
use glam::{DAffine2, DVec2};
|
||||
|
||||
/// [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)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct VectorData {
|
||||
pub subpaths: Vec<bezier_rs::Subpath<ManipulatorGroupId>>,
|
||||
pub transform: DAffine2,
|
||||
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.
|
||||
pub mirror_angle: Vec<ManipulatorGroupId>,
|
||||
}
|
||||
|
@ -24,6 +25,7 @@ impl core::hash::Hash for VectorData {
|
|||
self.subpaths.hash(state);
|
||||
self.transform.to_cols_array().iter().for_each(|x| x.to_bits().hash(state));
|
||||
self.style.hash(state);
|
||||
self.alpha_blending.hash(state);
|
||||
self.mirror_angle.hash(state);
|
||||
}
|
||||
}
|
||||
|
@ -35,6 +37,7 @@ impl VectorData {
|
|||
subpaths: Vec::new(),
|
||||
transform: DAffine2::IDENTITY,
|
||||
style: PathStyle::new(Some(Stroke::new(Some(Color::BLACK), 0.)), super::style::Fill::None),
|
||||
alpha_blending: AlphaBlending::new(),
|
||||
mirror_angle: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue