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 17:03:18 +05:30 committed by GitHub
parent ee2e61f38f
commit 42440c0d0b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 27 additions and 17 deletions

View File

@ -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 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)]
#[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))]
pub struct Color {
red: f32,
@ -225,6 +225,14 @@ pub struct Color {
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)]
impl Hash for Color {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {

View File

@ -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 width: 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).
}
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)]
pub struct TransformImage(pub DAffine2);

View File

@ -26,7 +26,7 @@ use kurbo::Shape;
use num_traits::Zero;
use std::collections::{HashMap, HashSet};
use std::fmt::Write;
use std::hash::{DefaultHasher, Hash, Hasher};
use std::hash::{Hash, Hasher};
use std::ops::Deref;
use std::sync::{Arc, LazyLock};
use vello::*;
@ -59,7 +59,7 @@ pub struct SvgRender {
pub svg: Vec<SvgSegment>,
pub svg_defs: String,
pub transform: DAffine2,
pub image_data: Vec<(u64, Image<Color>)>,
pub image_data: HashMap<Image<Color>, u64>,
indent: usize,
}
@ -69,7 +69,7 @@ impl SvgRender {
svg: Vec::default(),
svg_defs: String::new(),
transform: DAffine2::IDENTITY,
image_data: Vec::new(),
image_data: HashMap::new(),
indent: 0,
}
}
@ -1239,6 +1239,7 @@ impl Render for Table<Raster<CPU>> {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
for row in self.iter() {
let image = row.element;
let transform = *row.transform;
if image.data.is_empty() {
@ -1246,16 +1247,10 @@ impl Render for Table<Raster<CPU>> {
}
if render_params.to_canvas() {
let id = row.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) {
let mut image = image.data().clone();
image.map_pixels(|p| p.to_unassociated_alpha());
render.image_data.push((id, image));
}
let mut image_copy = image.clone();
image_copy.data_mut().map_pixels(|p| p.to_unassociated_alpha());
let id = *render.image_data.entry(image_copy.into_data()).or_insert_with(generate_uuid);
render.parent_tag(
"foreignObject",
|attributes| {

View File

@ -13,12 +13,13 @@ use graphic_types::raster_types::Image;
use graphic_types::raster_types::{CPU, Raster};
use rendering::{Render, RenderOutputType as RenderOutputTypeRequest, RenderParams, RenderSvgSegmentList, SvgRender, format_transform_matrix};
use rendering::{RenderMetadata, SvgSegment};
use std::collections::HashMap;
use std::sync::Arc;
use vector_types::GradientStops;
use wgpu_executor::RenderContext;
/// 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)]
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));
RenderOutputType::Svg {
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)) => {