mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 17:56:50 -06:00
Merge pull request #2514 from vangel76/fix/2415-overprint-same-colour
Overprint Preview: a colour overprinting itself stays that colour (#2415)
This commit is contained in:
@@ -94,26 +94,27 @@ impl Node {
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw the overprinting fills and strokes of `a`'s subtree with Multiply (those with a blend
|
||||
/// Draw the overprinting fills and strokes of `a`'s subtree with Darken (those with a blend
|
||||
/// mode of their own keep it), copying only the nodes on the way to them: on screen and on a
|
||||
/// printed plate this approximates their inks printing over the inks below. With `discard_white`
|
||||
/// printed plate this approximates their inks printing over the inks below. Darken keeps the
|
||||
/// stronger ink of each plate (and each additive channel), so a colour overprinting itself stays
|
||||
/// that colour, where Multiply would darken it. With `discard_white`
|
||||
/// (Document Setup → Discard White Overprint) white fills and strokes knock out instead.
|
||||
pub fn multiply_overprints(a: &mut Arc<Node>, discard_white: bool) {
|
||||
pub fn darken_overprints(a: &mut Arc<Node>, discard_white: bool) {
|
||||
if !a.has_overprint() {
|
||||
return;
|
||||
}
|
||||
let n = Arc::make_mut(a);
|
||||
let multiplies =
|
||||
|overprint: bool, blend: BlendMode, paint: &Paint| overprint && blend == BlendMode::Normal && !(discard_white && is_white(paint));
|
||||
let darkens = |overprint: bool, blend: BlendMode, paint: &Paint| overprint && blend == BlendMode::Normal && !(discard_white && is_white(paint));
|
||||
for it in &mut n.appearance.items {
|
||||
match it {
|
||||
crate::AppearanceItem::Fill(l) if multiplies(l.overprint, l.blend, &l.paint) => l.blend = BlendMode::Multiply,
|
||||
crate::AppearanceItem::Stroke(l) if multiplies(l.overprint, l.blend, &l.paint) => l.blend = BlendMode::Multiply,
|
||||
crate::AppearanceItem::Fill(l) if darkens(l.overprint, l.blend, &l.paint) => l.blend = BlendMode::Darken,
|
||||
crate::AppearanceItem::Stroke(l) if darkens(l.overprint, l.blend, &l.paint) => l.blend = BlendMode::Darken,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
for c in n.children_mut().into_iter().flatten() {
|
||||
multiply_overprints(c, discard_white);
|
||||
darken_overprints(c, discard_white);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ use kurbo::{Affine, Rect};
|
||||
use serde::Serialize;
|
||||
use vectorcraft_color::cms::{self, ProfileKind};
|
||||
use vectorcraft_doc::marks::PrinterMarks;
|
||||
use vectorcraft_doc::overprint::{clear_overprints, multiply_overprints};
|
||||
use vectorcraft_doc::overprint::{clear_overprints, darken_overprints};
|
||||
use vectorcraft_doc::{ColorMode, Document, Node, NodeKind};
|
||||
|
||||
pub use layout::{MAX_TILES, TileGrid};
|
||||
@@ -167,7 +167,7 @@ pub fn printed_document<'a>(doc: &'a Document, set: &PrintSettings) -> Cow<'a, D
|
||||
}
|
||||
|
||||
/// Overprinting fills and strokes in composite output: discarded (they knock out) or simulated
|
||||
/// (drawn with Multiply, as Overprint Preview shows them), after which nothing overprints any
|
||||
/// (drawn with Darken, as Overprint Preview shows them), after which nothing overprints any
|
||||
/// more; or preserved. Separations always honour them.
|
||||
fn composite_overprints(doc: &mut Document, set: &PrintSettings) {
|
||||
let how = set.advanced.overprints;
|
||||
@@ -179,7 +179,7 @@ fn composite_overprints(doc: &mut Document, set: &PrintSettings) {
|
||||
let nodes = doc.layers.iter_mut().chain(symbols).chain(doc.patterns.iter_mut().flat_map(|p| p.art.iter_mut()));
|
||||
for n in nodes {
|
||||
if how == PrintOverprints::Simulate {
|
||||
multiply_overprints(n, discard_white);
|
||||
darken_overprints(n, discard_white);
|
||||
}
|
||||
clear_overprints(n);
|
||||
}
|
||||
@@ -411,7 +411,7 @@ fn print_inner(doc: &Document, opts: &PrintOptions) -> Result<PrintReport, PdfEr
|
||||
ex.intent = opts.settings.color.intent;
|
||||
// The writer's colour output: the printer profile's conversion.
|
||||
ex.out = w.out.clone();
|
||||
// Composite output keeps overprints as the Advanced option says (plates multiply them).
|
||||
// Composite output keeps overprints as the Advanced option says (plates darken them).
|
||||
ex.overprint = !separations && pdf.advanced.overprint == Overprint::Preserve;
|
||||
}
|
||||
for &i in &plan.order {
|
||||
|
||||
@@ -7,7 +7,7 @@ use vectorcraft_color::Color;
|
||||
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::multiply_overprints;
|
||||
use vectorcraft_doc::overprint::darken_overprints;
|
||||
use vectorcraft_doc::{ColorMode, Document, Node};
|
||||
|
||||
use super::{DEFAULT_FREQUENCY, PrintInk, PrintSettings};
|
||||
@@ -93,14 +93,14 @@ impl<'a> Separator<'a> {
|
||||
}
|
||||
|
||||
/// The document as plate `plate`: art, symbols, pattern tiles and images in the grey of the
|
||||
/// ink's coverage. Overprinting fills and strokes multiply, so where they have none of this
|
||||
/// ink's coverage. Overprinting fills and strokes darken, so where they have none of this
|
||||
/// ink the plate below shows through instead of being knocked out.
|
||||
pub fn plate(&self, plate: &str) -> Document {
|
||||
let mut d = self.doc.clone();
|
||||
let discard_white = d.setup.discard_white_overprint;
|
||||
let nodes = d.layers.iter_mut().chain(d.symbols.iter_mut().map(|s| &mut s.art)).chain(d.patterns.iter_mut().flat_map(|p| p.art.iter_mut()));
|
||||
for n in nodes {
|
||||
multiply_overprints(n, discard_white);
|
||||
darken_overprints(n, discard_white);
|
||||
self.node(plate, Arc::make_mut(n));
|
||||
}
|
||||
for blob in d.images.values_mut() {
|
||||
|
||||
@@ -98,13 +98,13 @@ fn discarded_and_simulated_overprints_write_no_overprint() {
|
||||
assert!(preserve.warnings.is_empty(), "{:?}", preserve.warnings);
|
||||
assert!(String::from_utf8_lossy(&preserve.bytes).contains("/OP true/op true/OPM 1"), "preserved overprints overprint in the file");
|
||||
assert!(overprints(&printed_document(&d, &settings(json!({})))), "preserved");
|
||||
for (how, multiplies) in [("discard", false), ("simulate", true)] {
|
||||
for (how, darkens) in [("discard", false), ("simulate", true)] {
|
||||
let set = json!({"advanced": {"overprints": how}});
|
||||
assert!(!overprints(&printed_document(&d, &settings(set.clone()))), "{how}: nothing overprints");
|
||||
let r = job(&d, set);
|
||||
let text = String::from_utf8_lossy(&r.bytes);
|
||||
assert!(!text.contains("/OP"), "{how}: no overprint in the file");
|
||||
assert_eq!(text.contains("/Multiply"), multiplies, "{how}");
|
||||
assert_eq!(text.contains("/Darken"), darkens, "{how}");
|
||||
assert!(!r.warnings.iter().any(|w| w.contains("knockouts")), "{how}: {:?}", r.warnings);
|
||||
}
|
||||
// Separations honour overprints whatever the option says.
|
||||
|
||||
@@ -1773,12 +1773,12 @@ impl Renderer {
|
||||
return;
|
||||
}
|
||||
let tb = t.xf.transform_rect_bbox(g.bounds);
|
||||
// Overprinting characters multiply per draw, like overprinting fills and strokes
|
||||
// Overprinting characters darken per draw, like overprinting fills and strokes
|
||||
// ([`proof`]); the object blend mode is Normal here or applied by an enclosing layer.
|
||||
let overprints = proof::overprints(f.opts);
|
||||
let overprint = |ctx: &mut RenderContext, on: bool| {
|
||||
if overprints {
|
||||
ctx.set_blend_mode(if on { blend_mode(vectorcraft_color::BlendMode::Multiply) } else { BlendMode::default() });
|
||||
ctx.set_blend_mode(if on { blend_mode(vectorcraft_color::BlendMode::Darken) } else { BlendMode::default() });
|
||||
}
|
||||
};
|
||||
// The object's own fills and strokes paint the whole outline: those below the Characters
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
//! Placed raster images are not separated (limitation).
|
||||
//! * **Overprint Preview** (and Separations Preview, which implies it): fills and strokes that
|
||||
//! overprint ([`vectorcraft_doc::FillLayer::overprint`], characters' too) are drawn with
|
||||
//! Multiply, which approximates their inks printing over the inks below: a zero ink lets the
|
||||
//! Darken, which approximates their inks printing over the inks below: a zero ink lets the
|
||||
//! inks below show through, where a knockout would replace them.
|
||||
//!
|
||||
//! The app's current view state lives in [`view`] / [`set_view`]; the canvas copies it into
|
||||
@@ -25,7 +25,7 @@ use vectorcraft_color::cms::{self, Cms, PROCESS_PLATES};
|
||||
pub use vectorcraft_color::cms::{Intent, ProofSetup, ProofTarget};
|
||||
use vectorcraft_color::{Color, Swatch, SwatchGroup};
|
||||
pub use vectorcraft_doc::inks::{Inks, Link, Plate, inks, map_document_colors, map_node_colors, plates, spot_color};
|
||||
use vectorcraft_doc::overprint::multiply_overprints;
|
||||
use vectorcraft_doc::overprint::darken_overprints;
|
||||
use vectorcraft_doc::{Document, Node, NodeId, Shared};
|
||||
|
||||
use crate::RenderOptions;
|
||||
@@ -103,8 +103,8 @@ impl PartialEq for Plates {
|
||||
}
|
||||
}
|
||||
|
||||
/// What [`prepare`] does to the fills and strokes of each node: overprinting ones multiply
|
||||
/// ([`multiply_overprints`]), and with Separations Preview every color shows as the visible plates
|
||||
/// What [`prepare`] does to the fills and strokes of each node: overprinting ones darken
|
||||
/// ([`darken_overprints`]), and with Separations Preview every color shows as the visible plates
|
||||
/// print it ([`plate_color`]).
|
||||
struct How<'a> {
|
||||
doc: &'a Document,
|
||||
@@ -122,7 +122,7 @@ impl How<'_> {
|
||||
|
||||
/// Change the fills, strokes and other colors of node `a`, which holds no members.
|
||||
fn apply(&self, a: &mut Arc<Node>) {
|
||||
multiply_overprints(a, self.discard_white);
|
||||
darken_overprints(a, self.discard_white);
|
||||
if let Some((c, proof, visible)) = self.plates {
|
||||
map_node_colors(Arc::make_mut(a), &mut |col, sw| plate_color(self.doc, c, proof, visible, col, sw));
|
||||
}
|
||||
|
||||
@@ -261,3 +261,22 @@ fn rich_black_shows_black_ink_alone_as_rgb_black() {
|
||||
add(&mut d, n);
|
||||
assert_eq!(centre_with(&d, true), centre_with(&d, false), "an image shows as it is");
|
||||
}
|
||||
|
||||
/// An overprinting stroke in the colour of the fill under it shows no darker band in Overprint
|
||||
/// Preview: the same ink printed over itself is that ink (#2415). Multiply would square the colour.
|
||||
#[test]
|
||||
fn overprint_of_the_same_colour_shows_no_darker_band() {
|
||||
for (mode, c) in [(ColorMode::Rgb, Color::rgb(0.0, 0.55, 0.29)), (ColorMode::Cmyk, Color::cmyk(0.85, 0.0, 1.0, 0.0))] {
|
||||
let mut d = Document::new_with_mode(40.0, 40.0, mode);
|
||||
let mut n =
|
||||
Node::path(d.alloc_id(), shapes::rectangle(Rect::new(10.0, 10.0, 30.0, 30.0)), Appearance::basic(Paint::solid(c), Paint::solid(c), 8.0));
|
||||
for it in &mut n.appearance.items {
|
||||
*it.overprint_mut() = true;
|
||||
}
|
||||
add(&mut d, n);
|
||||
let opts = RenderOptions { background: Some([255, 255, 255, 255]), overprint_preview: true, ..Default::default() };
|
||||
let r = Renderer::new().render_region_with(&d, d.artboards[0].rect, 1.0, &opts);
|
||||
// (20, 20) is fill only; (12, 20) lies in the stroke's inner half, over the fill.
|
||||
assert!(near(r.pixel(12, 20), r.pixel(20, 20), 2), "{mode:?}: {:?} vs {:?}", r.pixel(12, 20), r.pixel(20, 20));
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -1107,7 +1107,7 @@ Each fill and stroke (and each character's fill and stroke) can overprint: its i
|
||||
of knocking them out. `object.setOverprint {fill?, stroke?, item?, ids?}` sets it (groups set their contents, type its
|
||||
characters too; `item` aims at one appearance item) and `attributes.info {ids?}` reads it back (`null` where the
|
||||
objects differ). Overprint Preview (`view.overprintPreview`) and Separations Preview show it; rendering approximates
|
||||
it by multiplying. Older files that listed Overprint Black objects in the document get the flags on load.
|
||||
it by darkening (the stronger ink of each plate wins, so a colour overprinting itself stays that colour). Older files that listed Overprint Black objects in the document get the flags on load.
|
||||
PDF export writes them with `advanced.overprint: "preserve"` (the default): each overprinting fill and stroke sets a
|
||||
graphics state with `/OP true /op true /OPM 1` (`/OPM 0` in PDF/A). With `discardWhiteOverprint` (`document.setup`, on by
|
||||
default) white ones knock out instead; `"discard"` writes none.
|
||||
@@ -3049,7 +3049,7 @@ The Print dialog's Advanced section and the printer profile are print settings t
|
||||
`file.print`): `advanced: {printAsBitmap, overprints: preserve|discard|simulate, flattenerPreset}` and `color:
|
||||
{profile}`. In composite output, `preserve` writes overprinting fills and strokes with an overprinting graphics state
|
||||
(`/OP`, as PDF export does), `discard` makes overprinting fills and strokes knock out and `simulate` prints them
|
||||
as Overprint Preview shows them (multiplied); either way the file has no overprint left. Separations always keep
|
||||
as Overprint Preview shows them (darkened); either way the file has no overprint left. Separations always keep
|
||||
overprints. `flattenerPreset` (High Resolution, Medium Resolution, Low Resolution or a saved preset,
|
||||
`flattener.presets.list`) flattens transparency before printing, as Object → Flatten Transparency does; empty, a
|
||||
PDF keeps it live (PostScript, which has none, uses `file.print`'s `flattenerPreset`, else this one, else medium).
|
||||
|
||||
Reference in New Issue
Block a user