Merge pull request #9246 from DuBearte/fix/9236-cmyk-split-channels

fix(engine): CMYK Split Channels reads the composite's exact inks (#9236)
This commit is contained in:
Tanish Manku authored and GitHub committed 2026-11-11 00:01:49 -07:00
commit f12cd5dff3
2 files changed
+40 -1

No files matched your search

+14 -1
View File
@@ -72,8 +72,21 @@ pub(super) fn split(s: &mut Session, p: &Value) -> Result<Value> {
}
let doc = s.documents()[idx].doc.clone();
let area = doc.bounds();
let (px, n) = native_pixels(&doc, None)?;
let mode = edit_mode(&doc);
// A CMYK composite's channels are its inks: mixed on ink values where the stack allows, since
// a trip through RGB moves them by a few levels (#9236).
let inks = if mode == ColorMode::Cmyk { photocraft_compose::cmyk::render_cmyk(&doc, doc.bounds()) } else { None };
let (px, n) = match inks {
Some(buf) => {
let n = doc.pixel_format().channels();
let px: Vec<f32> = buf.px.iter().flat_map(|p| p.iter().copied().take(n)).collect();
if px.len() != buf.px.len() * n {
return Err(EngineError::Other(format!("Split Channels: an ink pixel has fewer than {n} values")));
}
(px, n)
}
None => native_pixels(&doc, None)?,
};
let names = color_names(mode);
let short = |name: &str| name.chars().next().map(|c| c.to_ascii_uppercase().to_string()).unwrap_or_default();
let mut docs: Vec<Document> = Vec::new();
@@ -898,6 +898,32 @@ fn split_and_merge_channels() {
assert!(s.execute("channel.merge", json!({"mode": "cmyk"})).is_err());
}
/// #9236: CMYK channels split into greyscale shown as the Channels panel shows them (white
/// paper, dark ink), and merge back into the same inks.
#[test]
fn split_and_merge_cmyk_keep_ink_dark() {
let mut s = Session::new();
s.execute("file.new", json!({"width": 40, "height": 20, "mode": "cmyk", "background": "white"})).unwrap();
// Rich black (#8359): its inks don't survive a trip through RGB, so the old path fails here.
let rgb = photocraft_color::convert::cmyk_to_rgb([1.0, 1.0, 1.0, 0.0]).map(|c| c.clamp(0.0, 1.0));
s.execute("tools.setColors", json!({"foreground": rgb, "foregroundCmyk": [1.0, 1.0, 1.0, 0.0]})).unwrap();
s.execute("select.rect", json!({"x": 0, "y": 0, "width": 20, "height": 20})).unwrap();
s.execute("edit.fillForeground", json!({})).unwrap();
s.execute("select.deselect", json!({})).unwrap();
let (ink, paper) = (px(&s, 1, 1), px(&s, 30, 1));
assert!(ink[..3].iter().all(|v| *v > 0.99) && paper[..4].iter().all(|v| *v < 0.01), "{ink:?} {paper:?}");
s.execute("channel.split", json!({})).unwrap();
assert_eq!(s.documents().len(), 4);
for (i, ink) in ink[..4].iter().enumerate() {
let at = |x| s.documents()[i].doc.layers[0].surface().unwrap().pixel(x, 1)[0];
assert!((at(1) - (1.0 - ink)).abs() < 0.003, "channel {i}: {} for ink {ink}", at(1));
assert!((at(30) - 1.0).abs() < 0.003, "channel {i}: paper is white, got {}", at(30));
}
s.execute("channel.merge", json!({"mode": "cmyk", "documents": [0, 1, 2, 3]})).unwrap();
let back = px(&s, 1, 1);
assert!(back[..4].iter().zip(&ink[..4]).all(|(a, b)| (a - b).abs() < 0.003), "{back:?} vs {ink:?}");
}
#[test]
fn cmyk_split_and_merge_use_tone() {
// #5787: Split Channels of a white CMYK image gives white grayscale documents, and merging