diff options
| author | Martin Haug <mhaug@live.de> | 2024-02-12 14:03:36 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-02-12 13:03:36 +0000 |
| commit | 9f1e0390c1dfb5643fd3cc419e28888f1d17a95b (patch) | |
| tree | 6c2667d56a16928c4a54ae52df853fbc219d2439 /crates | |
| parent | f776f0a75fb36deabab8e8cdf880389e4e2eb6e8 (diff) | |
Add color-managed CMYK to RGB conversion (#3288)
Diffstat (limited to 'crates')
| -rw-r--r-- | crates/typst/Cargo.toml | 1 | ||||
| -rw-r--r-- | crates/typst/assets/CGATS001Compat-v2-micro.icc | bin | 0 -> 8464 bytes | |||
| -rw-r--r-- | crates/typst/src/visualize/color.rs | 55 |
3 files changed, 51 insertions, 5 deletions
diff --git a/crates/typst/Cargo.toml b/crates/typst/Cargo.toml index b9e3b494..650f53c3 100644 --- a/crates/typst/Cargo.toml +++ b/crates/typst/Cargo.toml @@ -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 } diff --git a/crates/typst/assets/CGATS001Compat-v2-micro.icc b/crates/typst/assets/CGATS001Compat-v2-micro.icc Binary files differnew file mode 100644 index 00000000..b5a73495 --- /dev/null +++ b/crates/typst/assets/CGATS001Compat-v2-micro.icc diff --git a/crates/typst/src/visualize/color.rs b/crates/typst/src/visualize/color.rs index 90ac8c3d..735a922c 100644 --- a/crates/typst/src/visualize/color.rs +++ b/crates/typst/src/visualize/color.rs @@ -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); - - Rgb::new(r, g, b, 1.0) + 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( + 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 { |
