mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-29 12:17:14 -06:00
Envelopes: a pixelated CMYK image with its own grid mask is written without /Interpolate
PDF export writes a CMYK image whose opacity mask is a gray image on its own pixel grid with the mask as the image's soft mask. That path always asked viewers to smooth the image, so a pixelated piece of an envelope with Anti-Alias off was still written with /Interpolate when it had such a mask. It now takes the pixelated flag as images without a mask do, and caches the smoothed and pixelated versions apart.
This commit is contained in:
@@ -47,8 +47,8 @@ impl Exporter<'_> {
|
||||
/// on the image's own pixel grid (an image's soft mask as it opens): the alpha is written as the
|
||||
/// image's soft mask, as an RGBA image's is, rather than as a luminosity soft mask over it,
|
||||
/// through which CoreGraphics draws CMYK images on black boxes (#2159). `None` for other
|
||||
/// images and masks; cached like [`Self::load_image`].
|
||||
pub(super) fn masked_image(&mut self, im: &vectorcraft_doc::ImageObject, m: &vectorcraft_doc::OpacityMask) -> Option<Image> {
|
||||
/// images and masks; cached like [`Self::load_image`], `interpolate` as there.
|
||||
pub(super) fn masked_image(&mut self, im: &vectorcraft_doc::ImageObject, m: &vectorcraft_doc::OpacityMask, interpolate: bool) -> Option<Image> {
|
||||
let art = &m.art;
|
||||
let NodeKind::Image(mi) = &art.kind else { return None };
|
||||
let plain =
|
||||
@@ -57,14 +57,18 @@ impl Exporter<'_> {
|
||||
return None;
|
||||
}
|
||||
let size = placed_size(im);
|
||||
let cache = format!("{}\u{0}{}\u{0}{}\u{0}{:.3}x{:.3}", im.key, mi.key, m.clip, size.0, size.1);
|
||||
let interpolate = interpolate && self.interpolate;
|
||||
let mut cache = format!("{}\u{0}{}\u{0}{}\u{0}{:.3}x{:.3}", im.key, mi.key, m.clip, size.0, size.1);
|
||||
if !interpolate && self.interpolate {
|
||||
cache.push_str("\u{0}pixelated");
|
||||
}
|
||||
if let Some(i) = self.images.get(&cache) {
|
||||
return i.clone();
|
||||
}
|
||||
let doc = self.doc;
|
||||
let img = doc.image_blob(im).filter(|b| b.is_cmyk()).and_then(|blob| {
|
||||
let alpha = crate::images::mask_alpha(doc.image_blob(mi)?, im.width, im.height, m.clip)?;
|
||||
let r = crate::images::recode_cmyk(blob, size, self.compression, self.interpolate, self.out.cmyk_pixels(), Some(alpha));
|
||||
let r = crate::images::recode_cmyk(blob, size, self.compression, interpolate, self.out.cmyk_pixels(), Some(alpha));
|
||||
if let (Some(w), Some(_)) = (r.warning, &r.image) {
|
||||
self.warn(w);
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@ impl Exporter<'_> {
|
||||
}
|
||||
let mask = n.extras().mask.as_deref().filter(|m| !m.disabled);
|
||||
let alpha = match (&n.kind, mask) {
|
||||
(NodeKind::Image(im), Some(m)) => self.masked_image(im, m),
|
||||
(NodeKind::Image(im), Some(m)) => self.masked_image(im, m, !n.extras().pixelated),
|
||||
_ => None,
|
||||
};
|
||||
if let Some(m) = mask
|
||||
|
||||
@@ -439,6 +439,38 @@ fn a_cmyk_image_masked_by_a_grey_image_of_its_grid_writes_it_as_its_smask() {
|
||||
assert!(String::from_utf8_lossy(&bytes).contains("/S/Luminosity"), "a luminosity soft mask");
|
||||
}
|
||||
|
||||
/// A pixelated CMYK image (an envelope's with Anti-Alias off) masked by a grey image of its grid
|
||||
/// is written without /Interpolate, as one without a mask is.
|
||||
#[test]
|
||||
fn a_pixelated_cmyk_image_with_its_grid_mask_asks_viewers_not_to_smooth_it() {
|
||||
for pixelated in [false, true] {
|
||||
let mut d = Document::new_with_mode(100.0, 100.0, ColorMode::Cmyk);
|
||||
d.images.insert("img".into(), ImageBlob::cmyk_tiff(&Inks::new(2, 2, flat([0u8, 102, 255, 0], 4)).unwrap()).unwrap());
|
||||
d.images.insert("alpha".into(), ImageBlob::new("image/png", png(image::GrayImage::from_raw(2, 2, vec![255, 128, 0, 64]).unwrap())));
|
||||
let image = |key: &str| {
|
||||
let im = ImageObject {
|
||||
key: key.into(),
|
||||
width: 2,
|
||||
height: 2,
|
||||
xf: Affine::scale(20.0),
|
||||
link: None,
|
||||
placement: Default::default(),
|
||||
pixels: None,
|
||||
};
|
||||
NodeKind::Image(Box::new(im))
|
||||
};
|
||||
let mask = vectorcraft_doc::OpacityMask::new(Node::new(d.alloc_id(), image("alpha")), true);
|
||||
let mut n = Node::new(d.alloc_id(), image("img")).with_mask(Some(Box::new(mask)));
|
||||
n.extras_mut().pixelated = pixelated;
|
||||
let l = d.layers[0].id;
|
||||
d.insert(Some(l), 0, n).unwrap();
|
||||
let out = export_with_report(&d, &PdfOptions { created: Some(1_700_000_000), ..PdfOptions::uncompressed() }).unwrap().bytes;
|
||||
let text = String::from_utf8_lossy(&out);
|
||||
assert!(!text.contains("/S/Luminosity"), "the mask is the image's /SMask");
|
||||
assert_eq!(text.contains("/Interpolate"), !pixelated, "pixelated: {pixelated}");
|
||||
}
|
||||
}
|
||||
|
||||
/// #2950: Press Quality converts to the document's profile with its numbers kept, so in a CMYK
|
||||
/// document an RGB image comes out CMYK, as the press needs; with no conversion (the default
|
||||
/// preset) it stays RGB, as before.
|
||||
|
||||
Reference in New Issue
Block a user