Legacy .ai export: warns when an image in art it flattens or expands can't be decoded

Saving a version 7 .ai expands raster effects into images and flattens
images with see-through pixels, opacity or a blending mode, and both
draw the art with the renderer. An image the renderer couldn't decode
drew nothing, so it was missing from the result, and the reply carried
no note. A semi-transparent image cut short was removed without one.

The save now compares decode_failures() before and after it writes the
artboards, as Expand Appearance and the raster exports do, and adds
"an image couldn't be decoded and is missing from the picture" to its
warnings once.

Docs: docs/mcp.md (aiVersion "7").
This commit is contained in:
dotCooCoo
2026-10-18 12:16:40 +07:00
committed by bflatastic
parent d66077a15f
commit d7e79448d7
3 changed files with 54 additions and 2 deletions
+9 -1
View File
@@ -15,7 +15,9 @@
//! blending mode written opaque is a box hiding the art under it (version 7 images have no alpha
//! channel, §8.1). With `flatten` (the default) each becomes an opaque image of itself and the art
//! under it, rendered from the document as it is, clipped to the area where it changes what shows;
//! the art under it stays as it was. With `omit` they are left out.
//! the art under it stays as it was. With `omit` they are left out. An image that can't be decoded
//! is missing from the images that flattening and expanding raster effects draw, and the warnings
//! then carry the raster exports' note once.
//!
//! The format can't hide objects, only layers (`hiddenObjects`): with `layer` (the default) each
//! layer's hidden objects go to a hidden layer of their own just above it, with `omit` they are
@@ -104,6 +106,7 @@ pub(super) fn encode(cmd: &str, f: &Format, doc: &Document, p: &Value) -> Result
enc.warnings.push(SEPARATE.to_string());
}
let created = vectorcraft_doc::metadata::now_unix();
let failed = vectorcraft_render::decode_failures();
for b in boards {
let one = super::save::artboard_doc(doc, b);
let (full, placed) = crate::cmd::place::document::full_documents(&one);
@@ -127,6 +130,11 @@ pub(super) fn encode(cmd: &str, f: &Format, doc: &Document, p: &Value) -> Result
}
enc.files.push((Some(b), out.bytes));
}
// Expanding raster effects and flattening images draw the art, and an image the renderer
// couldn't decode drew nothing there.
if let Some(w) = super::super::rasterfx::undecoded_warning(failed) {
note(&mut enc.warnings, w);
}
Ok(enc)
}
+42
View File
@@ -3,6 +3,7 @@
use serde_json::{Value, json};
use super::tests_menucmds::place_undecodable_png;
use super::*;
use crate::tests_links::Folder;
@@ -262,6 +263,47 @@ fn a_transparent_image_is_flattened_with_the_art_under_it_which_stays() {
assert_eq!(colour_at(page, 10.0, 20.0), [255, 0, 0], "the square outside the image");
}
/// How many of reply `r`'s warnings say that an image couldn't be decoded, as raster exports do.
fn undecoded_notes(r: &Value) -> usize {
warnings(r).iter().filter(|w| *w == "an image couldn't be decoded and is missing from the picture").count()
}
#[test]
fn a_flattened_image_that_cant_be_decoded_is_noted_once() {
let mut s = Session::new();
s.execute("file.new", &json!({"width": 200, "height": 100})).unwrap();
s.execute("artboard.new", &json!({"x": 0, "y": 0, "width": 200, "height": 100, "name": "B"})).unwrap();
let cut = place_undecodable_png(&mut s);
s.execute("transparency.set", &json!({"ids": [cut.0], "opacity": 50})).unwrap();
// Both artboards flatten it.
let r = s.execute("file.saveCopy", &json!({"format": "ai", "aiVersion": "7"})).unwrap();
assert_eq!(r["files"].as_array().map(Vec::len), Some(2), "{r}");
assert!(warnings(&r).iter().any(|w| w.contains("flattened with the art under them")), "{r}");
assert_eq!(undecoded_notes(&r), 1, "{r}");
}
#[test]
fn an_image_that_cant_be_decoded_with_an_expanded_effect_is_noted() {
let mut s = Session::new();
s.execute("file.new", &json!({"width": 200, "height": 100})).unwrap();
let cut = place_undecodable_png(&mut s);
s.execute("effect.apply", &json!({"effect": "blur.gaussian", "params": {"radius": 3}, "ids": [cut.0]})).unwrap();
// Left out rather than flattened: only expanding the blur draws it.
let r = s.execute("file.saveCopy", &json!({"format": "ai", "aiVersion": "7", "transparentImages": "omit"})).unwrap();
assert_eq!(undecoded_notes(&r), 1, "{r}");
}
#[test]
fn images_that_decode_give_no_decode_note() {
let mut s = shadow_over_square();
s.execute("effect.apply", &json!({"effect": "blur.gaussian", "params": {"radius": 3}})).unwrap(); // the blue image
for mode in ["flatten", "omit"] {
let r = s.execute("file.saveCopy", &json!({"format": "ai", "aiVersion": "7", "transparentImages": mode})).unwrap();
assert!(warnings(&r).iter().any(|w| w.contains("transparent images")), "{r}");
assert_eq!(undecoded_notes(&r), 0, "{r}");
}
}
#[test]
fn transparent_images_can_be_left_out_and_opacity_makes_one_transparent() {
let mut s = shadow_over_square();
+3 -1
View File
@@ -690,7 +690,9 @@ PostScript form, which keeps layers, groups, compound paths and clipping groups
of `range` (default all; `{name}-{artboard}.ai` when several, listed in the result's `files`), with
`transparentImages: "flatten"` (default) or `"omit"` for images with see-through pixels, opacity or a blending mode,
`hiddenObjects: "layer"` (default: a hidden layer of each layer's hidden objects), `"omit"` or `"visible"`, and
`outlineText: true` to write type as outlines instead of text objects. From the CLI: `vectorcraft-cli convert in.svg
`outlineText: true` to write type as outlines instead of text objects. When an image in art that is flattened or whose
raster effects are expanded can't be decoded, the result does not show it, and `warnings` carries "an image couldn't
be decoded and is missing from the picture" once. From the CLI: `vectorcraft-cli convert in.svg
out.ai --ai-version 7 [--hidden-objects layer|omit|visible] [--transparent-images flatten|omit] [--outline-text]
[--range R]`.