mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-15 21:57:04 +00:00
Merge pull request #7788 from dotCooCoo/fix/a-gradient-drawn-as-an-image-turns-rgb-because
PDF export: a spot stop that prints nothing keeps a gradient image in CMYK
This commit is contained in:
@@ -529,18 +529,18 @@ impl Exporter<'_> {
|
||||
/// their own.
|
||||
fn warn_spot_image(&mut self, g: &vectorcraft_color::GradientPaint) {
|
||||
let from_stops = g.gradient.kind != GradientKind::Freeform || !g.has_freeform_points();
|
||||
let prints_spot = || g.gradient.stops.iter().zip(g.gradient.drawn_stops()).any(|(s, d)| self.printed_spot(s, d).is_some());
|
||||
if from_stops && prints_spot() {
|
||||
if from_stops && self.prints_spot(&g.gradient) {
|
||||
self.warn(
|
||||
"spot colors in gradients exported as images (freeform or dithered gradients, ones mixed around the hue wheel, and Perceptual ones with RGB or Lab stops or too many stops) are written in process colors",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Is a stop of `g` linked to a spot swatch?
|
||||
fn has_spot_stop(&self, g: &vectorcraft_color::Gradient) -> bool {
|
||||
let doc = self.doc;
|
||||
g.stops.iter().any(|s| s.swatch.as_deref().is_some_and(|n| doc.swatch(n).is_some_and(|w| w.spot)))
|
||||
/// Does a stop of `g` print a spot swatch ([`Self::printed_spot`]): color something that draws
|
||||
/// ([`vectorcraft_color::Gradient::drawn_stops`]) at a tint above 0 or with process plates? A
|
||||
/// stop linked to a spot swatch that colors nothing adds no spot ink.
|
||||
fn prints_spot(&self, g: &vectorcraft_color::Gradient) -> bool {
|
||||
g.stops.iter().zip(g.drawn_stops()).any(|(s, d)| self.printed_spot(s, d).is_some())
|
||||
}
|
||||
|
||||
/// Draw diamond gradient `g`, placed on `bounds`, over `area` (inside the current clip) as four
|
||||
@@ -638,8 +638,8 @@ impl Exporter<'_> {
|
||||
/// scaled type gets its pixels too): a freeform gradient's colour field (at most
|
||||
/// [`MAX_FIELD_PX`] a side), or a linear, radial, conic or diamond gradient (at most [`MAX_GRADIENT_PX`] a side),
|
||||
/// with dither's noise when it paints a fill (`fill`). A linear or radial gradient whose stops
|
||||
/// are all CMYK and not linked to a spot swatch is a CMYK image, unless its Perceptual curve
|
||||
/// has too many stops; any other is an RGB image.
|
||||
/// are all CMYK and none of which prints a spot swatch ([`Self::prints_spot`]) is a CMYK image,
|
||||
/// unless its Perceptual curve has too many stops; any other is an RGB image.
|
||||
pub(super) fn field_image(&mut self, s: &mut Surface, g: &vectorcraft_color::GradientPaint, bounds: Rect, area: Rect, fill: bool) {
|
||||
use vectorcraft_color::cms::Model;
|
||||
use vectorcraft_color::freeform::{painted_box, spread_scale};
|
||||
@@ -667,7 +667,7 @@ impl Exporter<'_> {
|
||||
} else if !freeform {
|
||||
self.warn("linear and radial gradients drawn as pixels are exported as images");
|
||||
}
|
||||
let ramp_inks = if freeform || self.has_spot_stop(&g.gradient) { None } else { g.ink_pixels(b, area, cols, rows, fill) };
|
||||
let ramp_inks = if freeform || self.prints_spot(&g.gradient) { None } else { g.ink_pixels(b, area, cols, rows, fill) };
|
||||
let ink_image = |inks, alpha| {
|
||||
let convert = if self.convert { self.out.cmyk_pixels() } else { None };
|
||||
crate::images::from_inks(inks, alpha, cols.into(), rows.into(), self.interpolate, convert)
|
||||
|
||||
@@ -962,6 +962,35 @@ fn gradients_with_a_stop_that_is_not_process_cmyk_stay_rgb_images() {
|
||||
assert_eq!(w[0].space, "DeviceCMYK", "{w:?}");
|
||||
}
|
||||
|
||||
/// A stop linked to a spot swatch that prints no ink (it colors only clear stretches, sits out of
|
||||
/// order where it colors nothing, or has a tint of 0 and no process plates) leaves a dithered
|
||||
/// gradient of CMYK stops a CMYK image without the spot warning; a drawn stop with process plates
|
||||
/// at a tint of 0 still prints, so its gradient stays an RGB image with the warning.
|
||||
#[test]
|
||||
fn gradients_whose_spot_stops_print_nothing_stay_cmyk_images() {
|
||||
let gold = Color::cmyk(0.0, 0.2, 0.8, 0.1);
|
||||
let stop = |offset: f32, color: Color, opacity: f32| GradientStop { opacity, ..GradientStop::new(offset, color) };
|
||||
let spot = |offset: f32, opacity: f32| GradientStop { swatch: Some("Gold".into()), ..stop(offset, gold, opacity) };
|
||||
let (cyan, black) = (Color::cmyk(1.0, 0.0, 0.0, 0.0), Color::cmyk(0.0, 0.0, 0.0, 1.0));
|
||||
let clear = vec![stop(0.0, cyan, 1.0), stop(0.5, black, 1.0), stop(0.6, black, 0.0), spot(0.8, 0.0), stop(1.0, black, 0.0)];
|
||||
let out_of_order = vec![stop(0.0, cyan, 1.0), stop(0.6, black, 1.0), spot(0.3, 1.0), stop(0.6, black, 1.0), stop(1.0, black, 1.0)];
|
||||
let no_tint = vec![stop(0.0, cyan, 1.0), GradientStop { tint: 0.0, ..spot(1.0, 1.0) }];
|
||||
let plates = vec![stop(0.0, cyan, 1.0), GradientStop { tint: 0.0, plates: Some([0.0, 0.0, 0.0, 0.5]), ..spot(1.0, 1.0) }];
|
||||
for (name, stops, prints) in
|
||||
[("clear", clear, false), ("out of order", out_of_order, false), ("tint 0", no_tint, false), ("plates", plates, true)]
|
||||
{
|
||||
let mut d = gradient_doc(ColorMode::Cmyk, drawn_as_pixels(GradientKind::Linear, stops, true));
|
||||
d.swatches.push(Swatch { name: "Gold".into(), paint: Paint::solid(gold), global: true, spot: true });
|
||||
let out = export_with_report(&d, &opts(json!({}))).unwrap();
|
||||
let w = written(&out.bytes);
|
||||
let warned = out.warnings.iter().any(|m| m.contains("written in process colors"));
|
||||
let space = if prints { "DeviceRGB" } else { "DeviceCMYK" };
|
||||
assert!(w.iter().any(|i| i.space == space), "{name}: {w:?}");
|
||||
assert!(!w.iter().any(|i| i.space == if prints { "DeviceCMYK" } else { "DeviceRGB" }), "{name}: {w:?}");
|
||||
assert_eq!(warned, prints, "{name}: {:?}", out.warnings);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn freeform_gradients_of_lab_points_are_written_as_their_lab_mix() {
|
||||
let d = freeform_doc(ColorMode::Rgb, Color::lab(90.0, -60.0, 40.0), Color::lab(30.0, 60.0, -40.0), 1.0);
|
||||
|
||||
@@ -1730,6 +1730,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
|
||||
A `.ai` or EPS opened from its editing data keeps both swatches when a pattern's name, or a named color's, is also another swatch's: a pattern's swatch named as a default swatch, a named color or a palette color becomes "Name 2", and a named color named as a gradient or pattern swatch of the palette becomes "Name 2", with the fills, strokes and gradient stops linked to it following it (`ai::tests::a_pattern_swatch_named_as_another_swatch_is_numbered`, `ai::tests::a_color_named_as_a_palette_gradient_or_pattern_is_numbered`).
|
||||
PDF export writes a linear or radial gradient that mixes a spot color with process colors, or two spot colors, as a DeviceN shading of the spot inks and the process inks its stops use, so each spot ink prints on its own plate; an off-center midpoint is written as a stop with the inks halfway between its two stops. A gradient that mixes a spot color with RGB or Lab colors is still written without the spot ink, with a warning. A CMYK document, or an export that converts colors to a CMYK destination profile, writes RGB and Lab stops in CMYK first (unless the export converts colors to an RGB destination profile), so there such a gradient, Classic or Perceptual, is written as a DeviceN shading that keeps the spot ink. A Perceptual gradient of spot inks is written as a Separation or DeviceN shading of the stops on its curve; a dithered gradient fill, and a Perceptual gradient with RGB or Lab stops other than white or more than 4,096 stops on its curve, are written as images in process colors, with a warning. A Lab spot color in such a shading is named by the same Separation space, with the Lab alternate, as its flat fills (`engine/src/tests/linked_stops.rs`, `engine/src/tests/labspots.rs`).
|
||||
PDF and EPS export name a spot ink in a gradient's Separation or DeviceN space, and EPS in `%%DocumentCustomColors`, only when a stop linked to it at a tint above 0, or with process plates, colors part of the gradient (VectorCraft's own rule): a stop out of order whose neighboring stretches have no width, and in PDF a stop whose stretches are fully transparent, adds no ink and no empty plate. EPS writes every stop opaque, so there a transparent spot stop keeps its ink (`crates/pdf/src/tests/output.rs`, `crates/eps/src/tests.rs`).
|
||||
PDF export applies the same rule to a linear or radial gradient drawn as an image: a spot stop that colors only fully transparent stretches, sits out of order where its neighboring stretches have no width, or has a tint of 0 without process plates, leaves a gradient of CMYK stops a CMYK image with no spot color warning. A drawn spot stop at a tint above 0, or with process plates, still makes it an RGB image with the warning (VectorCraft's own rule; `pdf/src/tests/cmykimages.rs`, `gradients_whose_spot_stops_print_nothing_stay_cmyk_images`).
|
||||
PDF export writes a spot color named All or None (in a Separation space, the names of every plate and of no plate: [ISO 32000-1:2008](https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/PDF32000_2008.pdf), 8.6.6.4) as the ink All Spot or None Spot (All Spot 2 and on when a spot swatch already has that name) in its fills, strokes, gradients, colorized images and Lab alternates; Registration still prints on every plate (`pdf/src/tests/output.rs`).
|
||||
- `.vectorcraft` (lossless JSON, compressed, atomic saves, save down to v1), Save As with a format chooser, Save a Copy, Revert, templates, Data Recovery after a crash (browser tabs hold their copies with Web Locks too, so a background tab's copies are never taken for a crash's, and a tab repairs copies another removed when it resumes: #367), background save and export. The desktop app writes its warnings and errors (graphics adapter, a lost graphics device, panics the guard recovered from) to standard error and to `logs/vectorcraft.log` next to the preferences, the two previous runs kept and each file capped at 16 MiB, so a launch without a terminal still leaves something to attach to a bug report (#465). Native documents with a lot of path data (traced or generated art, in layers, symbols or patterns) are saved as compact JSON, about a fifth of the size; small documents stay pretty-printed (#1492). Saving to a network drive on macOS falls back to a plain fsync when the share refuses a full flush (#1625, part of #1620; not confirmed on a real share), and a save still succeeds when a folder refuses the temporary file, the rename over the document or copying permissions: the content is written elsewhere first and copied in, and a failed copy keeps the temporary file and names it (#1649, part of #1620). A document opened by a relative path finds its relatively linked images, and `vectorcraft-cli convert` lists what reading the input left out as `importWarnings` (#1606, fixes #1604).
|
||||
Preferences › File Handling › Data Recovery › Folder has a Choose… button on the desktop that picks the folder Data Recovery keeps its copies in ([Illustrator reference, Set Data Recovery preferences](https://web.archive.org/web/20210601162814/https://helpx.adobe.com/pdf/illustrator_reference.pdf)); a picked folder that Data Recovery cannot create its folders in or list is refused with a status message, and the field keeps its value. OK applies the same check to a folder typed into the field (VectorCraft's own rule): when it fails, the dialog stays open, the status bar says why and no preference changes; an empty field (the default folder) and the folder already saved are not checked. Preferences › Plug-ins & Scratch Disks is where additional plug-ins folders are set ([Illustrator preferences help](https://helpx.adobe.com/illustrator/desktop/get-started/preferences-and-settings/set-app-preferences.html)); its Additional Plug-ins Folder has a Choose… button on the desktop as well, VectorCraft's own, matching Data Recovery's, and VectorCraft's own rule refuses a picked or typed folder whose contents can't be listed. Additional Fonts Folder and Templates Folder stay typed only.
|
||||
|
||||
Reference in New Issue
Block a user