mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-14 11:14:47 +00:00
Merge pull request #5817 from dotCooCoo/fix/ai-mask-placeholder-uncolored
Images: a colorized image's missing-link placeholder shows in its own colors
This commit is contained in:
@@ -180,6 +180,13 @@ impl ImageBlob {
|
||||
pub fn is_proxy(&self) -> bool {
|
||||
self.proxy.as_ref().is_some_and(|p| Arc::ptr_eq(p, &self.bytes))
|
||||
}
|
||||
|
||||
/// Does a colorize paint ([`crate::NodeExtras::colorize`]) show on these bytes? Not while
|
||||
/// they are a preview whose pixels aren't grayscale, such as [`missing_placeholder`]: until
|
||||
/// the file is relinked the image shows the preview as it is, and keeps the paint.
|
||||
pub fn takes_colorize(&self) -> bool {
|
||||
!self.is_proxy() || self.gray_source().is_some()
|
||||
}
|
||||
}
|
||||
|
||||
/// The side of [`missing_placeholder`]'s image, in pixels.
|
||||
|
||||
@@ -151,11 +151,16 @@ impl FlattenOptions {
|
||||
|
||||
/// Whether colorized image `n`, art of `d`, counts as transparency by itself. With
|
||||
/// [`Self::keeps_colorize`], an image colorized with a solid paint whose pixels are grayscale
|
||||
/// or 1-bit doesn't: the EPS writer draws it in that paint.
|
||||
/// or 1-bit doesn't: the EPS writer draws it in that paint. An image whose pixels don't take
|
||||
/// the paint ([`vectorcraft_doc::ImageBlob::takes_colorize`]) never counts: it draws uncolorized.
|
||||
pub(crate) fn counts_colorize(&self, d: &Document, n: &Node) -> bool {
|
||||
let NodeKind::Image(im) = &n.kind else { return true };
|
||||
let blob = d.image_blob(im);
|
||||
if blob.is_some_and(|b| !b.takes_colorize()) {
|
||||
return false;
|
||||
}
|
||||
let solid = matches!(n.extras().colorize.as_deref(), Some(Paint::Solid { .. }));
|
||||
!self.keeps_colorize || !solid || d.image_blob(im).is_some_and(|b| b.gray_source().is_none())
|
||||
!self.keeps_colorize || !solid || blob.is_some_and(|b| b.gray_source().is_none())
|
||||
}
|
||||
|
||||
/// `n` shows an image whose see-through pixels count as transparency.
|
||||
|
||||
@@ -411,6 +411,27 @@ fn eps_export_still_flattens_a_colorize_paint_on_a_color_image() {
|
||||
assert!(out["warnings"].as_array().unwrap().iter().any(|w| w.as_str().unwrap().contains("transparency is flattened")), "{}", out["warnings"]);
|
||||
}
|
||||
|
||||
/// A linked image showing the missing-link placeholder draws uncolorized, so its colorize paint
|
||||
/// doesn't flatten it in EPS or PDF 1.3 export.
|
||||
#[test]
|
||||
fn a_colorized_missing_link_placeholder_is_not_flattened() {
|
||||
let mut s = session();
|
||||
let bytes = std::sync::Arc::new(vectorcraft_doc::links::missing_placeholder().unwrap());
|
||||
let id = image(&mut s, "pic", ImageBlob { mime: "image/png".into(), bytes: bytes.clone(), proxy: Some(bytes) });
|
||||
s.edit("Colorize", |d, _| {
|
||||
d.node_mut(id).unwrap().extras_mut().colorize = Some(Box::new(Paint::solid(Color::rgb8(255, 0, 0))));
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
let flattened =
|
||||
|out: &serde_json::Value| out["warnings"].as_array().unwrap().iter().any(|w| w.as_str().unwrap().contains("transparency is flattened"));
|
||||
let eps = s.execute("document.exportEps", &json!({"previewFormat": "none", "useArtboards": false})).unwrap();
|
||||
assert!(!flattened(&eps), "{}", eps["warnings"]);
|
||||
let pdf = s.execute("document.exportPdf", &json!({"compatibility": "1.3"})).unwrap();
|
||||
assert!(!flattened(&pdf), "{}", pdf["warnings"]);
|
||||
assert_eq!(colorize(&s, id), Some(Paint::solid(Color::rgb8(255, 0, 0))), "the image keeps its paint");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flattened_colorized_image_shows_the_art_under_white_pixels() {
|
||||
let mut s = session();
|
||||
|
||||
@@ -196,7 +196,7 @@ impl Exporter<'_> {
|
||||
}
|
||||
NodeKind::Text(t) => self.text(s, n, t, page),
|
||||
NodeKind::Image(im) => match n.extras().colorize.as_deref() {
|
||||
Some(p @ Paint::Solid { .. }) => {
|
||||
Some(p @ Paint::Solid { .. }) if self.doc.image_blob(im).is_none_or(vectorcraft_doc::ImageBlob::takes_colorize) => {
|
||||
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);
|
||||
|
||||
@@ -9,7 +9,7 @@ use vectorcraft_color::cms::{self, Cms, Model, ProfileKind};
|
||||
use vectorcraft_color::swatch::REGISTRATION;
|
||||
use vectorcraft_doc::inks::{Link, inks, plates, visit_node_colors};
|
||||
use vectorcraft_doc::overprint::darken_overprints;
|
||||
use vectorcraft_doc::{ColorMode, Document, Node, NodeKind};
|
||||
use vectorcraft_doc::{ColorMode, Document, ImageBlob, Node, NodeKind};
|
||||
|
||||
use super::{DEFAULT_FREQUENCY, PrintInk, PrintSettings};
|
||||
|
||||
@@ -120,7 +120,7 @@ impl<'a> Separator<'a> {
|
||||
for n in nodes {
|
||||
darken_overprints(n, discard_white);
|
||||
self.node(plate, Arc::make_mut(n));
|
||||
keep_colorized_pixels(n, &mut colorized);
|
||||
keep_colorized_pixels(n, &d.images, &mut colorized);
|
||||
}
|
||||
// A colorized image's pixels say where its color prints, on every plate: they are kept
|
||||
// under their own key, and only the color is separated.
|
||||
@@ -153,13 +153,20 @@ fn colorized_key(key: &str) -> String {
|
||||
}
|
||||
|
||||
/// Point the colorized images in `n`'s subtree at [`colorized_key`], collecting their keys in
|
||||
/// `keys`.
|
||||
fn keep_colorized_pixels(n: &mut Arc<Node>, keys: &mut BTreeSet<String>) {
|
||||
/// `keys`. An image whose pixels in `images` don't take its colorize paint
|
||||
/// ([`ImageBlob::takes_colorize`]) loses the paint instead, and is separated as the image it shows.
|
||||
fn keep_colorized_pixels(n: &mut Arc<Node>, images: &BTreeMap<String, ImageBlob>, keys: &mut BTreeSet<String>) {
|
||||
let colorized = |n: &Node| matches!(n.kind, NodeKind::Image(ref im) if im.pixels.is_none()) && n.extras().colorize.is_some();
|
||||
if !colorized(n) && n.children().is_none_or(|ch| ch.is_empty()) {
|
||||
return;
|
||||
}
|
||||
let node = Arc::make_mut(n);
|
||||
if colorized(node)
|
||||
&& let NodeKind::Image(im) = &node.kind
|
||||
&& images.get(&im.key).is_some_and(|b| !b.takes_colorize())
|
||||
{
|
||||
node.extras_mut().colorize = None;
|
||||
}
|
||||
if colorized(node)
|
||||
&& let NodeKind::Image(im) = &mut node.kind
|
||||
{
|
||||
@@ -168,7 +175,7 @@ fn keep_colorized_pixels(n: &mut Arc<Node>, keys: &mut BTreeSet<String>) {
|
||||
}
|
||||
if let Some(ch) = node.children_mut() {
|
||||
for c in ch.iter_mut() {
|
||||
keep_colorized_pixels(c, keys);
|
||||
keep_colorized_pixels(c, images, keys);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,6 +166,34 @@ fn separations_print_the_image_on_its_ink_plate() {
|
||||
}
|
||||
}
|
||||
|
||||
/// A linked image showing the missing-link placeholder is written in the placeholder's own colors
|
||||
/// though it keeps a colorize paint for its file, and printed separations separate it as an
|
||||
/// uncolorized image.
|
||||
#[test]
|
||||
fn a_colorized_missing_link_placeholder_is_written_in_its_own_colors() {
|
||||
let placeholder = || {
|
||||
let mut d = doc(ColorMode::Cmyk, None);
|
||||
let bytes = Arc::new(vectorcraft_doc::links::missing_placeholder().unwrap());
|
||||
d.images.insert("pic".into(), ImageBlob { mime: "image/png".into(), bytes: bytes.clone(), proxy: Some(bytes) });
|
||||
d
|
||||
};
|
||||
let plain = placeholder();
|
||||
let mut d = placeholder();
|
||||
let id = d.layers[0].children().unwrap()[0].id;
|
||||
d.node_mut(id).unwrap().extras_mut().colorize = Some(Box::new(Paint::solid(Color::cmyk(1.0, 0.0, 0.0, 0.0))));
|
||||
let (colorized, uncolorized) = (images(&pdf(&d)), images(&pdf(&plain)));
|
||||
assert!(!colorized.is_empty());
|
||||
assert_eq!(colorized, uncolorized, "the placeholder as it is, without a soft mask from its coverage");
|
||||
let settings: PrintSettings = serde_json::from_value(serde_json::json!({"output": {"mode": "separations"}})).unwrap();
|
||||
let job = plan(&d, &PrintOptions { settings, ..Default::default() }).unwrap();
|
||||
assert!(!job.plates.is_empty());
|
||||
for p in &job.plates {
|
||||
let n = &p.layers[0].children().unwrap()[0];
|
||||
let NodeKind::Image(im) = &n.kind else { panic!("the image") };
|
||||
assert_eq!((im.key.as_str(), n.extras().colorize.is_none()), ("pic", true), "separated as the image it shows");
|
||||
}
|
||||
}
|
||||
|
||||
/// A pixelated colorized image (an envelope's with Anti-Alias off) is written without
|
||||
/// /Interpolate, as an uncolorized one is.
|
||||
#[test]
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
use super::{
|
||||
Affine, Arc, BezPath, Document, FillRule, Frame, HashMap, Ink, Node, Pixmap, Rect, RenderContext, Renderer, Shape, blob_key, blob_pixmap,
|
||||
fill_rule, image_mip, ink, lookup, mip_level, peniko, proof, vello_cpu,
|
||||
Affine, Arc, BezPath, Document, FillRule, Frame, HashMap, Ink, Node, NodeKind, Pixmap, Rect, RenderContext, Renderer, Shape, blob_key,
|
||||
blob_pixmap, fill_rule, image_mip, ink, lookup, mip_level, peniko, proof, vello_cpu,
|
||||
};
|
||||
use crate::images;
|
||||
|
||||
/// The color image node `n` is colorized with ([`vectorcraft_doc::NodeExtras::colorize`]), if it
|
||||
/// is, as `ink` paints it: the display color, or on an ink plane the color's own inks, as a fill
|
||||
/// of that color paints them.
|
||||
pub(super) fn image_tint(n: &Node, ink: Ink) -> Option<[u8; 3]> {
|
||||
/// The color image node `n` of `doc` is colorized with ([`vectorcraft_doc::NodeExtras::colorize`]),
|
||||
/// if it is and its pixels take it ([`vectorcraft_doc::ImageBlob::takes_colorize`]), as `ink`
|
||||
/// paints it: the display color, or on an ink plane the color's own inks, as a fill of that color
|
||||
/// paints them.
|
||||
pub(super) fn image_tint(doc: &Document, n: &Node, ink: Ink) -> Option<[u8; 3]> {
|
||||
let c = n.extras().colorize.as_deref()?.color()?;
|
||||
if let NodeKind::Image(im) = &n.kind
|
||||
&& doc.image_blob(im).is_some_and(|b| !b.takes_colorize())
|
||||
{
|
||||
return None;
|
||||
}
|
||||
Some(ink.rgb(&c).map(|v| (v.clamp(0.0, 1.0) * 255.0).round() as u8))
|
||||
}
|
||||
|
||||
|
||||
@@ -218,7 +218,7 @@ impl Renderer {
|
||||
&& let Some(region) = self.clip_of(&m.art)
|
||||
&& shape_masked_image(a, im, ®ion.0)
|
||||
{
|
||||
self.draw_image_in(ctx, f, im, Some(®ion), images::image_tint(a, f.ink), a.extras().pixelated);
|
||||
self.draw_image_in(ctx, f, im, Some(®ion), images::image_tint(f.doc, a, f.ink), a.extras().pixelated);
|
||||
self.stats.drawn += 1;
|
||||
return;
|
||||
}
|
||||
@@ -486,7 +486,7 @@ impl Renderer {
|
||||
&& let NodeKind::Image(im) = &image.kind
|
||||
&& plain_image(image, im, ®ion.0)
|
||||
{
|
||||
self.draw_image_in(ctx, f, im, Some(®ion), images::image_tint(image, f.ink), image.extras().pixelated);
|
||||
self.draw_image_in(ctx, f, im, Some(®ion), images::image_tint(f.doc, image, f.ink), image.extras().pixelated);
|
||||
self.stats.drawn += 1;
|
||||
return;
|
||||
}
|
||||
@@ -536,7 +536,7 @@ impl Renderer {
|
||||
}
|
||||
}
|
||||
NodeKind::Text(t) => self.draw_text(ctx, f, n, t),
|
||||
NodeKind::Image(im) => self.draw_image_in(ctx, f, im, None, images::image_tint(n, f.ink), n.extras().pixelated),
|
||||
NodeKind::Image(im) => self.draw_image_in(ctx, f, im, None, images::image_tint(f.doc, n, f.ink), n.extras().pixelated),
|
||||
NodeKind::SymbolInstance { symbol, xf } => {
|
||||
if let Some(sym) = f.doc.symbols.iter().find(|s| &s.name == symbol) {
|
||||
// The instance draws when the frame has room for its art's objects; a symbol
|
||||
|
||||
@@ -142,6 +142,27 @@ fn colorize_dims_with_the_grays() {
|
||||
assert!(px[1].abs_diff(128) <= 3 && px[2].abs_diff(127) <= 3 && px[0] <= 2, "got {px:?}");
|
||||
}
|
||||
|
||||
/// A linked image showing the missing-link placeholder draws it in its own colors though it keeps
|
||||
/// a colorize paint for its file; a grayscale preview still draws colorized.
|
||||
#[test]
|
||||
fn a_colorized_missing_link_placeholder_draws_in_its_own_colors() {
|
||||
let proxy = |bytes: Vec<u8>| {
|
||||
let bytes = std::sync::Arc::new(bytes);
|
||||
ImageBlob { mime: "image/png".into(), bytes: bytes.clone(), proxy: Some(bytes) }
|
||||
};
|
||||
let blue = Paint::solid(Color::rgb(0.0, 0.0, 1.0));
|
||||
let sharp = RenderOptions { smooth_images: false, ..Default::default() };
|
||||
let placeholder = proxy(vectorcraft_doc::links::missing_placeholder().unwrap());
|
||||
// 64 pixels at 0.5 pt: (16, 5) is the light tile, (5, 5) the cross.
|
||||
let d = doc_of(placeholder, Some(blue.clone()), (64, 64), 0.5);
|
||||
let out = Renderer::new().render(&d, 40, 20, Affine::IDENTITY, &sharp);
|
||||
assert!(near(out.pixel(16, 5), [236, 236, 236, 255], 2), "the light tile, got {:?}", out.pixel(16, 5));
|
||||
assert!(near(out.pixel(5, 5), [200, 60, 60, 255], 2), "the red cross, got {:?}", out.pixel(5, 5));
|
||||
let d = doc(proxy(gray_png()), Some(blue));
|
||||
let px = Renderer::new().render(&d, 40, 20, Affine::IDENTITY, &sharp).pixel(10, 10);
|
||||
assert!(px[2] >= 250 && px[0] <= 5 && px[1] <= 5, "a grayscale preview takes the color, got {px:?}");
|
||||
}
|
||||
|
||||
/// Separations Preview shows a spot-colorized image on its ink's plate only.
|
||||
#[test]
|
||||
fn separations_preview_shows_the_image_on_its_spot_plate() {
|
||||
|
||||
Reference in New Issue
Block a user