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

View File

@ -77,6 +77,7 @@ pixglyph = "0.3"
proc-macro2 = "1" proc-macro2 = "1"
pulldown-cmark = "0.9" pulldown-cmark = "0.9"
quote = "1" quote = "1"
qcms = "0.3.0"
rayon = "1.7.0" rayon = "1.7.0"
regex = "1" regex = "1"
resvg = { version = "0.38.0", default-features = false, features = ["raster-images"] } 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 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 CC0 1.0 Universal

View File

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

Binary file not shown.

View File

@ -9,6 +9,7 @@ use palette::encoding::{self, Linear};
use palette::{ use palette::{
Darken, Desaturate, FromColor, Lighten, Okhsva, OklabHue, RgbHue, Saturate, ShiftHue, Darken, Desaturate, FromColor, Lighten, Okhsva, OklabHue, RgbHue, Saturate, ShiftHue,
}; };
use qcms::Profile;
use crate::diag::{bail, At, SourceResult, StrResult}; use crate::diag::{bail, At, SourceResult, StrResult};
use crate::foundations::{ 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. /// Equivalent of [`std::f32::EPSILON`] but for hue angles.
const ANGLE_EPSILON: f32 = 1e-5; 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. /// A color in a specific color space.
/// ///
/// Typst supports: /// Typst supports:
@ -1691,6 +1722,8 @@ impl Cmyk {
Cmyk::new(l * 0.75, l * 0.68, l * 0.67, l * 0.90) 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 { fn from_rgba(rgba: Rgb) -> Self {
let r = rgba.red; let r = rgba.red;
let g = rgba.green; let g = rgba.green;
@ -1709,11 +1742,23 @@ impl Cmyk {
} }
fn to_rgba(self) -> Rgb { fn to_rgba(self) -> Rgb {
let r = (1.0 - self.c) * (1.0 - self.k); let mut dest: [u8; 3] = [0; 3];
let g = (1.0 - self.m) * (1.0 - self.k); TO_SRGB.convert(
let b = (1.0 - self.y) * (1.0 - self.k); &[
(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 { 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).to-hex(), "#010203")
#test(rgb(1, 2, 3, 4).to-hex(), "#01020304") #test(rgb(1, 2, 3, 4).to-hex(), "#01020304")
#test(luma(40).to-hex(), "#282828") #test(luma(40).to-hex(), "#282828")
#test-repr(cmyk(4%, 5%, 6%, 7%).to-hex(), "#e4e1df") #test-repr(cmyk(4%, 5%, 6%, 7%).to-hex(), "#e0dcda")
#test-repr(rgb(cmyk(4%, 5%, 6%, 7%)).components(), (89.28%, 88.35%, 87.42%, 100%)) #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(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(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%)) #test-repr(cmyk(rgb(1, 2, 3)), cmyk(66.67%, 33.33%, 0%, 98.82%))