Add color-managed CMYK to RGB conversion (#3288)

This commit is contained in:
Martin Haug 2024-02-12 14:03:36 +01:00 committed by GitHub
parent f776f0a75f
commit 9f1e0390c1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 62 additions and 7 deletions

7
Cargo.lock generated
View File

@ -1778,6 +1778,12 @@ dependencies = [
"unicase",
]
[[package]]
name = "qcms"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edecfcd5d755a5e5d98e24cf43113e7cdaec5a070edd0f6b250c03a573da30fa"
[[package]]
name = "quick-xml"
version = "0.28.2"
@ -2554,6 +2560,7 @@ dependencies = [
"log",
"once_cell",
"palette",
"qcms",
"phf",
"rayon",
"regex",

View File

@ -77,6 +77,7 @@ pixglyph = "0.3"
proc-macro2 = "1"
pulldown-cmark = "0.9"
quote = "1"
qcms = "0.3.0"
rayon = "1.7.0"
regex = "1"
resvg = { version = "0.38.0", default-features = false, features = ["raster-images"] }

3
NOTICE
View File

@ -2,7 +2,8 @@ Licenses for third party components used by this project can be found below.
================================================================================
The Creative Commons Zero v1.0 Universal License applies to:
* The ICC profiles found in `crates/typst/icc/*`
* The ICC profiles found in `crates/typst-pdf/src/icc/*` and
`crates/typst/assets/*`.
CC0 1.0 Universal

View File

@ -41,6 +41,7 @@ lipsum = { workspace = true }
log = { workspace = true }
once_cell = { workspace = true }
palette = { workspace = true }
qcms = { workspace = true }
phf = { workspace = true }
rayon = { workspace = true }
regex = { workspace = true }

Binary file not shown.

View File

@ -9,6 +9,7 @@ use palette::encoding::{self, Linear};
use palette::{
Darken, Desaturate, FromColor, Lighten, Okhsva, OklabHue, RgbHue, Saturate, ShiftHue,
};
use qcms::Profile;
use crate::diag::{bail, At, SourceResult, StrResult};
use crate::foundations::{
@ -30,6 +31,36 @@ pub type Luma = palette::luma::Luma<encoding::Srgb, f32>;
/// Equivalent of [`std::f32::EPSILON`] but for hue angles.
const ANGLE_EPSILON: f32 = 1e-5;
/// The ICC profile used to convert from CMYK to RGB.
///
/// This is a minimal CMYK profile that only contains the necessary information
/// to convert from CMYK to RGB. It is based on the CGATS TR 001-1995
/// specification. See
/// https://github.com/saucecontrol/Compact-ICC-Profiles#cmyk.
static CGATS001_COMPACT_PROFILE: Lazy<Box<Profile>> = Lazy::new(|| {
let bytes = include_bytes!("../../assets/CGATS001Compat-v2-micro.icc");
Profile::new_from_slice(bytes, false).unwrap()
});
/// The target sRGB profile.
static SRGB_PROFILE: Lazy<Box<Profile>> = Lazy::new(|| {
let mut out = Profile::new_sRGB();
out.precache_output_transform();
out
});
static TO_SRGB: Lazy<qcms::Transform> = Lazy::new(|| {
qcms::Transform::new_to(
&CGATS001_COMPACT_PROFILE,
&SRGB_PROFILE,
qcms::DataType::CMYK,
qcms::DataType::RGB8,
// Our input profile only supports perceptual intent.
qcms::Intent::Perceptual,
)
.unwrap()
});
/// A color in a specific color space.
///
/// Typst supports:
@ -1691,6 +1722,8 @@ impl Cmyk {
Cmyk::new(l * 0.75, l * 0.68, l * 0.67, l * 0.90)
}
// This still uses naive conversion, because qcms does not support
// converting to CMYK yet.
fn from_rgba(rgba: Rgb) -> Self {
let r = rgba.red;
let g = rgba.green;
@ -1709,11 +1742,23 @@ impl Cmyk {
}
fn to_rgba(self) -> Rgb {
let r = (1.0 - self.c) * (1.0 - self.k);
let g = (1.0 - self.m) * (1.0 - self.k);
let b = (1.0 - self.y) * (1.0 - self.k);
let mut dest: [u8; 3] = [0; 3];
TO_SRGB.convert(
&[
(self.c * 255.0).round() as u8,
(self.m * 255.0).round() as u8,
(self.y * 255.0).round() as u8,
(self.k * 255.0).round() as u8,
],
&mut dest,
);
Rgb::new(r, g, b, 1.0)
Rgb::new(
dest[0] as f32 / 255.0,
dest[1] as f32 / 255.0,
dest[2] as f32 / 255.0,
1.0,
)
}
fn lighten(self, factor: f32) -> Self {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 875 B

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -143,8 +143,8 @@
#test(rgb(1, 2, 3).to-hex(), "#010203")
#test(rgb(1, 2, 3, 4).to-hex(), "#01020304")
#test(luma(40).to-hex(), "#282828")
#test-repr(cmyk(4%, 5%, 6%, 7%).to-hex(), "#e4e1df")
#test-repr(rgb(cmyk(4%, 5%, 6%, 7%)).components(), (89.28%, 88.35%, 87.42%, 100%))
#test-repr(cmyk(4%, 5%, 6%, 7%).to-hex(), "#e0dcda")
#test-repr(rgb(cmyk(4%, 5%, 6%, 7%)).components(), (87.84%, 86.27%, 85.49%, 100%))
#test-repr(rgb(luma(40%)).components(alpha: false), (40%, 40%, 40%))
#test-repr(cmyk(luma(40)).components(), (11.76%, 10.67%, 10.51%, 14.12%))
#test-repr(cmyk(rgb(1, 2, 3)), cmyk(66.67%, 33.33%, 0%, 98.82%))