mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-01 05:37:23 -06:00
Merge pull request #4716 from dotCooCoo/fix/pdf-image-repeat-bound
PDF export: bound a colorized image's color channel
This commit is contained in:
@@ -3,6 +3,12 @@ use super::{Arc, BlendMode, Color, Exporter, Image, NodeKind, Paint, Rect, Size,
|
||||
use krilla::color::device_n::{Colorant, DeviceNSpace, Process};
|
||||
use krilla::image::{BitsPerComponent, CustomImage, ImageColorspace};
|
||||
|
||||
/// The byte count of a repeated color channel, within [`vectorcraft_doc::MAX_RASTER_ALLOC`].
|
||||
fn tinted_color_len(samples: usize, pixels: usize) -> Option<usize> {
|
||||
let bytes = samples.checked_mul(pixels)?;
|
||||
(u64::try_from(bytes).ok()? <= vectorcraft_doc::MAX_RASTER_ALLOC).then_some(bytes)
|
||||
}
|
||||
|
||||
/// A colorized image's pixels: one color in every pixel, shown as much as its soft mask says.
|
||||
#[derive(Clone, Hash)]
|
||||
struct Tinted {
|
||||
@@ -52,8 +58,9 @@ impl Exporter<'_> {
|
||||
/// 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; cached like [`Self::load_image`]. With `interpolate` (and the export's own
|
||||
/// setting), viewers may smooth it.
|
||||
/// 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.
|
||||
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);
|
||||
@@ -96,6 +103,7 @@ impl Exporter<'_> {
|
||||
other => (other.to_rgb().map(q).to_vec(), ImageColorspace::Rgb),
|
||||
},
|
||||
};
|
||||
tinted_color_len(samples.len(), n)?;
|
||||
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());
|
||||
@@ -245,3 +253,16 @@ pub(super) fn embed(blob: &vectorcraft_doc::ImageBlob, interpolate: bool, sixtee
|
||||
Some(Image::from_rgba8(rgba.into_raw(), w, h))
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::tinted_color_len;
|
||||
|
||||
#[test]
|
||||
fn tinted_color_rejects_oversized_and_overflowing_buffers() {
|
||||
let max = usize::try_from(vectorcraft_doc::MAX_RASTER_ALLOC).unwrap_or(usize::MAX);
|
||||
assert_eq!(tinted_color_len(4, max / 4), Some(max));
|
||||
assert_eq!(tinted_color_len(4, max / 4 + 1), None);
|
||||
assert_eq!(tinted_color_len(4, usize::MAX), None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::exporter::{address, blend, color, constant_mask, cover, in_range, norm, rects_overlap, rule, to_path, writable, xf};
|
||||
use super::exporter::{UNDECODED, address, blend, color, constant_mask, cover, in_range, norm, rects_overlap, rule, to_path, writable, xf};
|
||||
use super::{
|
||||
Affine, AppearanceItem, BezPath, BlendMode, Exporter, Fill, FillRule, LineCap, LineJoin, Mark, Node, NodeKind, NormalizedF32, Paint, Path, Rect,
|
||||
Shape, Stroke, StrokeAlign, StrokeDash, StrokeLayer, Surface, WrittenShape, krilla, stroke,
|
||||
@@ -182,10 +182,18 @@ impl Exporter<'_> {
|
||||
self.shape(s, n, &bp, *rule, page);
|
||||
}
|
||||
NodeKind::Text(t) => self.text(s, n, t, page),
|
||||
NodeKind::Image(im) => {
|
||||
let colorized = n.extras().colorize.as_deref().and_then(|p| self.colorized_image(im, p, !n.extras().pixelated));
|
||||
self.image(s, im, colorized.or(alpha), n.extras().pixelated)
|
||||
}
|
||||
NodeKind::Image(im) => match n.extras().colorize.as_deref() {
|
||||
Some(p @ Paint::Solid { .. }) => {
|
||||
if writable(s, im.xf, Rect::new(0.0, 0.0, im.width.max(1).into(), im.height.max(1).into())) {
|
||||
if let Some(colorized) = self.colorized_image(im, p, !n.extras().pixelated) {
|
||||
self.image(s, im, Some(colorized), n.extras().pixelated);
|
||||
} else {
|
||||
self.warn(UNDECODED);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => 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.
|
||||
|
||||
Reference in New Issue
Block a user