Fix rasterize node document leakage with hashmap and eq check (#3550)

* fix: rasterize node document leakage with hashmap and eq check

* Use single Hashmap and ignore source id

* use or_insert_with instead of Entry match

---------

Co-authored-by: Dennis Kobert <dennis@kobert.dev>
This commit is contained in:
Ayush Amawate
2026-01-04 11:33:18 +00:00
committed by GitHub
co-authored by Dennis Kobert
parent ee2e61f38f
commit 42440c0d0b
4 changed files with 27 additions and 17 deletions
@@ -216,7 +216,7 @@ impl Pixel for Luma {}
/// The other components (RGB) are stored as `f32` that range from `0.0` up to `f32::MAX`, /// The other components (RGB) are stored as `f32` that range from `0.0` up to `f32::MAX`,
/// the values encode the brightness of each channel proportional to the light intensity in cd/m² (nits) in HDR, and `0.0` (black) to `1.0` (white) in SDR color. /// the values encode the brightness of each channel proportional to the light intensity in cd/m² (nits) in HDR, and `0.0` (black) to `1.0` (white) in SDR color.
#[repr(C)] #[repr(C)]
#[derive(Debug, Default, Clone, Copy, PartialEq, Pod, Zeroable, BufferStruct)] #[derive(Debug, Default, Clone, Copy, Pod, Zeroable, BufferStruct)]
#[cfg_attr(feature = "std", derive(dyn_any::DynAny, specta::Type, serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "std", derive(dyn_any::DynAny, specta::Type, serde::Serialize, serde::Deserialize))]
pub struct Color { pub struct Color {
red: f32, red: f32,
@@ -225,6 +225,14 @@ pub struct Color {
alpha: f32, alpha: f32,
} }
impl PartialEq for Color {
fn eq(&self, other: &Self) -> bool {
self.red == other.red && self.green == other.green && self.blue == other.blue && self.alpha == other.alpha
}
}
impl Eq for Color {}
#[allow(clippy::derived_hash_with_manual_eq)] #[allow(clippy::derived_hash_with_manual_eq)]
impl Hash for Color { impl Hash for Color {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) { fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
@@ -39,7 +39,7 @@ mod base64_serde {
} }
} }
#[derive(Clone, PartialEq, Default, specta::Type, serde::Serialize, serde::Deserialize)] #[derive(Clone, Eq, Default, specta::Type, serde::Serialize, serde::Deserialize)]
pub struct Image<P: Pixel> { pub struct Image<P: Pixel> {
pub width: u32, pub width: u32,
pub height: u32, pub height: u32,
@@ -53,6 +53,12 @@ pub struct Image<P: Pixel> {
// TODO: Currently it is always anchored at the top left corner at (0, 0). The bottom right corner of the new origin field would correspond to (1, 1). // TODO: Currently it is always anchored at the top left corner at (0, 0). The bottom right corner of the new origin field would correspond to (1, 1).
} }
impl<P: Pixel + PartialEq> PartialEq for Image<P> {
fn eq(&self, other: &Self) -> bool {
self.width == other.width && self.height == other.height && self.data == other.data
}
}
#[derive(Debug, Clone, dyn_any::DynAny, Default, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] #[derive(Debug, Clone, dyn_any::DynAny, Default, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct TransformImage(pub DAffine2); pub struct TransformImage(pub DAffine2);
+8 -13
View File
@@ -26,7 +26,7 @@ use kurbo::Shape;
use num_traits::Zero; use num_traits::Zero;
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::fmt::Write; use std::fmt::Write;
use std::hash::{DefaultHasher, Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::ops::Deref; use std::ops::Deref;
use std::sync::{Arc, LazyLock}; use std::sync::{Arc, LazyLock};
use vello::*; use vello::*;
@@ -59,7 +59,7 @@ pub struct SvgRender {
pub svg: Vec<SvgSegment>, pub svg: Vec<SvgSegment>,
pub svg_defs: String, pub svg_defs: String,
pub transform: DAffine2, pub transform: DAffine2,
pub image_data: Vec<(u64, Image<Color>)>, pub image_data: HashMap<Image<Color>, u64>,
indent: usize, indent: usize,
} }
@@ -69,7 +69,7 @@ impl SvgRender {
svg: Vec::default(), svg: Vec::default(),
svg_defs: String::new(), svg_defs: String::new(),
transform: DAffine2::IDENTITY, transform: DAffine2::IDENTITY,
image_data: Vec::new(), image_data: HashMap::new(),
indent: 0, indent: 0,
} }
} }
@@ -1239,6 +1239,7 @@ impl Render for Table<Raster<CPU>> {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) { fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
for row in self.iter() { for row in self.iter() {
let image = row.element; let image = row.element;
let transform = *row.transform; let transform = *row.transform;
if image.data.is_empty() { if image.data.is_empty() {
@@ -1246,16 +1247,10 @@ impl Render for Table<Raster<CPU>> {
} }
if render_params.to_canvas() { if render_params.to_canvas() {
let id = row.source_node_id.map(|x| x.0).unwrap_or_else(|| { let mut image_copy = image.clone();
let mut state = DefaultHasher::new(); image_copy.data_mut().map_pixels(|p| p.to_unassociated_alpha());
image.data().hash(&mut state); let id = *render.image_data.entry(image_copy.into_data()).or_insert_with(generate_uuid);
state.finish()
});
if !render.image_data.iter().any(|(old_id, _)| *old_id == id) {
let mut image = image.data().clone();
image.map_pixels(|p| p.to_unassociated_alpha());
render.image_data.push((id, image));
}
render.parent_tag( render.parent_tag(
"foreignObject", "foreignObject",
|attributes| { |attributes| {
+3 -2
View File
@@ -13,12 +13,13 @@ use graphic_types::raster_types::Image;
use graphic_types::raster_types::{CPU, Raster}; use graphic_types::raster_types::{CPU, Raster};
use rendering::{Render, RenderOutputType as RenderOutputTypeRequest, RenderParams, RenderSvgSegmentList, SvgRender, format_transform_matrix}; use rendering::{Render, RenderOutputType as RenderOutputTypeRequest, RenderParams, RenderSvgSegmentList, SvgRender, format_transform_matrix};
use rendering::{RenderMetadata, SvgSegment}; use rendering::{RenderMetadata, SvgSegment};
use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use vector_types::GradientStops; use vector_types::GradientStops;
use wgpu_executor::RenderContext; use wgpu_executor::RenderContext;
/// List of (canvas id, image data) pairs for embedding images as canvases in the final SVG string. /// List of (canvas id, image data) pairs for embedding images as canvases in the final SVG string.
type ImageData = Vec<(u64, Image<Color>)>; type ImageData = HashMap<Image<Color>, u64>;
#[derive(Clone, dyn_any::DynAny)] #[derive(Clone, dyn_any::DynAny)]
pub enum RenderIntermediateType { pub enum RenderIntermediateType {
@@ -162,7 +163,7 @@ async fn render<'a: 'n>(ctx: impl Ctx + ExtractFootprint + ExtractVarArgs, edito
rendering.wrap_with_transform(footprint.transform, Some(logical_resolution)); rendering.wrap_with_transform(footprint.transform, Some(logical_resolution));
RenderOutputType::Svg { RenderOutputType::Svg {
svg: rendering.svg.to_svg_string(), svg: rendering.svg.to_svg_string(),
image_data: rendering.image_data, image_data: rendering.image_data.into_iter().map(|(image, id)| (id, image)).collect(),
} }
} }
(RenderOutputTypeRequest::Vello, RenderIntermediateType::Vello(vello_data)) => { (RenderOutputTypeRequest::Vello, RenderIntermediateType::Vello(vello_data)) => {