mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-27 03:06:57 -06:00
Merge pull request #2440 from dotCooCoo/fix/raster-effect-export-huge-bounds
PDF export: Add Around is clamped when a file opens, and art too large for f64 gets no effect image
This commit is contained in:
@@ -170,6 +170,8 @@ pub(crate) fn effects_scale(doc: &Document) -> f64 {
|
||||
/// fills' and strokes' raster effects included). With `knockout`, the coverage of that art is knocked out
|
||||
/// of the image, which then only holds what the effects add around it (a shadow to go under the
|
||||
/// vector object). The document's raster effects settings add room around it and finish its pixels.
|
||||
/// `None` when it paints nothing, when the area it covers overflows `f64` or the scale is not
|
||||
/// positive, or when its PNG can't be encoded.
|
||||
pub(crate) fn effect_image(out: &mut Document, whole: &Node, knockout: Option<&Node>, scale: f64) -> Option<Node> {
|
||||
// What it paints (its members' effects included), and how far its fills' and strokes' raster
|
||||
// effects reach beyond that.
|
||||
@@ -177,8 +179,16 @@ pub(crate) fn effect_image(out: &mut Document, whole: &Node, knockout: Option<&N
|
||||
let look = out.raster_effects.clone();
|
||||
let reach = whole.appearance.items.iter().map(|i| effects::outset(i.effects(), b)).fold(0.0, f64::max) + 2.0 + look.add_around;
|
||||
let b = b.inflate(reach, reach);
|
||||
// An area too large for f64 gives a zero scale and an image transform that is not finite.
|
||||
let area = b.width() * b.height();
|
||||
if !area.is_finite() {
|
||||
return None;
|
||||
}
|
||||
// Whole pixels, and no larger than 64 Mpx.
|
||||
let scale = scale.min((64.0e6 / (b.width() * b.height()).max(1.0)).sqrt());
|
||||
let scale = scale.min((64.0e6 / area.max(1.0)).sqrt());
|
||||
if !(scale.is_finite() && scale > 0.0) {
|
||||
return None;
|
||||
}
|
||||
let region = Rect::new(b.x0, b.y0, b.x0 + (b.width() * scale).ceil().max(1.0) / scale, b.y0 + (b.height() * scale).ceil().max(1.0) / scale);
|
||||
// Anti-alias off: hard edges (the effects themselves stay smooth).
|
||||
let anti_alias = if look.anti_alias { AntiAlias::Art } else { AntiAlias::None };
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
//! raster effect images they make (PDF export, Expand Appearance) and the Rasterize defaults.
|
||||
|
||||
use serde_json::{Value, json};
|
||||
use vectorcraft_doc::rastersettings::MAX_ADD_AROUND;
|
||||
use vectorcraft_doc::{Background, Document, NodeId, NodeKind, RasterColorModel, RasterEffectsSettings};
|
||||
|
||||
use super::*;
|
||||
@@ -98,6 +99,60 @@ fn add_around_grows_the_effect_image() {
|
||||
assert_eq!((after.width() - before.width(), after.height() - before.height()), (72, 72));
|
||||
}
|
||||
|
||||
/// The transforms of the images in `doc` flattened for output.
|
||||
fn image_transforms(doc: &Document) -> Vec<[f64; 6]> {
|
||||
let mut xfs = vec![];
|
||||
crate::flatten_raster_effects(doc).unwrap().walk(|n| {
|
||||
if let NodeKind::Image(im) = &n.kind {
|
||||
xfs.push(im.xf.as_coeffs());
|
||||
}
|
||||
});
|
||||
xfs
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_file_with_add_around_out_of_range_opens_with_it_in_range() {
|
||||
let mut s = session();
|
||||
shadowed(&mut s);
|
||||
let mut d = (*s.doc().unwrap().doc).clone();
|
||||
d.raster_effects.add_around = 1e300;
|
||||
let back = vectorcraft_format::load(&vectorcraft_format::save_file(&d)).unwrap();
|
||||
assert_eq!(back.raster_effects.add_around, MAX_ADD_AROUND);
|
||||
let xfs = image_transforms(&back);
|
||||
assert!(!xfs.is_empty() && xfs.iter().flatten().all(|c| c.is_finite()), "{xfs:?}");
|
||||
// 1,000 pt on every side at 72 ppi.
|
||||
assert_eq!(effect_image(&back).width() - effect_image(&s.doc().unwrap().doc).width(), 2000);
|
||||
d.raster_effects.add_around = -5.0;
|
||||
assert_eq!(vectorcraft_format::load(&vectorcraft_format::save_file(&d)).unwrap().raster_effects.add_around, 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn art_too_large_for_f64_gets_no_effect_image() {
|
||||
let mut s = session();
|
||||
let r = shadowed(&mut s);
|
||||
// Settings out of range in a document made without opening a file.
|
||||
let mut d = (*s.doc().unwrap().doc).clone();
|
||||
d.raster_effects.add_around = 1e300;
|
||||
assert_eq!(image_transforms(&d), Vec::<[f64; 6]>::new());
|
||||
let mut d = (*s.doc().unwrap().doc).clone();
|
||||
let NodeKind::Path { path, live, .. } = &mut d.node_mut(r).unwrap().kind else { panic!("a path") };
|
||||
(*path, *live) = (vectorcraft_geom::shapes::rectangle(vectorcraft_geom::Rect::new(-1e300, -1e300, 1e300, 1e300)), None);
|
||||
assert_eq!(image_transforms(&d), Vec::<[f64; 6]>::new());
|
||||
// Expand Appearance of it by id, with nothing selected, puts no image into the document (the
|
||||
// rectangle at its own size gets one).
|
||||
let normal = (*s.doc().unwrap().doc).clone();
|
||||
let mut expanded_images = |d: Document| {
|
||||
let st = s.doc_mut().unwrap();
|
||||
(st.doc, st.selection) = (std::sync::Arc::new(d), Default::default());
|
||||
s.execute("effect.expandAppearance", &json!({"ids": [r.0]})).unwrap();
|
||||
let mut images = 0;
|
||||
s.doc().unwrap().doc.walk(|n| images += usize::from(matches!(n.kind, NodeKind::Image(_))));
|
||||
images
|
||||
};
|
||||
assert_eq!(expanded_images(normal), 1);
|
||||
assert_eq!(expanded_images(d), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_color_model_and_anti_alias_reach_the_pixels() {
|
||||
let mut s = session();
|
||||
|
||||
@@ -552,6 +552,7 @@ pub fn load_file(bytes: &[u8]) -> Result<NativeFile, FormatError> {
|
||||
// Untrusted: capped, named and pointing at objects the file has.
|
||||
doc.tidy_saved_selections();
|
||||
doc.tidy_graph_designs();
|
||||
doc.raster_effects.add_around = doc.raster_effects.add_around.clamp(0.0, vectorcraft_doc::rastersettings::MAX_ADD_AROUND);
|
||||
let mut depth_notes = vec![];
|
||||
doc.limit_depth_noting(&mut depth_notes);
|
||||
let profiles = f.profiles.into_iter().filter_map(|(name, icc)| Some((name, bytes_of(&icc)?))).collect();
|
||||
|
||||
+3
-2
@@ -2344,8 +2344,9 @@ written unless the whole batch succeeds puts the saves and exports last, or runs
|
||||
`document.rasterEffectsSettings` holds how raster effects (shadows, glows, blurs, feathers) become images when PDF
|
||||
export or Expand Appearance renders them: `resolution` (ppi, also New Document's `rasterEffectsPpi`), `colorModel`
|
||||
(the document's `rgb`/`cmyk`, `grayscale` or `bitmap`), `background` (`white` makes the images opaque), `antiAlias`
|
||||
(off: hard edges), `clippingMask` (the white stays under the art only), `addAround` (points of room around the art)
|
||||
and `preserveSpotColors` (stored). They are also the defaults of `object.rasterize`, whose params override them (its
|
||||
(off: hard edges), `clippingMask` (the white stays under the art only), `addAround` (0 to 1,000 points of room
|
||||
around the art; a file's value outside that range opens as the nearer end) and `preserveSpotColors` (stored). They are
|
||||
also the defaults of `object.rasterize`, whose params override them (its
|
||||
`clippingMask` puts the image in a clip group with the art's outline). No params reports them; any of them changes them
|
||||
in one undo step. PDF export renders the raster effects of every kind of object this way (paths, groups, layers, type,
|
||||
images, symbol instances, live objects, and those in opacity masks, symbols and pattern tiles): shadows and outer glows
|
||||
|
||||
Reference in New Issue
Block a user