* fix: recognize paragraph drafts as unsaved work (Issue #811) * fix(color): honour the /Lab colour space /WhitePoint (#1203) The old path fed every Lab value to the bundled ICC transform as if the space's white point were D50, so a /Lab space with any other /WhitePoint (e.g. D65 [0.9505 1 1.089]) rendered off by several levels per channel and a D65 blue came out 45 levels too red. Map L*a*b* to XYZ in the space's own white coordinates (ISO 32000-2 §8.6.5.4), Bradford-adapt it to D65 and convert to sRGB directly, as Poppler does. Missing or degenerate white points fall back to a flat white and hostile components are clamped without panicking. Add a render regression that checks the five reported flat patches of the D65 white-point fixture against their CIE sRGB values. * Lab: map normalised Indexed palette components onto L*/Range; drop unused LAB.icc An /Indexed palette over Lab passes its entries as byte/255 (manual_scale), which the new Lab conversion read as raw L*a*b* and drew near black. Map them onto L* 0..100 and the a*/b* /Range, with a regression test. Record the patch as hayro-interpret (25) in vendor/README.md, and remove LAB.icc (no longer referenced) with its ATTRIBUTION, NOTICE and assets README entries. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Main gates: rustfmt tests/viewer.rs; a type alias for the refresh-steps array (clippy type_complexity) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Brandon Thomas <echelon@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
13637b8440
commit
bde9dc52fe
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
Every asset PdfCraft includes, bundles or uses to build its published material, with its author, source and licence. The policy is in [AGENTS.md](AGENTS.md) §1. The machine-readable list, with SHA-256 hashes, is [ATTRIBUTION.toml](ATTRIBUTION.toml). Licence texts are kept beside the assets and summarised in [NOTICE](NOTICE).
|
||||
|
||||
## In this repository (269)
|
||||
## In this repository (268)
|
||||
|
||||
| Asset | Title | Author | Licence | Source | Used for |
|
||||
|---|---|---|---|---|---|
|
||||
|
|
@ -221,7 +221,6 @@ Every asset PdfCraft includes, bundles or uses to build its published material,
|
|||
| `vendor/hayro-interpret/assets/FoxitSerifItalic.pfb` | FoxitSerifItalic (standard-14 substitute font) | Foxit Software Inc. / PDFium Authors | BSD-3-Clause | https://pdfium.googlesource.com/pdfium (via hayro-interpret 0.7.0 assets/) | Renders non-embedded standard-14 fonts (vendored hayro-interpret) |
|
||||
| `vendor/hayro-interpret/assets/FoxitSymbol.pfb` | FoxitSymbol (standard-14 substitute font) | Foxit Software Inc. / PDFium Authors | BSD-3-Clause | https://pdfium.googlesource.com/pdfium (via hayro-interpret 0.7.0 assets/) | Renders non-embedded standard-14 fonts (vendored hayro-interpret) |
|
||||
| `vendor/hayro-interpret/assets/CGATS001Compat-v2-micro.icc` | CGATS001Compat v2 micro ICC profile | Clinton Ingram (saucecontrol/Compact-ICC-Profiles) | CC0-1.0 | https://github.com/saucecontrol/Compact-ICC-Profiles | Default CMYK colour conversion (vendored hayro-interpret) |
|
||||
| `vendor/hayro-interpret/assets/LAB.icc` | Lab abstract ICC profile | The Hayro Authors (generated with LittleCMS 2) | MIT OR Apache-2.0 | https://github.com/LaurenzV/hayro (hayro-interpret 0.7.0 assets/) | Lab colour space conversion (vendored hayro-interpret) |
|
||||
| `docs/brand/artcraft-logo-white.png` | ArtCraft brand: artcraft-logo-white | ArtCraft Team | LicenseRef-Storyteller-Trademark | ArtCraft Team brand assets (provided by the project owner, 2026-10-01; identical copies in storytold/photocraft) | ArtCraft branding next to the community links (About dialog, Help menu, home screen) and in docs. ArtCraft Team trademark, not open source; may be used only unmodified within PdfCraft; forks and modified versions must remove it. |
|
||||
| `docs/brand/artcraft-logo-white.svg` | ArtCraft brand: artcraft-logo-white | ArtCraft Team | LicenseRef-Storyteller-Trademark | ArtCraft Team brand assets (provided by the project owner, 2026-10-01; identical copies in storytold/photocraft) | ArtCraft branding next to the community links (About dialog, Help menu, home screen) and in docs. ArtCraft Team trademark, not open source; may be used only unmodified within PdfCraft; forks and modified versions must remove it. |
|
||||
| `docs/brand/artcraft-logo.png` | ArtCraft brand: artcraft-logo | ArtCraft Team | LicenseRef-Storyteller-Trademark | ArtCraft Team brand assets (provided by the project owner, 2026-10-01; identical copies in storytold/photocraft) | ArtCraft branding next to the community links (About dialog, Help menu, home screen) and in docs. ArtCraft Team trademark, not open source; may be used only unmodified within PdfCraft; forks and modified versions must remove it. |
|
||||
|
|
|
|||
|
|
@ -2353,17 +2353,6 @@ kind = "data"
|
|||
usage = "Default CMYK colour conversion (vendored hayro-interpret)"
|
||||
sha256 = "73e1ba37d2bad5bab2a964f40a9eed96209666efc067c3322626214bbef234a0"
|
||||
|
||||
[[asset]]
|
||||
path = "vendor/hayro-interpret/assets/LAB.icc"
|
||||
title = "Lab abstract ICC profile"
|
||||
author = "The Hayro Authors (generated with LittleCMS 2)"
|
||||
source = "https://github.com/LaurenzV/hayro (hayro-interpret 0.7.0 assets/)"
|
||||
licence = "MIT OR Apache-2.0"
|
||||
licence_file = "vendor/hayro-interpret/LICENSE-MIT"
|
||||
kind = "data"
|
||||
usage = "Lab colour space conversion (vendored hayro-interpret)"
|
||||
sha256 = "a6efc5c30ece826eba2a83ddf5aa7026289b0c5ffa90acb129bc59ab8d7f5c59"
|
||||
|
||||
[[bundled]]
|
||||
crate = "epaint_default_fonts"
|
||||
version = "0.36.2"
|
||||
|
|
|
|||
3
NOTICE
3
NOTICE
|
|
@ -35,8 +35,7 @@ list (author, source, license) is in ATTRIBUTION.md.
|
|||
- lopdf (bootstrap document inspection, via crates.io) — MIT.
|
||||
- egui default fonts (epaint_default_fonts, via eframe) — Ubuntu Light (Ubuntu Font Licence 1.0),
|
||||
Hack (MIT / Bitstream Vera licence), Noto Emoji (SIL OFL 1.1), emoji-icon-font (MIT).
|
||||
- Colour profiles in hayro-interpret — CGATS001Compat-v2-micro.icc (CC0-1.0, Clinton Ingram) and
|
||||
LAB.icc (generated by the hayro authors with LittleCMS 2; MIT OR Apache-2.0).
|
||||
- Colour profile in hayro-interpret — CGATS001Compat-v2-micro.icc (CC0-1.0, Clinton Ingram).
|
||||
|
||||
Required notices:
|
||||
|
||||
|
|
|
|||
|
|
@ -3925,7 +3925,8 @@ fn edits_let_go_of_the_previous_working_file() {
|
|||
|| Edit::AddWatermark { pages: vec![0], settings: Watermark { text: "Draft".into(), ..Watermark::default() }, file: None, replace: false };
|
||||
s.apply(id, Edit::RotatePages { pages: vec![1], degrees: 90 }).unwrap();
|
||||
// A full refresh, a comment refresh, a content refresh, then undo and redo.
|
||||
let steps: [(&str, Box<dyn Fn(&mut Session)>); 5] = [
|
||||
type Step<'a> = Box<dyn Fn(&mut Session) + 'a>;
|
||||
let steps: [(&str, Step<'_>); 5] = [
|
||||
("rotate", Box::new(|s: &mut Session| s.apply(id, Edit::RotatePages { pages: vec![0], degrees: 90 }).unwrap())),
|
||||
("comment", Box::new(move |s: &mut Session| s.apply(id, comment()).unwrap())),
|
||||
("watermark", Box::new(move |s: &mut Session| s.apply(id, watermark()).unwrap())),
|
||||
|
|
|
|||
|
|
@ -1905,6 +1905,98 @@ mod tests {
|
|||
assert!(remainder.is_empty());
|
||||
assert!(pixels.iter().all(|pixel| *pixel == [0, 0, 255, 255]));
|
||||
}
|
||||
/// #1203: a /Lab colour space must honour its own /WhitePoint. The old path
|
||||
/// fed every Lab value to the ICC transform as if the white point were D50,
|
||||
/// shifting non-D50 pages several levels per channel (for example a D65
|
||||
/// blue came out 45 levels too red). Conversion and expected values follow
|
||||
/// ISO 32000-2 §8.6.5.4 with the space's own /WhitePoint [0.9505 1 1.089].
|
||||
#[test]
|
||||
fn lab_honours_the_space_white_point() {
|
||||
use super::*;
|
||||
let render = |l: f32, a: f32, b: f32| -> [u8; 4] {
|
||||
let content = format!("/L cs {l} {a} {b} sc 0 0 200 200 re f");
|
||||
let bytes = format!(
|
||||
"%PDF-1.7\n1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj\n\
|
||||
2 0 obj << /Type /Pages /Kids [3 0 R] /Count 1 >> endobj\n\
|
||||
3 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 200 200] /Resources << /ColorSpace << /L [/Lab << /WhitePoint [0.9505 1 1.089] /Range [-100 100 -100 100] >>] >> >> /Contents 4 0 R >> endobj\n\
|
||||
4 0 obj << /Length {} >> stream\n{content}\nendstream endobj\n\
|
||||
trailer << /Root 1 0 R >>\n%%EOF",
|
||||
content.len()
|
||||
)
|
||||
.into_bytes();
|
||||
let page = PageRenderer::new(Arc::new(bytes), RenderConfig::default()).render(RenderRequest {
|
||||
page: 0,
|
||||
kind: RequestKind::Pixels,
|
||||
tile: None,
|
||||
scale: 1.0,
|
||||
tag: 0,
|
||||
preview: false,
|
||||
});
|
||||
assert!(page.error.is_none(), "Lab ({l} {a} {b}): {:?}", page.error);
|
||||
assert_eq!((page.width, page.height), (200, 200));
|
||||
page.rgba[(100 * page.width as usize + 100) * 4..][..4].try_into().unwrap()
|
||||
};
|
||||
|
||||
for (l, a, b, expected) in [
|
||||
(40.0, 20.0, -60.0, [19, 90, 194, 255]),
|
||||
(70.0, -50.0, 30.0, [77, 193, 114, 255]),
|
||||
(60.0, 0.0, -40.0, [87, 148, 215, 255]),
|
||||
(50.0, 60.0, 40.0, [217, 61, 55, 255]),
|
||||
(30.0, 40.0, 20.0, [130, 37, 42, 255]),
|
||||
] {
|
||||
let got = render(l, a, b);
|
||||
for (channel, (got, want)) in got.iter().zip(expected).enumerate() {
|
||||
assert!((*got).abs_diff(want) <= 2, "Lab ({l} {a} {b}) channel {channel}: got {got:?}, expected {expected:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
/// #1203: an `/Indexed` palette over Lab looks its entries up as
|
||||
/// `byte / 255`, which must map onto L* 0..100 and the a*/b* /Range. They
|
||||
/// were read as raw L*a*b* (L* below 1), so such palettes drew near black.
|
||||
#[test]
|
||||
fn indexed_palettes_over_lab_keep_their_colours() {
|
||||
use super::*;
|
||||
let lab = "[/Lab << /WhitePoint [0.9505 1 1.089] /Range [-100 100 -100 100] >>]";
|
||||
let render = |content: &str| -> [u8; 4] {
|
||||
let bytes = format!(
|
||||
"%PDF-1.7\n1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj\n\
|
||||
2 0 obj << /Type /Pages /Kids [3 0 R] /Count 1 >> endobj\n\
|
||||
3 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 200 200] /Resources << /ColorSpace << /L {lab} /I [/Indexed {lab} 1 <000000 80CCB2>] >> >> /Contents 4 0 R >> endobj\n\
|
||||
4 0 obj << /Length {} >> stream\n{content}\nendstream endobj\n\
|
||||
trailer << /Root 1 0 R >>\n%%EOF",
|
||||
content.len()
|
||||
)
|
||||
.into_bytes();
|
||||
let page = PageRenderer::new(Arc::new(bytes), RenderConfig::default()).render(RenderRequest {
|
||||
page: 0,
|
||||
kind: RequestKind::Pixels,
|
||||
tile: None,
|
||||
scale: 1.0,
|
||||
tag: 0,
|
||||
preview: false,
|
||||
});
|
||||
assert!(page.error.is_none(), "{content}: {:?}", page.error);
|
||||
assert_eq!((page.width, page.height), (200, 200));
|
||||
let start = (100 * page.width as usize + 100) * 4;
|
||||
let mut pixel = [0; 4];
|
||||
pixel.copy_from_slice(page.rgba.get(start..start + 4).expect("centre pixel"));
|
||||
pixel
|
||||
};
|
||||
|
||||
// Palette entry 1 is <80 CC B2>: L* = 128/255·100, a* = -100 + 204/255·200,
|
||||
// b* = -100 + 178/255·200, about Lab (50.2 60 39.6): a warm red.
|
||||
let indexed = render("/I cs 1 sc 0 0 200 200 re f");
|
||||
let direct = render(&format!(
|
||||
"/L cs {} {} {} sc 0 0 200 200 re f",
|
||||
128.0 / 255.0 * 100.0,
|
||||
-100.0 + 204.0 / 255.0 * 200.0,
|
||||
-100.0 + 178.0 / 255.0 * 200.0
|
||||
));
|
||||
for (channel, (got, want)) in indexed.iter().zip(direct).enumerate() {
|
||||
assert!(got.abs_diff(want) <= 1, "channel {channel}: indexed {indexed:?}, direct {direct:?}");
|
||||
}
|
||||
assert!(indexed[0] > 180, "the palette red must not render near black: {indexed:?}");
|
||||
}
|
||||
#[test]
|
||||
fn watchdog_skips_a_stuck_page_and_keeps_rendering() {
|
||||
use super::*;
|
||||
|
|
|
|||
|
|
@ -193,9 +193,7 @@ fn marking_while_searching_does_not_jitter_or_reset_search_state() {
|
|||
assert!(h.state().views[0].goto.is_none());
|
||||
|
||||
// User draws or marks an annotation on page 0
|
||||
let shape = pdfcraft_engine::Shape::Ink {
|
||||
strokes: vec![vec![[10.0, 10.0], [20.0, 20.0]]],
|
||||
};
|
||||
let shape = pdfcraft_engine::Shape::Ink { strokes: vec![vec![[10.0, 10.0], [20.0, 20.0]]] };
|
||||
let stroke = pdfcraft_engine::Edit::AddAnnotation(pdfcraft_engine::NewAnnotation {
|
||||
page: 0,
|
||||
style: pdfcraft_engine::Style::default_for(&shape),
|
||||
|
|
@ -206,11 +204,7 @@ fn marking_while_searching_does_not_jitter_or_reset_search_state() {
|
|||
h.state_mut().apply_edit(stroke);
|
||||
|
||||
// Right after applying the edit, search matches and current match must still be valid
|
||||
assert_eq!(
|
||||
h.state().views[0].find.as_ref().unwrap().current,
|
||||
current_before,
|
||||
"applying an annotation must not wipe current search match"
|
||||
);
|
||||
assert_eq!(h.state().views[0].find.as_ref().unwrap().current, current_before, "applying an annotation must not wipe current search match");
|
||||
|
||||
// Let the canvas process the reload
|
||||
let reload_deadline = std::time::Instant::now() + std::time::Duration::from_secs(5);
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
Binary file not shown.
|
|
@ -10,6 +10,4 @@ Copyright 2014 PDFium Authors. All rights reserved.
|
|||
found in the LICENSE file.
|
||||
|
||||
Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com
|
||||
```
|
||||
|
||||
The LAB.icc profile was generated with LCMS2.
|
||||
```
|
||||
|
|
@ -11,7 +11,7 @@ use hayro_syntax::object::Stream;
|
|||
use hayro_syntax::object::dict::keys::*;
|
||||
use moxcms::{
|
||||
ColorProfile, DataColorSpace, Layout, Transform8BitExecutor, TransformF32Executor,
|
||||
TransformOptions, Xyzd,
|
||||
TransformOptions,
|
||||
};
|
||||
use smallvec::{SmallVec, ToSmallVec, smallvec};
|
||||
use std::borrow::Cow;
|
||||
|
|
@ -601,11 +601,7 @@ impl CalRgb {
|
|||
Self::matrix_product(&Self::BRADFORD_SCALE_INVERSE_MATRIX, &lms_flat)
|
||||
}
|
||||
|
||||
fn normalize_white_point_to_d65(
|
||||
&self,
|
||||
source_white_point: &[f32; 3],
|
||||
xyz: &[f32; 3],
|
||||
) -> [f32; 3] {
|
||||
fn normalize_white_point_to_d65(source_white_point: &[f32; 3], xyz: &[f32; 3]) -> [f32; 3] {
|
||||
let lms = Self::matrix_product(&Self::BRADFORD_SCALE_MATRIX, xyz);
|
||||
let lms_d65 = Self::to_d65(source_white_point, &lms);
|
||||
Self::matrix_product(&Self::BRADFORD_SCALE_INVERSE_MATRIX, &lms_d65)
|
||||
|
|
@ -637,7 +633,7 @@ impl ToRgb for CalRgb {
|
|||
|
||||
let xyz_flat = self.normalize_white_point_to_flat(&self.white_point, &xyz);
|
||||
let xyz_black = Self::compensate_black_point(&self.black_point, &xyz_flat);
|
||||
let xyz_d65 = self.normalize_white_point_to_d65(&Self::FLAT_WHITEPOINT, &xyz_black);
|
||||
let xyz_d65 = Self::normalize_white_point_to_d65(&Self::FLAT_WHITEPOINT, &xyz_black);
|
||||
let srgb_xyz = Self::matrix_product(&Self::SRGB_D65_XYZ_TO_RGB_MATRIX, &xyz_d65);
|
||||
|
||||
output.copy_from_slice(&[
|
||||
|
|
@ -654,58 +650,93 @@ impl ToRgb for CalRgb {
|
|||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct Lab {
|
||||
range: [f32; 4],
|
||||
profile: ICCProfile,
|
||||
white_point: [f32; 3],
|
||||
}
|
||||
|
||||
impl Lab {
|
||||
fn new(dict: &Dict<'_>) -> Option<Self> {
|
||||
let white_point = dict.get::<[f32; 3]>(WHITE_POINT).unwrap_or([1.0, 1.0, 1.0]);
|
||||
// Not sure how this should be used.
|
||||
let _black_point = dict.get::<[f32; 3]>(BLACK_POINT).unwrap_or([0.0, 0.0, 0.0]);
|
||||
let range = dict
|
||||
.get::<[f32; 4]>(RANGE)
|
||||
.unwrap_or([-100.0, 100.0, -100.0, 100.0]);
|
||||
|
||||
let mut profile = ColorProfile::new_from_slice(include_bytes!("../assets/LAB.icc")).ok()?;
|
||||
profile.white_point = Xyzd::new(
|
||||
white_point[0] as f64,
|
||||
white_point[1] as f64,
|
||||
white_point[2] as f64,
|
||||
);
|
||||
Some(Self { range, white_point })
|
||||
}
|
||||
|
||||
let profile = ICCProfile::new_from_src_profile(
|
||||
profile, false,
|
||||
// This flag is only used to scale the values to [0.0, 1.0], but
|
||||
// we already take care of this in the `convert_f32` method.
|
||||
// Therefore, leave this as false, even though this is a LAB profile.
|
||||
false, 3,
|
||||
)?;
|
||||
/// The white point the space's L\*a\*b\* values are relative to (ISO 32000-2
|
||||
/// §8.6.5.4). A missing or degenerate entry falls back to a flat white point
|
||||
/// so hostile input cannot break the conversion.
|
||||
fn usable_white_point(&self) -> [f32; 3] {
|
||||
let white_point = self.white_point;
|
||||
if white_point
|
||||
.iter()
|
||||
.all(|component| component.is_finite() && *component > 0.0)
|
||||
{
|
||||
white_point
|
||||
} else {
|
||||
[1.0, 1.0, 1.0]
|
||||
}
|
||||
}
|
||||
|
||||
Some(Self { range, profile })
|
||||
/// Inverse of the CIE f(t) helper that maps relative fX/fY/fZ to absolute
|
||||
/// XYZ as a ratio of the white point.
|
||||
fn inverse_lab_f(t: f32) -> f32 {
|
||||
const DELTA: f32 = 6.0 / 29.0;
|
||||
if t > DELTA {
|
||||
t * t * t
|
||||
} else {
|
||||
3.0 * DELTA * DELTA * (t - 4.0 / 29.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToRgb for Lab {
|
||||
fn convert_f32(&self, input: &[f32], output: &mut [u8], manual_scale: bool) -> Option<()> {
|
||||
if !manual_scale {
|
||||
// moxcms expects values between 0.0 and 1.0, so we need to undo
|
||||
// the scaling.
|
||||
let [min_a, max_a, min_b, max_b] = self.range;
|
||||
let white_point = self.usable_white_point();
|
||||
let [xw, yw, zw] = white_point;
|
||||
|
||||
let input = input
|
||||
.chunks_exact(3)
|
||||
.flat_map(|i| {
|
||||
let l = i[0] / 100.0;
|
||||
let a = (i[1] + 128.0) / 255.0;
|
||||
let b = (i[2] + 128.0) / 255.0;
|
||||
for (input, output) in input.chunks_exact(3).zip(output.chunks_exact_mut(3)) {
|
||||
// PdfCraft patch: `manual_scale` means the components arrive
|
||||
// normalised to [0, 1] (an `/Indexed` palette over Lab looks its
|
||||
// bytes up as `byte / 255`). Map them linearly onto L* 0..100 and
|
||||
// the a*/b* /Range (ISO 32000-2 §8.6.6.3) before converting.
|
||||
let (l, a, b) = if manual_scale {
|
||||
(
|
||||
clamp_component(input[0], 0.0, 1.0) * 100.0,
|
||||
min_a + clamp_component(input[1], 0.0, 1.0) * (max_a - min_a),
|
||||
min_b + clamp_component(input[2], 0.0, 1.0) * (max_b - min_b),
|
||||
)
|
||||
} else {
|
||||
(input[0], input[1], input[2])
|
||||
};
|
||||
let l = clamp_component(l, 0.0, 100.0);
|
||||
let a = clamp_component(a, min_a, max_a);
|
||||
let b = clamp_component(b, min_b, max_b);
|
||||
|
||||
[l, a, b]
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
// L*a*b* is relative to the space's own /WhitePoint. Map it to XYZ
|
||||
// in that white's coordinates, Bradford-adapt to D65 and convert to
|
||||
// sRGB (ISO 32000-2 §8.6.5.4).
|
||||
let fy = (l + 16.0) / 116.0;
|
||||
let fx = fy + a / 500.0;
|
||||
let fz = fy - b / 200.0;
|
||||
let xyz = [
|
||||
xw * Self::inverse_lab_f(fx),
|
||||
yw * Self::inverse_lab_f(fy),
|
||||
zw * Self::inverse_lab_f(fz),
|
||||
];
|
||||
|
||||
self.profile.convert_f32(&input, output, manual_scale)
|
||||
} else {
|
||||
self.profile.convert_f32(input, output, manual_scale)
|
||||
let xyz_d65 = CalRgb::normalize_white_point_to_d65(&white_point, &xyz);
|
||||
let srgb = CalRgb::matrix_product(&CalRgb::SRGB_D65_XYZ_TO_RGB_MATRIX, &xyz_d65);
|
||||
|
||||
output.copy_from_slice(&[
|
||||
f32_to_u8(CalRgb::srgb_transfer_function(srgb[0])),
|
||||
f32_to_u8(CalRgb::srgb_transfer_function(srgb[1])),
|
||||
f32_to_u8(CalRgb::srgb_transfer_function(srgb[2])),
|
||||
]);
|
||||
}
|
||||
|
||||
Some(())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1029,6 +1060,15 @@ fn f32_to_u8(val: f32) -> u8 {
|
|||
(val * 255.0 + 0.5) as u8
|
||||
}
|
||||
|
||||
/// Clamp an input-derived colour component to `[lo, hi]` without panicking on
|
||||
/// NaN or on a reversed range. Non-finite values map to zero.
|
||||
fn clamp_component(value: f32, lo: f32, hi: f32) -> f32 {
|
||||
if !value.is_finite() {
|
||||
return 0.0;
|
||||
}
|
||||
value.max(lo).min(hi)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
/// A color.
|
||||
pub struct Color {
|
||||
|
|
|
|||
Loading…
Reference in New Issue