mirror of
https://github.com/storytold/craft-libs.git
synced 2026-11-07 12:13:20 +00:00
krilla: a gray stop in a CMYK gradient becomes its exact black (patch 17)
A gray stop rewritten into a CMYK space took K as 1 - gray in f32, so gray 0.96 wrote K 0.04000002 and the 8-bit gray 128 wrote 0.4980392 rather than 127/255. K is now subtracted from the shortest decimal that writes the gray, and an 8-bit gray n gives (255 - n) / 255. Patch 17, SOURCE.json and the ledger are updated.
This commit is contained in:
@@ -1112,6 +1112,18 @@ fn f32_colors_keep_their_inks_in_shadings() {
|
||||
assert_eq!(mesh_shadings(&bytes), vec![(("DeviceCMYK".to_owned(), vec![]), 4, vec![corner.repeat(4)])]);
|
||||
}
|
||||
|
||||
/// A gray stop in a CMYK gradient becomes the black ink `1 - gray`: gray 0.96 is K 0.04, and the
|
||||
/// 8-bit gray 128 is K 127/255, as `cmyk::Color::new` writes it.
|
||||
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), test)]
|
||||
fn gray_stops_keep_their_exact_black_in_cmyk_shadings() {
|
||||
let cyan = || -> backend::color::Color { cmyk::Color::new_f32(0.04, 0.0, 0.0, 0.0).into() };
|
||||
let bytes = page_with(|surface| fill_rect(surface, 0.0, linear([luma::Color::new_f32(0.96).into(), cyan()])));
|
||||
assert_eq!(shading_ends(&bytes), (vec![0.0, 0.0, 0.0, 0.04], vec![0.04, 0.0, 0.0, 0.0]));
|
||||
let bytes = page_with(|surface| fill_rect(surface, 0.0, linear([luma::Color::new(128).into(), cyan()])));
|
||||
assert_eq!(shading_ends(&bytes).0, vec![0.0, 0.0, 0.0, 127.0 / 255.0]);
|
||||
}
|
||||
|
||||
const FOGRA39: &[u8] = include_bytes!("../../cms/profiles/craft-coated-fogra39.icc");
|
||||
|
||||
fn text(dict: &Dict<'_>, key: &[u8]) -> Option<String> {
|
||||
|
||||
Vendored
+5
-1
@@ -427,6 +427,10 @@ as a multiple of 1/255: C75 becomes 0.7490196 and C4 M4 Y4 K4 becomes 0.03921568
|
||||
- Gradient stops that are rewritten into one DeviceCMYK, Separation or DeviceN space keep
|
||||
their components, and a DeviceN color's CMYK alternate (annotations, tags) is no longer
|
||||
rounded.
|
||||
- A gray stop rewritten into a CMYK space becomes the black ink `1 - gray`, subtracted from the
|
||||
shortest decimal that writes the gray, so gray 0.96 is K 0.04. An 8-bit gray `n` is K
|
||||
`(255 - n) / 255`, as `cmyk::Color::new` writes it.
|
||||
- Mesh gradients write their corner colors and opacities at 16 bits a component instead of 8.
|
||||
|
||||
Tests: `crates/pdf/tests/print_color.rs` (`f32_colors_*`, `mesh_*`, `rgb_mesh_is_a_device_rgb_shading`).
|
||||
Tests: `crates/pdf/tests/print_color.rs` (`f32_colors_*`, `gray_stops_keep_their_exact_black_in_cmyk_shadings`,
|
||||
`mesh_*`, `rgb_mesh_is_a_device_rgb_shading`).
|
||||
|
||||
Vendored
+2
-2
@@ -152,7 +152,7 @@
|
||||
{
|
||||
"path": "src/graphics/color.rs",
|
||||
"upstream_sha256": "e5843d33ef184ab5c458025d1b1dc53df6ee6aaf085595ea14604d080b98f8a4",
|
||||
"sha256": "5b0de85e0f3eac2905f975f676f1ef5d0eebb99a9a35670fefb419ed36fed6cf"
|
||||
"sha256": "e052d835a4f113ec3730f32c1708e80ee4a284978110f58c77d88c48f3cd15ae"
|
||||
},
|
||||
{
|
||||
"path": "src/graphics/device_n.rs",
|
||||
@@ -207,7 +207,7 @@
|
||||
{
|
||||
"path": "src/graphics/shading_function.rs",
|
||||
"upstream_sha256": "0e0fc8bb1a32272457ef560fa80933972d0f5ef5fb7046424c6de48093cfd978",
|
||||
"sha256": "6da45a96a98d32d4ef878fa03ac1fddbccccea2e2d6333d3a0e90ca009061f81"
|
||||
"sha256": "502571de1a0d1f1d32310d3801ae70250c7ac129506fdf4dd99e786c24268d12"
|
||||
},
|
||||
{
|
||||
"path": "src/graphics/shading_pattern.rs",
|
||||
|
||||
+24
-12
@@ -1,5 +1,5 @@
|
||||
diff --git a/src/graphics/color.rs b/src/graphics/color.rs
|
||||
index 6fb0618..06a3098 100644
|
||||
index 6fb0618..581de00 100644
|
||||
--- a/src/graphics/color.rs
|
||||
+++ b/src/graphics/color.rs
|
||||
@@ -69,7 +69,7 @@
|
||||
@@ -11,7 +11,7 @@ index 6fb0618..06a3098 100644
|
||||
|
||||
use crate::configure::ValidationError;
|
||||
use crate::graphics::icc::ICCBasedColorSpace;
|
||||
@@ -82,6 +82,48 @@ pub(crate) const DEVICE_GRAY: &str = "DeviceGray";
|
||||
@@ -82,6 +82,60 @@ pub(crate) const DEVICE_GRAY: &str = "DeviceGray";
|
||||
/// The PDF name for the device CMYK color space.
|
||||
pub(crate) const DEVICE_CMYK: &str = "DeviceCMYK";
|
||||
|
||||
@@ -41,6 +41,18 @@ index 6fb0618..06a3098 100644
|
||||
+ pub(crate) fn to_u8(self) -> u8 {
|
||||
+ (self.0 * 255.0).round() as u8
|
||||
+ }
|
||||
+
|
||||
+ /// `1 - value`, the black ink of a gray. An 8-bit value `n / 255` gives `(255 - n) / 255`,
|
||||
+ /// as the 8-bit constructors write it. Any other value is subtracted from 1 in f64, from
|
||||
+ /// the shortest decimal that writes it, so gray 0.96 gives 0.04 rather than 0.04000002.
|
||||
+ pub(crate) fn complement(self) -> f32 {
|
||||
+ let byte = self.to_u8();
|
||||
+ if Tint::from_u8(byte) == self {
|
||||
+ return Tint::from_u8(255 - byte).0;
|
||||
+ }
|
||||
+ let written = self.0.to_string().parse::<f64>().unwrap_or(f64::from(self.0));
|
||||
+ (1.0 - written) as f32
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+impl PartialEq for Tint {
|
||||
@@ -60,7 +72,7 @@ index 6fb0618..06a3098 100644
|
||||
/// A wrapper for storing colors from different color spaces.
|
||||
#[derive(Debug, Hash, Eq, PartialEq, Clone)]
|
||||
pub enum Color {
|
||||
@@ -160,7 +202,10 @@ impl RegularColor {
|
||||
@@ -160,7 +214,10 @@ impl RegularColor {
|
||||
pub(crate) fn as_rgb(self) -> Option<rgb::Color> {
|
||||
Some(match self {
|
||||
Self::Rgb(r) => r,
|
||||
@@ -72,7 +84,7 @@ index 6fb0618..06a3098 100644
|
||||
Self::Cmyk(_) => return None,
|
||||
})
|
||||
}
|
||||
@@ -183,16 +228,24 @@ impl SpecialColor {
|
||||
@@ -183,16 +240,24 @@ impl SpecialColor {
|
||||
|
||||
/// Gray-scale colors.
|
||||
pub mod luma {
|
||||
@@ -100,7 +112,7 @@ index 6fb0618..06a3098 100644
|
||||
}
|
||||
|
||||
/// Create a black luma color.
|
||||
@@ -206,7 +259,7 @@ pub mod luma {
|
||||
@@ -206,7 +271,7 @@ pub mod luma {
|
||||
}
|
||||
|
||||
pub(crate) fn to_pdf_color(self) -> f32 {
|
||||
@@ -109,7 +121,7 @@ index 6fb0618..06a3098 100644
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,27 +304,43 @@ impl From<SpecialColor> for Color {
|
||||
@@ -251,27 +316,43 @@ impl From<SpecialColor> for Color {
|
||||
|
||||
/// CMYK colors.
|
||||
pub mod cmyk {
|
||||
@@ -162,7 +174,7 @@ index 6fb0618..06a3098 100644
|
||||
}
|
||||
}
|
||||
|
||||
@@ -390,23 +459,35 @@ pub mod rgb {
|
||||
@@ -390,23 +471,35 @@ pub mod rgb {
|
||||
|
||||
/// Separation (spot) colors.
|
||||
pub mod separation {
|
||||
@@ -202,7 +214,7 @@ index 6fb0618..06a3098 100644
|
||||
}
|
||||
|
||||
pub(crate) fn color_space(&self) -> SeparationSpace {
|
||||
@@ -498,7 +579,7 @@ pub mod separation {
|
||||
@@ -498,7 +591,7 @@ pub mod separation {
|
||||
pub mod device_n {
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -211,7 +223,7 @@ index 6fb0618..06a3098 100644
|
||||
|
||||
/// A process colorant.
|
||||
#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)]
|
||||
@@ -656,7 +737,7 @@ pub mod device_n {
|
||||
@@ -656,7 +749,7 @@ pub mod device_n {
|
||||
/// A DeviceN color: one tint per colorant of its space.
|
||||
#[derive(Debug, Hash, Eq, PartialEq, Clone)]
|
||||
pub struct Color {
|
||||
@@ -220,7 +232,7 @@ index 6fb0618..06a3098 100644
|
||||
pub(crate) space: DeviceNSpace,
|
||||
}
|
||||
|
||||
@@ -666,20 +747,30 @@ pub mod device_n {
|
||||
@@ -666,20 +759,30 @@ pub mod device_n {
|
||||
///
|
||||
/// Returns `None` unless there is one tint per colorant.
|
||||
pub fn new(tints: Vec<u8>, space: DeviceNSpace) -> Option<Self> {
|
||||
@@ -258,7 +270,7 @@ index 6fb0618..06a3098 100644
|
||||
}
|
||||
|
||||
diff --git a/src/graphics/shading_function.rs b/src/graphics/shading_function.rs
|
||||
index 74c56f3..8aae81a 100644
|
||||
index 74c56f3..47b55c5 100644
|
||||
--- a/src/graphics/shading_function.rs
|
||||
+++ b/src/graphics/shading_function.rs
|
||||
@@ -277,12 +277,12 @@ impl GradientProperties {
|
||||
@@ -290,7 +302,7 @@ index 74c56f3..8aae81a 100644
|
||||
- Color::Regular(RegularColor::Cmyk(c)) => inks.process = [c.0, c.1, c.2, c.3],
|
||||
- Color::Regular(RegularColor::Luma(l)) => inks.process[3] = 255 - l.0,
|
||||
+ Color::Regular(RegularColor::Cmyk(c)) => inks.process = c.to_pdf_color(),
|
||||
+ Color::Regular(RegularColor::Luma(l)) => inks.process[3] = 1.0 - l.to_pdf_color(),
|
||||
+ Color::Regular(RegularColor::Luma(l)) => inks.process[3] = l.0.complement(),
|
||||
Color::Regular(RegularColor::Rgb(_)) => return None,
|
||||
Color::Special(SpecialColor::Separation(s)) => {
|
||||
let SeparationColorant::Custom(name) = &s.space.colorant else {
|
||||
|
||||
Vendored
+12
@@ -108,6 +108,18 @@ impl Tint {
|
||||
pub(crate) fn to_u8(self) -> u8 {
|
||||
(self.0 * 255.0).round() as u8
|
||||
}
|
||||
|
||||
/// `1 - value`, the black ink of a gray. An 8-bit value `n / 255` gives `(255 - n) / 255`,
|
||||
/// as the 8-bit constructors write it. Any other value is subtracted from 1 in f64, from
|
||||
/// the shortest decimal that writes it, so gray 0.96 gives 0.04 rather than 0.04000002.
|
||||
pub(crate) fn complement(self) -> f32 {
|
||||
let byte = self.to_u8();
|
||||
if Tint::from_u8(byte) == self {
|
||||
return Tint::from_u8(255 - byte).0;
|
||||
}
|
||||
let written = self.0.to_string().parse::<f64>().unwrap_or(f64::from(self.0));
|
||||
(1.0 - written) as f32
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Tint {
|
||||
|
||||
+1
-1
@@ -359,7 +359,7 @@ pub(crate) fn common_color_space(colors: &[Color]) -> Option<Vec<Color>> {
|
||||
};
|
||||
match color {
|
||||
Color::Regular(RegularColor::Cmyk(c)) => inks.process = c.to_pdf_color(),
|
||||
Color::Regular(RegularColor::Luma(l)) => inks.process[3] = 1.0 - l.to_pdf_color(),
|
||||
Color::Regular(RegularColor::Luma(l)) => inks.process[3] = l.0.complement(),
|
||||
Color::Regular(RegularColor::Rgb(_)) => return None,
|
||||
Color::Special(SpecialColor::Separation(s)) => {
|
||||
let SeparationColorant::Custom(name) = &s.space.colorant else {
|
||||
|
||||
Reference in New Issue
Block a user