mirror of
https://github.com/storytold/craft-libs.git
synced 2026-10-31 07:39:23 +00:00
plate-test: mesh gradient paint and scene S17 (spot, process and DeviceN corners)
This commit is contained in:
1 parent
60dc2b54ce
commit
e5b73eb4cf
4 files changed
+199
-10
No files matched your search
@@ -52,6 +52,7 @@ Paints:
|
||||
| Several inks (DeviceN) | `{"inks": {"Cyan": 60, "Black": 40}}` |
|
||||
| Registration (`/All`) | `"registration"` |
|
||||
| Linear gradient | `{"linear": {"from": [x, y], "to": [x, y], "stops": [{"at": 0, "paint": …}, …]}}` |
|
||||
| Mesh gradient (one Coons patch with straight edges) | `{"mesh": {"rect": [x, y, w, h], "corners": [paint, paint, paint, paint]}}` (top left, top right, bottom right, bottom left) |
|
||||
|
||||
Images are synthetic: `{"px": [w, h], "ramp": [paint, paint]}` (left to right),
|
||||
`"split": [paint, paint]` (left and right halves) or `"feather": pt` (a gray soft-mask image,
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
{
|
||||
"id": "S17",
|
||||
"title": "Mesh gradient with a spot, process and DeviceN corner",
|
||||
"source": "storytold/craft-libs#184 (mesh gradients), CodeRabbit review",
|
||||
"page": [
|
||||
240,
|
||||
160
|
||||
],
|
||||
"inks": [
|
||||
{
|
||||
"name": "Gold",
|
||||
"cmyk": [
|
||||
0,
|
||||
20,
|
||||
60,
|
||||
20
|
||||
]
|
||||
}
|
||||
],
|
||||
"objects": [
|
||||
{
|
||||
"type": "rect",
|
||||
"rect": [
|
||||
20,
|
||||
20,
|
||||
200,
|
||||
120
|
||||
],
|
||||
"fill": {
|
||||
"mesh": {
|
||||
"rect": [
|
||||
20,
|
||||
20,
|
||||
200,
|
||||
120
|
||||
],
|
||||
"corners": [
|
||||
{
|
||||
"spot": "Gold",
|
||||
"tint": 100
|
||||
},
|
||||
{
|
||||
"cmyk": [
|
||||
100,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"inks": {
|
||||
"Magenta": 100,
|
||||
"Gold": 50
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmyk": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"expect": {
|
||||
"plates": [
|
||||
"Cyan",
|
||||
"Magenta",
|
||||
"Yellow",
|
||||
"Black",
|
||||
"Gold"
|
||||
],
|
||||
"points": [
|
||||
{
|
||||
"at": [
|
||||
24,
|
||||
24
|
||||
],
|
||||
"inks": {
|
||||
"Gold": 95
|
||||
},
|
||||
"tol": 10,
|
||||
"note": "top left: Gold"
|
||||
},
|
||||
{
|
||||
"at": [
|
||||
216,
|
||||
24
|
||||
],
|
||||
"inks": {
|
||||
"Cyan": 95
|
||||
},
|
||||
"tol": 10,
|
||||
"note": "top right: Cyan"
|
||||
},
|
||||
{
|
||||
"at": [
|
||||
216,
|
||||
136
|
||||
],
|
||||
"inks": {
|
||||
"Magenta": 95,
|
||||
"Gold": 47
|
||||
},
|
||||
"tol": 10,
|
||||
"note": "bottom right: Magenta and half Gold"
|
||||
},
|
||||
{
|
||||
"at": [
|
||||
24,
|
||||
136
|
||||
],
|
||||
"inks": {},
|
||||
"tol": 10,
|
||||
"note": "bottom left: paper"
|
||||
},
|
||||
{
|
||||
"at": [
|
||||
120,
|
||||
80
|
||||
],
|
||||
"inks": {
|
||||
"Gold": 37.5,
|
||||
"Cyan": 25,
|
||||
"Magenta": 25
|
||||
},
|
||||
"tol": 10,
|
||||
"note": "center: bilinear mix of the corners"
|
||||
}
|
||||
]
|
||||
},
|
||||
"structure": {
|
||||
"page_group": "none",
|
||||
"smask": false,
|
||||
"color_spaces_include": [
|
||||
"DeviceN(Cyan,Magenta,Gold, alt DeviceCMYK)"
|
||||
],
|
||||
"device_rgb": false,
|
||||
"output_intents": []
|
||||
},
|
||||
"note": "Corner colors in different ink spaces share one DeviceN space: process colorants first, then the spot."
|
||||
}
|
||||
@@ -20,7 +20,7 @@ use krilla::image::{BitsPerComponent, CustomImage, Image, ImageColorspace};
|
||||
use krilla::mask::{Mask as KrillaMask, MaskType, TransferFunction};
|
||||
use krilla::num::NormalizedF32;
|
||||
use krilla::page::PageSettings;
|
||||
use krilla::paint::{Fill, LinearGradient, Paint as KrillaPaint, SpreadMethod, Stop, Stroke as KrillaStroke};
|
||||
use krilla::paint::{Fill, LinearGradient, MeshGradient, MeshPatch, Paint as KrillaPaint, SpreadMethod, Stop, Stroke as KrillaStroke};
|
||||
use krilla::pdf::{Pdf, PdfDocument};
|
||||
use krilla::surface::Surface;
|
||||
use krilla::text::{Font, TextDirection};
|
||||
@@ -318,12 +318,7 @@ impl Resolver<'_, '_> {
|
||||
fn paint(&self, paint: &Paint) -> Result<KrillaPaint, Error> {
|
||||
match paint {
|
||||
Paint::Linear { linear } => {
|
||||
// krilla brings ink-based stops into one space; RGB and registration don't mix.
|
||||
let space = linear.stops.first().map(|s| space_of(&s.paint));
|
||||
let mixed = linear.stops.iter().any(|s| Some(space_of(&s.paint)) != space);
|
||||
if mixed && linear.stops.iter().any(|s| matches!(s.paint, Paint::Rgb { .. } | Paint::Named(_))) {
|
||||
return Err(unsupported("gradient between RGB or registration and other color spaces"));
|
||||
}
|
||||
mixable(linear.stops.iter().map(|s| &s.paint))?;
|
||||
let mut stops = Vec::new();
|
||||
for stop in &linear.stops {
|
||||
let offset = NormalizedF32::new(stop.at).ok_or_else(|| self.bad("gradient stop outside 0–1"))?;
|
||||
@@ -341,6 +336,23 @@ impl Resolver<'_, '_> {
|
||||
}
|
||||
.into())
|
||||
}
|
||||
Paint::Mesh { mesh } => {
|
||||
mixable(mesh.corners.iter())?;
|
||||
let [x, y, w, h] = mesh.rect;
|
||||
let corners = [(x, y), (x + w, y), (x + w, y + h), (x, y + h)];
|
||||
let mut points = [(0.0, 0.0); 12];
|
||||
for (i, &(ax, ay)) in corners.iter().enumerate() {
|
||||
let (bx, by) = corners[(i + 1) % 4];
|
||||
for step in 0..3 {
|
||||
let t = step as f32 / 3.0;
|
||||
points[3 * i + step] = (ax + (bx - ax) * t, ay + (by - ay) * t);
|
||||
}
|
||||
}
|
||||
let [a, b, c, d] = &mesh.corners;
|
||||
let colors = [self.color(a)?, self.color(b)?, self.color(c)?, self.color(d)?];
|
||||
let patch = MeshPatch { points, colors, opacities: [NormalizedF32::ONE; 4] };
|
||||
Ok(MeshGradient { patches: vec![patch], transform: Transform::identity(), anti_alias: false }.into())
|
||||
}
|
||||
solid => Ok(self.color(solid)?.into()),
|
||||
}
|
||||
}
|
||||
@@ -359,7 +371,7 @@ impl Resolver<'_, '_> {
|
||||
separation::Color::new(byte(*tint), SeparationSpace::new(SeparationColorant::Custom(spot.clone()), alternate)).into()
|
||||
}
|
||||
Paint::Inks { inks } => self.device_n(inks)?.into(),
|
||||
Paint::Linear { .. } => return Err(self.bad("gradients can't nest")),
|
||||
Paint::Linear { .. } | Paint::Mesh { .. } => return Err(self.bad("gradients can't nest")),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -457,7 +469,7 @@ impl Resolver<'_, '_> {
|
||||
Paint::Rgb { rgb } => (ImageColorspace::Rgb, rgb.to_vec()),
|
||||
Paint::Inks { .. } | Paint::Spot { .. } => return Err(self.bad("image paints must share a color space")),
|
||||
Paint::Named(_) => return Err(unsupported("registration image")),
|
||||
Paint::Linear { .. } => return Err(self.bad("images take solid paints")),
|
||||
Paint::Linear { .. } | Paint::Mesh { .. } => return Err(self.bad("images take solid paints")),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -475,6 +487,16 @@ enum Space<'a> {
|
||||
Other,
|
||||
}
|
||||
|
||||
/// krilla brings ink-based gradient colors into one space; RGB and registration don't mix.
|
||||
fn mixable<'a>(paints: impl Iterator<Item = &'a Paint> + Clone) -> Result<(), Error> {
|
||||
let space = paints.clone().next().map(space_of);
|
||||
let mixed = paints.clone().any(|p| Some(space_of(p)) != space);
|
||||
if mixed && paints.into_iter().any(|p| matches!(p, Paint::Rgb { .. } | Paint::Named(_))) {
|
||||
return Err(unsupported("gradient between RGB or registration and other color spaces"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn space_of(paint: &Paint) -> Space<'_> {
|
||||
match paint {
|
||||
Paint::Cmyk { .. } => Space::Cmyk,
|
||||
|
||||
@@ -169,6 +169,9 @@ pub enum Paint {
|
||||
Linear {
|
||||
linear: Gradient,
|
||||
},
|
||||
Mesh {
|
||||
mesh: Box<Mesh>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
|
||||
@@ -190,6 +193,15 @@ pub struct Gradient {
|
||||
pub stops: Vec<GradientStop>,
|
||||
}
|
||||
|
||||
/// A mesh gradient of one Coons patch with straight edges over `rect` (page coordinates).
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Mesh {
|
||||
pub rect: [f32; 4],
|
||||
/// Solid paints at the top-left, top-right, bottom-right and bottom-left corners.
|
||||
pub corners: [Paint; 4],
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct GradientStop {
|
||||
@@ -520,12 +532,20 @@ impl Scene {
|
||||
out.push("gradients need at least two stops".into());
|
||||
}
|
||||
for stop in &linear.stops {
|
||||
if matches!(stop.paint, Paint::Linear { .. }) {
|
||||
if matches!(stop.paint, Paint::Linear { .. } | Paint::Mesh { .. }) {
|
||||
out.push("gradient stops must be solid paints".into());
|
||||
}
|
||||
self.paint_problems(&stop.paint, out);
|
||||
}
|
||||
}
|
||||
Paint::Mesh { mesh } => {
|
||||
for corner in &mesh.corners {
|
||||
if matches!(corner, Paint::Linear { .. } | Paint::Mesh { .. }) {
|
||||
out.push("mesh corners must be solid paints".into());
|
||||
}
|
||||
self.paint_problems(corner, out);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user