EPS import: CMYK images keep their ink amounts

The PostScript interpreter that reads EPS and PostScript files turned
the samples of a CMYK image into display RGB and stored an RGBA PNG,
so the image lost its ink amounts. The DCTDecode filter decoded a CMYK
JPEG to RGB, so it lost its ink amounts too. The PDF importer keeps
the ink amounts of 8- and 16-bit CMYK images (in a CMYK TIFF, or the
CMYK JPEG itself).

A CMYK image (image or colorimage in DeviceCMYK, CIEBasedDEFG or a
four-component ICCBased space) now keeps its samples in a CMYK TIFF
(ImageBlob::cmyk_tiff), read through its Decode array, at every bit
depth the interpreter reads. A CMYK JPEG gives its samples as stored,
and the image's Decode array reads them: [1 0 1 0 1 0 1 0] undoes the
inverted samples of Adobe's CMYK JPEGs. The pixels that a MaskColor
or an ImageType 3 mask hides become a grayscale opacity mask over the
image, as in PDF import. A mask that hides nothing adds no mask. Gray,
RGB, indexed and spot color images and image masks stay RGBA PNGs.

Docs: docs/file-format-parity.md, docs/target-app-parity.md.
This commit is contained in:
dotCooCoo
2026-10-16 06:16:35 +07:00
committed by bflatastic
parent 28cb75cc59
commit 3b327d9512
6 changed files with 248 additions and 26 deletions
@@ -65,6 +65,34 @@ fn other_eps_files_are_read_by_the_postscript_interpreter() {
assert_eq!(s.doc().unwrap().title(), "plain.eps");
}
/// A CMYK image the PostScript interpreter reads keeps its ink amounts when the EPS opens and when
/// it is placed, with its transparent pixel as its opacity mask.
#[test]
fn cmyk_images_keep_their_inks_when_an_eps_opens_or_is_placed() {
let eps = b"%!PS-Adobe-3.0 EPSF-3.0\n%%BoundingBox: 0 0 60 20\n%%EndComments\n/DeviceCMYK setcolorspace 60 20 scale \
<< /ImageType 4 /Width 2 /Height 1 /BitsPerComponent 8 /Decode [0 1 0 1 0 1 0 1] /ImageMatrix [2 0 0 -1 0 1] \
/MaskColor [10 20 30 40] /DataSource <0a141e28 00667f08> >> image\nshowpage\n%%EOF\n";
// Each image object's ink amounts (`None` when it isn't CMYK) and whether it has a mask.
let images = |s: &Session| {
let d = &s.doc().unwrap().doc;
let mut found = vec![];
d.walk(|n| {
if let vectorcraft_doc::NodeKind::Image(im) = &n.kind {
found.push((d.images[&im.key].cmyk().map(|i| i.data), n.extras().mask.is_some()));
}
});
found
};
let want = (Some(vec![10, 20, 30, 40, 0, 102, 127, 8]), true);
let mut s = Session::new();
let r = open(&mut s, "cmyk.eps", eps).unwrap();
assert_eq!(r["format"], "eps", "{r}");
assert_eq!(images(&s), std::slice::from_ref(&want));
let r = s.execute("file.place", &json!({"name": "cmyk.eps", "dataBase64": vectorcraft_format::base64_encode(eps)})).unwrap();
assert_eq!(r["ids"].as_array().unwrap().len(), 1, "{r}");
assert_eq!(images(&s), [want.clone(), want]);
}
#[test]
fn an_unreadable_eps_file_comes_in_as_its_preview() {
let mut s = Session::new();