mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
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",
|
"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",
|
||||||
|
@ -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
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 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
|
||||||
|
|
||||||
|
@ -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 }
|
||||||
|
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::{
|
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 |
@ -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%))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user