summaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorMartin Haug <mhaug@live.de>2024-02-12 14:03:36 +0100
committerGitHub <noreply@github.com>2024-02-12 13:03:36 +0000
commit9f1e0390c1dfb5643fd3cc419e28888f1d17a95b (patch)
tree6c2667d56a16928c4a54ae52df853fbc219d2439 /crates
parentf776f0a75fb36deabab8e8cdf880389e4e2eb6e8 (diff)
Add color-managed CMYK to RGB conversion (#3288)
Diffstat (limited to 'crates')
-rw-r--r--crates/typst/Cargo.toml1
-rw-r--r--crates/typst/assets/CGATS001Compat-v2-micro.iccbin0 -> 8464 bytes
-rw-r--r--crates/typst/src/visualize/color.rs55
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
new file mode 100644
index 00000000..b5a73495
--- /dev/null
+++ b/crates/typst/assets/CGATS001Compat-v2-micro.icc
Binary files differ
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 {