mirror of
https://github.com/storytold/craft-libs.git
synced 2026-11-03 01:00:48 +00:00
prepress: non_exhaustive public enums, and checked plate sizes for hand-made Plates
This commit is contained in:
1 parent
fde8b638c3
commit
bdace448d9
5 files changed
+40
-14
No files matched your search
@@ -19,6 +19,7 @@ pub struct CmsConversion {
|
||||
|
||||
/// Why a profile can't be a printing condition.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
|
||||
#[non_exhaustive]
|
||||
pub enum ProfileError {
|
||||
#[error("the profile could not be read: {0}")]
|
||||
Unreadable(String),
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
use craft_pdf::interpret::color::{CmykConversion, cmyk_to_srgb8};
|
||||
|
||||
use crate::cms::lab_to_srgb8;
|
||||
use crate::coverage::filled;
|
||||
use crate::{Alternate, InkId, Plates, Process};
|
||||
|
||||
/// Pixels converted per call into the CMYK profile, to bound the scratch buffers.
|
||||
@@ -60,10 +61,11 @@ impl Plates {
|
||||
/// colorimetric with black point compensation, or through the bundled CMYK profile when
|
||||
/// `cms` can't. Each spot plate is then multiplied over them by the spot's appearance at
|
||||
/// its tint, from its alternate. Spots whose Lab alternate craft-cms can't convert are
|
||||
/// left out.
|
||||
/// left out. Empty when `width × height × 3` overflows or can't be allocated.
|
||||
pub fn composite(&self, enabled: &[bool], cms: &dyn CmykConversion) -> Vec<u8> {
|
||||
let n = (self.width as usize).saturating_mul(self.height as usize);
|
||||
let mut rgb = vec![255_u8; n.saturating_mul(3)];
|
||||
let Some(mut rgb) = self.pixels().and_then(|n| n.checked_mul(3)).and_then(|len| filled(len, 255_u8)) else {
|
||||
return Vec::new();
|
||||
};
|
||||
let process: Vec<Option<&[u8]>> = Process::ALL
|
||||
.iter()
|
||||
.map(|p| {
|
||||
@@ -140,4 +142,14 @@ mod tests {
|
||||
let no_gold = p.composite(&[true, true, true, true, false], &cms);
|
||||
assert_eq!(&no_gold[6..9], black);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plates_too_large_to_hold_give_empty_results() {
|
||||
let p = crate::Plates { width: u32::MAX, height: u32::MAX, ..synthetic(0, 0, [50.0, 0.0, 0.0], Default::default()) };
|
||||
assert!(p.composite(&[], &CmsConversion::fogra39()).is_empty());
|
||||
assert!(p.tac(&[]).is_empty());
|
||||
assert_eq!(p.stats(&[]).max_total_at, None);
|
||||
assert!(p.above(10.0, &[]).regions().is_empty());
|
||||
assert_eq!(p.tint(crate::InkId::process(crate::Process::Black), u32::MAX - 1, u32::MAX - 1), None);
|
||||
}
|
||||
}
|
||||
@@ -66,6 +66,14 @@ pub struct Region {
|
||||
pub pixels: u32,
|
||||
}
|
||||
|
||||
/// `len` copies of `value`; `None` when the memory can't be had.
|
||||
pub(crate) fn filled<T: Clone>(len: usize, value: T) -> Option<Vec<T>> {
|
||||
let mut v = Vec::new();
|
||||
v.try_reserve_exact(len).ok()?;
|
||||
v.resize(len, value);
|
||||
Some(v)
|
||||
}
|
||||
|
||||
fn on(enabled: &[bool], index: usize) -> bool {
|
||||
enabled.get(index).copied().unwrap_or(true)
|
||||
}
|
||||
@@ -75,12 +83,16 @@ fn percent(tint: u64) -> f32 {
|
||||
}
|
||||
|
||||
impl Plates {
|
||||
fn pixels(&self) -> usize {
|
||||
(self.width as usize).saturating_mul(self.height as usize)
|
||||
/// `width × height`; `None` when it overflows `usize`, as it can for hand-made plates.
|
||||
pub(crate) fn pixels(&self) -> Option<usize> {
|
||||
usize::try_from(self.width).ok()?.checked_mul(usize::try_from(self.height).ok()?)
|
||||
}
|
||||
|
||||
fn index_of(&self, x: u32, y: u32) -> Option<usize> {
|
||||
(x < self.width && y < self.height).then(|| y as usize * self.width as usize + x as usize)
|
||||
pub(crate) fn index_of(&self, x: u32, y: u32) -> Option<usize> {
|
||||
if x >= self.width || y >= self.height {
|
||||
return None;
|
||||
}
|
||||
usize::try_from(y).ok()?.checked_mul(usize::try_from(self.width).ok()?)?.checked_add(usize::try_from(x).ok()?)
|
||||
}
|
||||
|
||||
fn enabled_planes<'a>(&'a self, enabled: &'a [bool]) -> impl Iterator<Item = &'a Vec<u8>> + 'a {
|
||||
@@ -112,9 +124,9 @@ impl Plates {
|
||||
}
|
||||
|
||||
/// The TAC of the enabled inks at every pixel, row-major, as a sum of tint bytes (255 per
|
||||
/// ink at 100%).
|
||||
/// ink at 100%). Empty when `width × height` overflows or can't be allocated.
|
||||
fn totals(&self, enabled: &[bool]) -> Vec<u32> {
|
||||
let mut totals = vec![0_u32; self.pixels()];
|
||||
let Some(mut totals) = self.pixels().and_then(|n| filled(n, 0_u32)) else { return Vec::new() };
|
||||
for plane in self.enabled_planes(enabled) {
|
||||
for (sum, tint) in totals.iter_mut().zip(plane) {
|
||||
*sum = sum.saturating_add(u32::from(*tint));
|
||||
@@ -130,7 +142,7 @@ impl Plates {
|
||||
|
||||
/// Each ink's figures over the page, and the TAC of the enabled inks.
|
||||
pub fn stats(&self, enabled: &[bool]) -> Stats {
|
||||
let n = self.pixels();
|
||||
let n = self.pixels().unwrap_or(0);
|
||||
let mean = |sum: u64| if n == 0 { 0.0 } else { percent(sum) / n as f32 };
|
||||
let inks = self
|
||||
.planes
|
||||
|
||||
@@ -359,6 +359,7 @@ impl InkSet {
|
||||
|
||||
/// A color as ink tints.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[non_exhaustive]
|
||||
pub enum InkColor {
|
||||
/// Tints `0.0..=1.0` of the listed inks; other inks are unspecified (they keep what is
|
||||
/// under the paint when it overprints, and are knocked out otherwise). One entry is a
|
||||
|
||||
@@ -53,6 +53,7 @@ const MAX_CONDITION: usize = 160;
|
||||
/// The printing condition process color converts to when the page has no usable output
|
||||
/// intent.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
#[non_exhaustive]
|
||||
pub enum Fallback {
|
||||
/// craft-cms's Coated FOGRA39 profile.
|
||||
#[default]
|
||||
@@ -82,6 +83,7 @@ impl Default for Options {
|
||||
|
||||
/// How process color that isn't CMYK already became CMYK.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[non_exhaustive]
|
||||
pub enum Conversion {
|
||||
/// Through the CMYK profile of the page's or document's output intent (ISO 32000-2
|
||||
/// §14.11.5).
|
||||
@@ -123,14 +125,12 @@ impl Plates {
|
||||
|
||||
/// Ink `id`'s tint at pixel (`x`, `y`), 0–255.
|
||||
pub fn tint(&self, id: InkId, x: u32, y: u32) -> Option<u8> {
|
||||
if x >= self.width || y >= self.height {
|
||||
return None;
|
||||
}
|
||||
self.plane(id)?.get(y as usize * self.width as usize + x as usize).copied()
|
||||
self.plane(id)?.get(self.index_of(x, y)?).copied()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[non_exhaustive]
|
||||
pub enum Error {
|
||||
#[error(transparent)]
|
||||
Open(#[from] OpenError),
|
||||
|
||||
Reference in new issue
Block a user