Envelopes: Anti-Alias and Preserve Shape Using change how distorted images draw

Envelope Options stored Anti-Alias and Preserve Shape Using, but nothing that
draws an image read them: a distorted image was always smoothed and always
cut into clip groups.

- With Anti-Alias off, each piece of a distorted image is marked pixelated
  (saved as `pixelated`). The renderer draws it with nearest-neighbor
  sampling and without mips, SVG export writes `image-rendering: pixelated`,
  and PDF export writes it without /Interpolate.
- With Preserve Shape Using Transparency, each piece is the image with a
  white opacity mask in the piece's shape instead of a clip group. The
  renderer paints an image whose mask is one plain white path straight into
  that path, as it does for an image in a clip group, when the path lies
  inside the image's frame.

Docs: docs/mcp.md, docs/target-app-parity.md.
This commit is contained in:
dotCooCoo
2026-10-29 11:07:57 +07:00
committed by bflatastic
parent 4651e80973
commit ab69d8f6d9
15 changed files with 345 additions and 28 deletions
+13 -8
View File
@@ -4,17 +4,21 @@ use super::{BlendMode, Exporter, Image, NodeKind, Rect, Size, Surface};
impl Exporter<'_> {
/// Image `im` placed `size` points wide and high, resampled and compressed as the Compression
/// settings say ([`crate::images::recode`], [`crate::images::recode_cmyk`] for CMYK images);
/// cached by key (and size when images are resampled). `None`, with [`UNDECODED`] among the
/// warnings, for an image that is missing, can't be decoded or is in [`Self::left_out`].
fn load_image(&mut self, im: &vectorcraft_doc::ImageObject, size: (f64, f64)) -> Option<Image> {
/// cached by key (and size when images are resampled). With `interpolate` (and the export's
/// own setting), viewers may smooth it. `None`, with [`UNDECODED`] among the warnings, for an
/// image that is missing, can't be decoded or is in [`Self::left_out`].
fn load_image(&mut self, im: &vectorcraft_doc::ImageObject, size: (f64, f64), interpolate: bool) -> Option<Image> {
let key = im.key.as_str();
let c = self.compression;
let sized = [c.color.downsample, c.gray.downsample, c.mono.downsample].iter().any(|d| *d != crate::Downsample::None);
let cache = if sized { format!("{key}\u{0}{:.3}x{:.3}", size.0, size.1) } else { key.to_string() };
let interpolate = interpolate && self.interpolate;
let mut cache = if sized { format!("{key}\u{0}{:.3}x{:.3}", size.0, size.1) } else { key.to_string() };
if !interpolate && self.interpolate {
cache.push_str("\u{0}pixelated");
}
if let Some(i) = self.images.get(&cache) {
return i.clone();
}
let interpolate = self.interpolate;
let doc = self.doc;
let out = self.out.clone();
let img = doc.image_blob(im).filter(|_| !self.left_out.contains(key)).and_then(|blob| {
@@ -70,13 +74,14 @@ impl Exporter<'_> {
img
}
/// Draw image `im`: `img` when given (its pixels already written), else as it is loaded.
pub(super) fn image(&mut self, s: &mut Surface, im: &vectorcraft_doc::ImageObject, img: Option<Image>) {
/// Draw image `im`: `img` when given (its pixels already written), else as it is loaded; with
/// `pixelated` ([`vectorcraft_doc::NodeExtras::pixelated`]) viewers are asked not to smooth it.
pub(super) fn image(&mut self, s: &mut Surface, im: &vectorcraft_doc::ImageObject, img: Option<Image>, pixelated: bool) {
// An image placed or sized beyond [`MAX_COORD`] is left out.
if !writable(s, im.xf, Rect::new(0.0, 0.0, im.width.max(1).into(), im.height.max(1).into())) {
return;
}
let Some(img) = img.or_else(|| self.load_image(im, placed_size(im))) else { return };
let Some(img) = img.or_else(|| self.load_image(im, placed_size(im), !pixelated)) else { return };
let Some(size) = Size::from_wh(im.width.max(1) as f32, im.height.max(1) as f32) else { return };
s.push_transform(&xf(im.xf));
s.draw_image(img, size);
+1 -1
View File
@@ -182,7 +182,7 @@ impl Exporter<'_> {
self.shape(s, n, &bp, *rule, page);
}
NodeKind::Text(t) => self.text(s, n, t, page),
NodeKind::Image(im) => self.image(s, im, alpha),
NodeKind::Image(im) => self.image(s, im, alpha, n.extras().pixelated),
NodeKind::SymbolInstance { symbol, xf } => {
// Within the objects a frame may draw of symbol instances, as on screen: nested
// instances multiply.