mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-08 17:40:54 +00:00
Color: Generic CMYK separates nearby colors alike when an ink sits at 0 or 100%
The Generic CMYK solver clamps each ink to 0..1 after a step. When an ink sits at 0 or 1 and the step pushes it further out, the clamp removes most of the step, and the solve can stop after a step under 1e-6. Where it stopped depended on the input's last float bits. For hex ed1e79, magenta ranged from 93.2% to 93.9% and yellow from 15.3% to 16.3% over 4,001 f32 values of its red, with 230 jumps over 0.001 between neighboring values. An ink at 0 or 1 whose descent direction points out of the range now stays where it is, and the step moves the other inks. The solve stops when every ink is held. Two new tests step a channel one f32 at a time and check that every ink stays within 0.001: the red of ed1e79 (cyan held at 0) and the blue of sRGB (0, 0, 0.25) (cyan and magenta held at 1). The solver still minimizes plain Lab distance to the gamut-mapped target. The CMYK values of many RGB colors change; hex 00ff00 now separates to C=61 M=0 Y=100 K=0 (cyan 0.6235 before, 0.6064 now), and the swatch test that pins its name is updated.
This commit is contained in:
@@ -231,6 +231,21 @@ impl GenericCmyk {
|
||||
}
|
||||
g[p] = -(0..3).map(|i| j[i][p] * r[i]).sum::<f32>();
|
||||
}
|
||||
// An ink at a bound whose descent direction points out of the box stays where it is; the step moves the other inks.
|
||||
for p in 0..3 {
|
||||
if (x[p] <= 0.0 && g[p] < 0.0) || (x[p] >= 1.0 && g[p] > 0.0) {
|
||||
a[p] = [0.0; 3];
|
||||
for row in &mut a {
|
||||
row[p] = 0.0;
|
||||
}
|
||||
a[p][p] = 1.0;
|
||||
g[p] = 0.0;
|
||||
}
|
||||
}
|
||||
// Every ink is held at a bound, or the gradient is zero: no step can lower the error.
|
||||
if g == [0.0; 3] {
|
||||
break;
|
||||
}
|
||||
let mut improved = false;
|
||||
for _ in 0..8 {
|
||||
let mut al = a;
|
||||
|
||||
@@ -259,6 +259,46 @@ fn generic_cmyk_black_round_trips_with_bpc() {
|
||||
assert!(raw.iter().all(|v| *v > 0.05), "{raw:?}");
|
||||
}
|
||||
|
||||
/// A color whose solve holds cyan at 0 separates to the same inks when its values change by a few
|
||||
/// float steps: hex ed1e79 with its red stepped one `f32` at a time over 4,001 values (about 0.00024
|
||||
/// in all) stays within 0.001 on every ink.
|
||||
#[test]
|
||||
fn generic_cmyk_separates_nearby_colors_at_the_gamut_edge_alike() {
|
||||
let c = cms();
|
||||
let pink = [237.0f32 / 255.0, 30.0 / 255.0, 121.0 / 255.0];
|
||||
let (mut lo, mut hi) = ([f32::MAX; 4], [f32::MIN; 4]);
|
||||
for step in -2000i32..=2000 {
|
||||
let mut rgb = pink;
|
||||
rgb[0] = f32::from_bits(pink[0].to_bits().wrapping_add_signed(step));
|
||||
let ink = c.srgb_to_cmyk(rgb, Intent::RelativeColorimetric);
|
||||
for i in 0..4 {
|
||||
lo[i] = lo[i].min(ink[i]);
|
||||
hi[i] = hi[i].max(ink[i]);
|
||||
}
|
||||
}
|
||||
assert!((0..4).all(|i| hi[i] - lo[i] < 1e-3), "{lo:?} to {hi:?}");
|
||||
assert_eq!(lo[0], 0.0, "{lo:?}");
|
||||
}
|
||||
|
||||
/// A color whose solve holds cyan and magenta at 1 separates to the same inks when its values change
|
||||
/// by a few float steps: sRGB (0, 0, 0.25) with its blue stepped one `f32` at a time over 121 values
|
||||
/// stays within 0.001 on every ink.
|
||||
#[test]
|
||||
fn generic_cmyk_separates_nearby_colors_with_inks_at_full_alike() {
|
||||
let c = cms();
|
||||
let (mut lo, mut hi) = ([f32::MAX; 4], [f32::MIN; 4]);
|
||||
for step in -60i32..=60 {
|
||||
let blue = f32::from_bits(0.25f32.to_bits().wrapping_add_signed(step));
|
||||
let ink = c.srgb_to_cmyk([0.0, 0.0, blue], Intent::RelativeColorimetric);
|
||||
for i in 0..4 {
|
||||
lo[i] = lo[i].min(ink[i]);
|
||||
hi[i] = hi[i].max(ink[i]);
|
||||
}
|
||||
}
|
||||
assert!((0..4).all(|i| hi[i] - lo[i] < 1e-3), "{lo:?} to {hi:?}");
|
||||
assert_eq!((lo[0], lo[1]), (1.0, 1.0), "{lo:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lab_colours_round_trip_through_the_cms() {
|
||||
let c = cms_no_bpc();
|
||||
|
||||
@@ -503,7 +503,7 @@ fn a_cmyk_document_starts_with_cmyk_swatches_and_stores_new_colours_as_cmyk() {
|
||||
// A swatch made from a hex colour is CMYK as well; the document round-trips natively.
|
||||
let name = run(&mut s, "swatch.new", json!({"color": "#00ff00"}))["name"].as_str().unwrap().to_string();
|
||||
assert_eq!(doc(&s).swatch(&name).and_then(|w| w.paint.color()), Some(managed(Color::rgb(0.0, 1.0, 0.0))));
|
||||
assert_eq!(name, "C=62 M=0 Y=100 K=0");
|
||||
assert_eq!(name, "C=61 M=0 Y=100 K=0");
|
||||
let json = serde_json::to_string(doc(&s)).unwrap();
|
||||
assert_eq!(serde_json::from_str::<vectorcraft_doc::Document>(&json).unwrap(), *doc(&s));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user