PDF: colorized images that can't be exported are cached as missing

colorized_image returned before its cache insertion when the image was
missing, left out or undecodable, or when its color channel was larger
than MAX_RASTER_ALLOC. Every placement of such an image then decoded it
and computed its coverage again. Both cases now cache None for the key,
as load_image does.
This commit is contained in:
dotCooCoo
2026-10-31 05:42:41 +07:00
committed by bflatastic
parent 20f7f8be1e
commit 7d706ffa26
+39 -8
View File
@@ -57,10 +57,10 @@ impl Exporter<'_> {
/// that ink (a DeviceN space of the one ink, which lists its Separation space), Registration in
/// every process ink and every spot ink of the document, other colors in gray, RGB or CMYK as
/// the Output settings write them. The coverage is resampled as the Compression settings ask
/// ([`crate::images::coverage_sized`]). `None` when the paint isn't a solid color or the pixels
/// can't be decoded or its color channel exceeds [`vectorcraft_doc::MAX_RASTER_ALLOC`]; cached
/// like [`Self::load_image`]. With `interpolate` (and the export's own setting), viewers may
/// smooth it.
/// ([`crate::images::coverage_sized`]). `None` when the paint isn't a solid color, the image is
/// missing or left out, its pixels can't be decoded, or its color channel exceeds
/// [`vectorcraft_doc::MAX_RASTER_ALLOC`]; image rejections are cached like [`Self::load_image`].
/// With `interpolate` (and the export's own setting), viewers may smooth it.
pub(super) fn colorized_image(&mut self, im: &vectorcraft_doc::ImageObject, paint: &Paint, interpolate: bool) -> Option<Image> {
let Paint::Solid { color, swatch, tint } = paint else { return None };
let size = placed_size(im);
@@ -73,11 +73,15 @@ impl Exporter<'_> {
return i.clone();
}
let doc = self.doc;
let (width, height, alpha) = doc
let Some((width, height, alpha)) = doc
.image_blob(im)
.filter(|_| !self.left_out.contains(&im.key))
.and_then(|b| b.coverage())
.and_then(|(w, h, cover)| crate::images::coverage_sized(w, h, cover, size, self.compression))?;
.and_then(|(w, h, cover)| crate::images::coverage_sized(w, h, cover, size, self.compression))
else {
self.images.insert(cache, None);
return None;
};
let n = alpha.len();
let registration = swatch.as_deref() == Some(vectorcraft_color::swatch::REGISTRATION);
let inks: Vec<(String, [f32; 4])> = if registration {
@@ -103,7 +107,10 @@ impl Exporter<'_> {
other => (other.to_rgb().map(q).to_vec(), ImageColorspace::Rgb),
},
};
tinted_color_len(samples.len(), n)?;
if tinted_color_len(samples.len(), n).is_none() {
self.images.insert(cache, None);
return None;
}
let color = Arc::new(samples.repeat(n));
let img = Image::from_custom(Tinted { color, alpha: Arc::new(alpha), width, height, space, device_n }, interpolate).ok();
self.images.insert(cache, img.clone());
@@ -256,7 +263,31 @@ pub(super) fn embed(blob: &vectorcraft_doc::ImageBlob, interpolate: bool, sixtee
#[cfg(test)]
mod tests {
use super::tinted_color_len;
use super::{Exporter, tinted_color_len};
use vectorcraft_color::{Color, Paint};
use vectorcraft_doc::{Document, ImageBlob, ImageObject};
use vectorcraft_geom::Affine;
#[test]
fn colorized_image_caches_rejected_images() {
let mut doc = Document::new(10.0, 10.0);
doc.images.insert("bad".into(), ImageBlob::png(vec![1, 2, 3]));
doc.images.insert("left-out".into(), ImageBlob::png(vec![1, 2, 3]));
let settings = crate::PdfSettings::default();
let mut exporter = Exporter::new(&doc, &settings);
exporter.left_out.insert("left-out".into());
let paint = Paint::solid(Color::rgb(1.0, 0.0, 0.0));
for key in ["missing", "bad", "left-out"] {
let image =
ImageObject { key: key.into(), width: 1, height: 1, xf: Affine::IDENTITY, link: None, placement: Default::default(), pixels: None };
assert!(exporter.colorized_image(&image, &paint, true).is_none(), "{key}");
let cache = format!("{key}\u{0}{paint:?}\u{0}1.000x1.000");
assert!(matches!(exporter.images.get(&cache), Some(None)), "rejection for {key} was not cached");
assert!(exporter.colorized_image(&image, &paint, true).is_none(), "second call for {key}");
}
assert_eq!(exporter.images.len(), 3);
}
#[test]
fn tinted_color_rejects_oversized_and_overflowing_buffers() {