mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-08-23 14:34:06 +00:00
Display images in the SVG viewport renderer via canvases instead of base64 PNGs (#2903)
* add: move images as rendered canvases to node_graph_executor
* add: added the frontend message
* fix: bytemuck stuff
* fix: canvas element breaking
* fix: width issues
* fix: remove the old message
* npm: run lint-fix
* fix
* works finally
* fix transforms
* Fix self closing tag
* fix: reuse id
* fix: have it working with repeat instance
* cargo: fmt
* fix
* Avoid "canvas" prefix to IDs
* fix
* fix: vello issue from 6111440
* fix: gpu stuff
* fix: vello bbox
* Code review
---------
Co-authored-by: hypercube <0hypercube@gmail.com>
Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
parent
45bd031a36
commit
72f1047a27
17 changed files with 280 additions and 117 deletions
|
@ -20,6 +20,7 @@ use graphene_core::{Artboard, ArtboardGroupTable, GraphicElement, GraphicGroupTa
|
|||
use num_traits::Zero;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::fmt::Write;
|
||||
use std::hash::{DefaultHasher, Hash, Hasher};
|
||||
use std::ops::Deref;
|
||||
use std::sync::{Arc, LazyLock};
|
||||
#[cfg(feature = "vello")]
|
||||
|
@ -175,6 +176,10 @@ impl RenderParams {
|
|||
let alignment_parent_transform = Some(transform);
|
||||
Self { alignment_parent_transform, ..*self }
|
||||
}
|
||||
|
||||
pub fn to_canvas(&self) -> bool {
|
||||
!self.for_export && !self.thumbnail && !self.for_mask
|
||||
}
|
||||
}
|
||||
|
||||
pub fn format_transform_matrix(transform: DAffine2) -> String {
|
||||
|
@ -243,8 +248,7 @@ impl GraphicElementRendered for GraphicGroupTable {
|
|||
attributes.push("transform", matrix);
|
||||
}
|
||||
|
||||
let factor = if render_params.for_mask { 1. } else { instance.alpha_blending.fill };
|
||||
let opacity = instance.alpha_blending.opacity * factor;
|
||||
let opacity = instance.alpha_blending.opacity(render_params.for_mask);
|
||||
if opacity < 1. {
|
||||
attributes.push("opacity", opacity.to_string());
|
||||
}
|
||||
|
@ -299,8 +303,7 @@ impl GraphicElementRendered for GraphicGroupTable {
|
|||
};
|
||||
let mut bounds = None;
|
||||
|
||||
let factor = if render_params.for_mask { 1. } else { alpha_blending.fill };
|
||||
let opacity = alpha_blending.opacity * factor;
|
||||
let opacity = instance.alpha_blending.opacity(render_params.for_mask);
|
||||
if opacity < 1. || (render_params.view_mode != ViewMode::Outline && alpha_blending.blend_mode != BlendMode::default()) {
|
||||
bounds = self
|
||||
.instance_ref_iter()
|
||||
|
@ -503,8 +506,7 @@ impl GraphicElementRendered for VectorDataTable {
|
|||
}
|
||||
attributes.push_val(fill_and_stroke);
|
||||
|
||||
let factor = if render_params.for_mask { 1. } else { instance.alpha_blending.fill };
|
||||
let opacity = instance.alpha_blending.opacity * factor;
|
||||
let opacity = instance.alpha_blending.opacity(render_params.for_mask);
|
||||
if opacity < 1. {
|
||||
attributes.push("opacity", opacity.to_string());
|
||||
}
|
||||
|
@ -545,10 +547,13 @@ impl GraphicElementRendered for VectorDataTable {
|
|||
_ => instance.alpha_blending.blend_mode.to_peniko(),
|
||||
};
|
||||
let mut layer = false;
|
||||
let factor = if render_params.for_mask { 1. } else { instance.alpha_blending.fill };
|
||||
let opacity = instance.alpha_blending.opacity * factor;
|
||||
|
||||
let opacity = instance.alpha_blending.opacity(render_params.for_mask);
|
||||
if opacity < 1. || instance.alpha_blending.blend_mode != BlendMode::default() {
|
||||
layer = true;
|
||||
let weight = instance.instance.style.stroke().unwrap().weight;
|
||||
let quad = Quad::from_box(layer_bounds).inflate(weight * element_transform.matrix2.determinant());
|
||||
let layer_bounds = quad.bounding_box();
|
||||
scene.push_layer(
|
||||
peniko::BlendMode::new(blend_mode, peniko::Compose::SrcOver),
|
||||
opacity,
|
||||
|
@ -945,56 +950,122 @@ impl GraphicElementRendered for RasterDataTable<CPU> {
|
|||
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
|
||||
for instance in self.instance_ref_iter() {
|
||||
let transform = *instance.transform;
|
||||
|
||||
let image = &instance.instance;
|
||||
|
||||
if image.data.is_empty() {
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
|
||||
let base64_string = image.base64_string.clone().unwrap_or_else(|| {
|
||||
use base64::Engine;
|
||||
if render_params.to_canvas() {
|
||||
let id = instance.source_node_id.map(|x| x.0).unwrap_or_else(|| {
|
||||
let mut state = DefaultHasher::new();
|
||||
image.data().hash(&mut state);
|
||||
state.finish()
|
||||
});
|
||||
if !render.image_data.iter().any(|(old_id, _)| *old_id == id) {
|
||||
render.image_data.push((id, image.data().clone()));
|
||||
}
|
||||
render.parent_tag(
|
||||
"foreignObject",
|
||||
|attributes| {
|
||||
let mut transform_values = transform.to_scale_angle_translation();
|
||||
let size = DVec2::new(image.width as f64, image.height as f64);
|
||||
transform_values.0 /= size;
|
||||
|
||||
let output = image.to_png();
|
||||
let preamble = "data:image/png;base64,";
|
||||
let mut base64_string = String::with_capacity(preamble.len() + output.len() * 4);
|
||||
base64_string.push_str(preamble);
|
||||
base64::engine::general_purpose::STANDARD.encode_string(output, &mut base64_string);
|
||||
base64_string
|
||||
});
|
||||
render.leaf_tag("image", |attributes| {
|
||||
attributes.push("width", 1.to_string());
|
||||
attributes.push("height", 1.to_string());
|
||||
attributes.push("preserveAspectRatio", "none");
|
||||
attributes.push("href", base64_string);
|
||||
let matrix = format_transform_matrix(transform);
|
||||
if !matrix.is_empty() {
|
||||
attributes.push("transform", matrix);
|
||||
}
|
||||
let factor = if render_params.for_mask { 1. } else { instance.alpha_blending.fill };
|
||||
let opacity = instance.alpha_blending.opacity * factor;
|
||||
if opacity < 1. {
|
||||
attributes.push("opacity", opacity.to_string());
|
||||
}
|
||||
if instance.alpha_blending.blend_mode != BlendMode::default() {
|
||||
attributes.push("style", instance.alpha_blending.blend_mode.render());
|
||||
}
|
||||
});
|
||||
let matrix = DAffine2::from_scale_angle_translation(transform_values.0, transform_values.1, transform_values.2);
|
||||
let matrix = format_transform_matrix(matrix);
|
||||
if !matrix.is_empty() {
|
||||
attributes.push("transform", matrix);
|
||||
}
|
||||
|
||||
attributes.push("width", size.x.to_string());
|
||||
attributes.push("height", size.y.to_string());
|
||||
|
||||
let opacity = instance.alpha_blending.opacity(render_params.for_mask);
|
||||
if opacity < 1. {
|
||||
attributes.push("opacity", opacity.to_string());
|
||||
}
|
||||
|
||||
if instance.alpha_blending.blend_mode != BlendMode::default() {
|
||||
attributes.push("style", instance.alpha_blending.blend_mode.render());
|
||||
}
|
||||
},
|
||||
|render| {
|
||||
render.leaf_tag(
|
||||
"img", // Must be a self-closing (void element) tag, so we can't use `div` or `span`, for example
|
||||
|attributes| {
|
||||
attributes.push("data-canvas-placeholder", id.to_string());
|
||||
},
|
||||
)
|
||||
},
|
||||
);
|
||||
} else {
|
||||
let base64_string = image.base64_string.clone().unwrap_or_else(|| {
|
||||
use base64::Engine;
|
||||
|
||||
let output = image.to_png();
|
||||
let preamble = "data:image/png;base64,";
|
||||
let mut base64_string = String::with_capacity(preamble.len() + output.len() * 4);
|
||||
base64_string.push_str(preamble);
|
||||
base64::engine::general_purpose::STANDARD.encode_string(output, &mut base64_string);
|
||||
base64_string
|
||||
});
|
||||
|
||||
render.leaf_tag("image", |attributes| {
|
||||
attributes.push("width", "1");
|
||||
attributes.push("height", "1");
|
||||
attributes.push("preserveAspectRatio", "none");
|
||||
attributes.push("href", base64_string);
|
||||
let matrix = format_transform_matrix(transform);
|
||||
if !matrix.is_empty() {
|
||||
attributes.push("transform", matrix);
|
||||
}
|
||||
|
||||
let opacity = instance.alpha_blending.opacity(render_params.for_mask);
|
||||
if opacity < 1. {
|
||||
attributes.push("opacity", opacity.to_string());
|
||||
}
|
||||
if instance.alpha_blending.blend_mode != BlendMode::default() {
|
||||
attributes.push("style", instance.alpha_blending.blend_mode.render());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "vello")]
|
||||
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, _: &mut RenderContext, _render_params: &RenderParams) {
|
||||
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, _: &mut RenderContext, render_params: &RenderParams) {
|
||||
use vello::peniko;
|
||||
|
||||
for instance in self.instance_ref_iter() {
|
||||
let image = &instance.instance;
|
||||
if image.data.is_empty() {
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
let image = peniko::Image::new(image.to_flat_u8().0.into(), peniko::ImageFormat::Rgba8, image.width, image.height).with_extend(peniko::Extend::Repeat);
|
||||
let transform = transform * *instance.transform * DAffine2::from_scale(1. / DVec2::new(image.width as f64, image.height as f64));
|
||||
|
||||
scene.draw_image(&image, kurbo::Affine::new(transform.to_cols_array()));
|
||||
let alpha_blending = *instance.alpha_blending;
|
||||
let blend_mode = alpha_blending.blend_mode.to_peniko();
|
||||
|
||||
let opacity = alpha_blending.opacity(render_params.for_mask);
|
||||
let mut layer = false;
|
||||
|
||||
if opacity < 1. || alpha_blending.blend_mode != BlendMode::default() {
|
||||
if let Some(bounds) = self.bounding_box(transform, false) {
|
||||
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(blending, opacity, kurbo::Affine::IDENTITY, &rect);
|
||||
layer = true;
|
||||
}
|
||||
}
|
||||
|
||||
let image = peniko::Image::new(image.to_flat_u8().0.into(), peniko::ImageFormat::Rgba8, image.width, image.height).with_extend(peniko::Extend::Repeat);
|
||||
let image_transform = transform * *instance.transform * DAffine2::from_scale(1. / DVec2::new(image.width as f64, image.height as f64));
|
||||
|
||||
scene.draw_image(&image, kurbo::Affine::new(image_transform.to_cols_array()));
|
||||
|
||||
if layer {
|
||||
scene.pop_layer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1029,12 +1100,14 @@ impl GraphicElementRendered for RasterDataTable<GPU> {
|
|||
|
||||
for instance in self.instance_ref_iter() {
|
||||
let blend_mode = *instance.alpha_blending;
|
||||
let layer = blend_mode != Default::default();
|
||||
if layer {
|
||||
let Some(bounds) = self.bounding_box(transform, true) else { return };
|
||||
let blending = peniko::BlendMode::new(blend_mode.blend_mode.to_peniko(), peniko::Compose::SrcOver);
|
||||
let rect = kurbo::Rect::new(bounds[0].x, bounds[0].y, bounds[1].x, bounds[1].y);
|
||||
scene.push_layer(blending, blend_mode.opacity, kurbo::Affine::IDENTITY, &rect);
|
||||
let mut layer = false;
|
||||
if blend_mode != Default::default() {
|
||||
if let Some(bounds) = self.bounding_box(transform, true) {
|
||||
let blending = peniko::BlendMode::new(blend_mode.blend_mode.to_peniko(), peniko::Compose::SrcOver);
|
||||
let rect = kurbo::Rect::new(bounds[0].x, bounds[0].y, bounds[1].x, bounds[1].y);
|
||||
scene.push_layer(blending, blend_mode.opacity, kurbo::Affine::IDENTITY, &rect);
|
||||
layer = true;
|
||||
}
|
||||
}
|
||||
|
||||
let image = peniko::Image::new(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue