Add color-managed CMYK to RGB conversion (#3288)
This commit is contained in:
parent
f776f0a75f
commit
9f1e0390c1
7
Cargo.lock
generated
7
Cargo.lock
generated
@ -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",
|
||||
|
@ -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
3
NOTICE
@ -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
|
||||
|
||||
|
@ -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 }
|
||||
|
BIN
crates/typst/assets/CGATS001Compat-v2-micro.icc
Normal file
BIN
crates/typst/assets/CGATS001Compat-v2-micro.icc
Normal file
Binary file not shown.
@ -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 |
@ -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%))
|
||||
|
Loading…
x
Reference in New Issue
Block a user