mirror of
https://github.com/storytold/designcraft.git
synced 2026-10-14 19:27:07 -04:00
E: CMYK transparency blend space on screen
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
e856b849bb
commit
558e798dfc
6 files changed
+89
-10
No files matched your search
+6
-6
@@ -49,7 +49,7 @@ DesignCraft aims at full Adobe InDesign parity — and to be better: faster, ope
|
||||
- Endnotes (`endnote.insert/edit/delete/list/options`, Type ▸ Insert Endnote): references number through the document, the endnote frame on a new last page lists them under a heading; IDML keeps the listed text only.
|
||||
- Editorial notes (`note.new/edit/delete/convertToText/list`, Notes panel): anchored in text, flagged on screen, never printed or exported.
|
||||
- File ▸ Print Booklet (`file.printBooklet`): saddle-stitch or 2-up consecutive printer spreads as PDF, padded with blanks, with a gap between pages.
|
||||
- Edit ▸ Transparency Blend Space (`edit.transparencyBlendSpace`; RGB for web/mobile documents; IDML TransparencyPreference) — exported PDFs give pages with transparency a DeviceCMYK or DeviceRGB page group, so viewers and RIPs blend in that space; on-screen compositing is still RGB.
|
||||
- Edit ▸ Transparency Blend Space (`edit.transparencyBlendSpace`; RGB for web/mobile documents; IDML TransparencyPreference) — exported PDFs give pages with transparency a DeviceCMYK or DeviceRGB page group, so viewers and RIPs blend in that space. On screen, spreads with transparency in a CMYK blend space are shown through the working CMYK profile (their colours keep to its gamut, as when composited in CMYK).
|
||||
- High Contrast interface theme (fifth Interface Color Theme: black chrome, white text, yellow selection).
|
||||
- Place Excel workbooks (.xlsx): the first worksheet's used range becomes a table (shared/inline strings, numbers, booleans).
|
||||
- Object Library (`library.new/open/add/place/remove/list/json/close`, Library panel): `.dclib` files of snippets that keep stories, styles, swatches and images.
|
||||
@@ -167,18 +167,18 @@ DesignCraft aims at full Adobe InDesign parity — and to be better: faster, ope
|
||||
|
||||
## How far from full parity (estimate, 2026-10-04)
|
||||
|
||||
**Breadth: ~99% weighted** (P0 core 99%, P1 99%, P2 96%) over the 275 features of the InDesign catalogue, scored
|
||||
**Breadth: ~99% weighted** (P0 core 99%, P1 100%, P2 97%) over the 275 features of the InDesign catalogue, scored
|
||||
row by row in [docs/parity.md](docs/parity.md) (`cargo xtask parity` recomputes it). Many features scored done still
|
||||
lack some of InDesign's options or dialog details, so **overall parity including depth is about 82%**.
|
||||
lack some of InDesign's options or dialog details, so **overall parity including depth is about 83%**.
|
||||
|
||||
**Remaining work: about 185 wall-clock hours of a single Claude Opus 5.5 agent** (±30%), or roughly 60–85 hours with
|
||||
**Remaining work: about 170 wall-clock hours of a single Claude Opus 5.5 agent** (±30%), or roughly 60–85 hours with
|
||||
four agents in parallel on separate crates:
|
||||
|
||||
| Work | Estimate |
|
||||
|---|---|
|
||||
| Open P0 (2: PDF/X-4 validation against a certified checker, PostScript interpretation for EPS) | 8 h |
|
||||
| Open P1 (1 partial: on-screen blending in the blend space) | 4 h |
|
||||
| Open P2 (16 partial: vertical/RTL refinements, alternate-layout linking, separations from source colours, buttons as form fields, interactive PDF extras, scripting, localization breadth, …) | 45 h |
|
||||
| Open P1 (none partial) | 0 h |
|
||||
| Open P2 (4 partial: localization breadth, INDD pages (not readable clean-room), in-app media playback, vertical metrics and warichu) | 14 h |
|
||||
| Depth and pixel fidelity of every dialog, panel and menu against InDesign 2026 | 90 h |
|
||||
| Performance (incremental composition, GPU raster) and interchange hardening (IDML/PDF corpus) | 55 h |
|
||||
|
||||
|
||||
@@ -1100,6 +1100,16 @@ impl Item {
|
||||
self.shape == Shape::Group && matches!(self.content, Content::Group { .. })
|
||||
}
|
||||
|
||||
/// Does this object (or anything in it) involve transparency (opacity, a blend mode, effects,
|
||||
/// knockout)?
|
||||
pub fn involves_transparency(&self) -> bool {
|
||||
self.opacity < 1.0
|
||||
|| self.blend != Default::default()
|
||||
|| self.effects.any()
|
||||
|| self.knockout
|
||||
|| self.children().iter().any(|c| c.involves_transparency())
|
||||
}
|
||||
|
||||
/// A frame with items pasted into it (Edit › Paste Into): drawn clipped to its path.
|
||||
pub fn has_nested_items(&self) -> bool {
|
||||
self.shape != Shape::Group && matches!(&self.content, Content::Group { items } if !items.is_empty())
|
||||
|
||||
@@ -226,7 +226,7 @@ pub(crate) fn options(p: &Value, page_count: usize) -> Result<PdfOptions> {
|
||||
|
||||
/// Does this object (or anything in it) need the transparency flattener?
|
||||
fn transparent(it: &designcraft_doc::Item) -> bool {
|
||||
it.opacity < 1.0 || it.blend != Default::default() || it.effects.any() || it.knockout || it.children().iter().any(|c| transparent(c))
|
||||
it.involves_transparency()
|
||||
}
|
||||
|
||||
/// Transparency Flattener: each object involving transparency becomes an opaque image of its
|
||||
|
||||
@@ -130,6 +130,9 @@ pub struct RenderOptions {
|
||||
pub rich_black: bool,
|
||||
/// View › Overprint Preview: overprinting inks mix with what's beneath.
|
||||
pub overprint_preview: bool,
|
||||
/// Screen: spreads with transparency are shown as blended in the document's blend space (a
|
||||
/// CMYK blend space keeps their colours in the working CMYK gamut).
|
||||
pub blend_space_view: bool,
|
||||
}
|
||||
|
||||
/// View › Display Performance: how placed graphics and effects are drawn on screen.
|
||||
@@ -166,6 +169,7 @@ impl Default for RenderOptions {
|
||||
quality: DisplayQuality::High,
|
||||
rich_black: false,
|
||||
overprint_preview: false,
|
||||
blend_space_view: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -303,6 +307,17 @@ impl Renderer {
|
||||
// Render straight into the returned buffer (no extra copy of the frame).
|
||||
let mut pixels = vec![0u8; w as usize * h as usize * 4];
|
||||
ctx.render(vello_cpu::PixmapMut::new(w, h, &mut pixels).expect("buffer size"), &mut self.resources);
|
||||
if opts.blend_space_view && doc.settings.blend_space == designcraft_doc::BlendSpace::Cmyk && opts.plate.is_none() {
|
||||
let mut img = Rendered { width: w as u32, height: h as u32, pixels };
|
||||
for pl in spreads {
|
||||
let Some(sp) = doc.spread(pl.spread) else { continue };
|
||||
if sp.items.iter().any(|it| it.involves_transparency() && !it.hidden) {
|
||||
let r = (view * pl.xf).transform_rect_bbox(sp.bounds());
|
||||
blend_space_view(&mut img, r);
|
||||
}
|
||||
}
|
||||
pixels = img.pixels;
|
||||
}
|
||||
self.ctx = Some(ctx);
|
||||
self.stories.clear();
|
||||
self.stats.micros = now().saturating_sub(start);
|
||||
@@ -1066,6 +1081,31 @@ pub fn proof_view(img: &mut Rendered, setup: &designcraft_color::cms::ProofSetup
|
||||
}
|
||||
}
|
||||
|
||||
/// A CMYK transparency blend space on screen: the pixels of `area` (device space) through the
|
||||
/// working CMYK profile and back, as a spread composited in that space looks.
|
||||
fn blend_space_view(img: &mut Rendered, area: Rect) {
|
||||
use designcraft_color::cms::{ProofSetup, ProofTarget};
|
||||
let lut = designcraft_color::cms::active().proof_lut(&ProofSetup { target: ProofTarget::WorkingCmyk, ..Default::default() });
|
||||
let (w, h) = (img.width as i64, img.height as i64);
|
||||
let (x0, y0) = ((area.x0.floor() as i64).clamp(0, w), (area.y0.floor() as i64).clamp(0, h));
|
||||
let (x1, y1) = ((area.x1.ceil() as i64).clamp(0, w), (area.y1.ceil() as i64).clamp(0, h));
|
||||
for y in y0..y1 {
|
||||
let row = &mut img.pixels[((y * w + x0) * 4) as usize..((y * w + x1) * 4) as usize];
|
||||
for px in row.chunks_exact_mut(4) {
|
||||
let a = px[3];
|
||||
if a == 0 {
|
||||
continue;
|
||||
}
|
||||
let k = 255.0 / a as f32;
|
||||
let rgb = [px[0], px[1], px[2]].map(|v| ((v as f32 * k).round()).min(255.0) as u8);
|
||||
let out = lut.apply8(rgb);
|
||||
for i in 0..3 {
|
||||
px[i] = ((out[i] as u32 * a as u32 + 127) / 255) as u8;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn separation_view(img: &mut Rendered, plate: Option<u8>, ink_limit: Option<f32>) {
|
||||
for px in img.pixels.chunks_exact_mut(4) {
|
||||
let a = px[3] as f32 / 255.0;
|
||||
@@ -1207,6 +1247,33 @@ mod tests {
|
||||
assert!(on[2] < 60 && on[0] > 180, "magenta over yellow → red: {on:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cmyk_blend_space_shows_transparent_spreads_in_cmyk() {
|
||||
let mut d = Document::new(&NewDocument::default());
|
||||
d.swatches.push(designcraft_color::swatch::Swatch::color("Green", designcraft_color::Color::rgb8(0, 255, 0)));
|
||||
let lid = d.default_layer();
|
||||
let id = designcraft_doc::ItemId(d.alloc());
|
||||
let mut it =
|
||||
Item::new(id, lid, designcraft_doc::Shape::Rectangle, designcraft_geom::shapes::rectangle(Rect::new(100.0, 100.0, 200.0, 200.0)));
|
||||
it.fill = Fill::swatch("Green");
|
||||
d.insert_item(SpreadRef::Doc(0), it, None).unwrap();
|
||||
let cache = Cache::new();
|
||||
let mut r = Renderer::new();
|
||||
r.threads = 0;
|
||||
let px = |d: &Document, r: &mut Renderer| {
|
||||
r.render_page(d, &cache, 0, 1.0, false, &RenderOptions { blend_space_view: true, ..Default::default() }).unwrap().pixel(150, 150)
|
||||
};
|
||||
// No transparency on the spread: shown as is.
|
||||
assert_eq!(px(&d, &mut r)[1], 255);
|
||||
// With transparency in a CMYK blend space the bright green leaves the RGB gamut's edge.
|
||||
d.item_mut(id).unwrap().opacity = 0.99;
|
||||
let cmyk = px(&d, &mut r);
|
||||
assert!(cmyk[0] > 20 || cmyk[1] < 240, "{cmyk:?}");
|
||||
d.settings.blend_space = designcraft_doc::BlendSpace::Rgb;
|
||||
let rgb = px(&d, &mut r);
|
||||
assert!(rgb[1] > 245 && rgb[0] < 10, "{rgb:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn knockout_group_hides_its_own_objects() {
|
||||
let mut d = Document::new(&NewDocument::default());
|
||||
|
||||
@@ -421,6 +421,7 @@ fn render_texture(app: &mut DesignApp, ctx: &egui::Context, rect: Rect, xf: &Xf,
|
||||
quality: app.ui.display_quality,
|
||||
rich_black: app.ui.rich_black,
|
||||
overprint_preview: app.ui.overprint_preview,
|
||||
blend_space_view: !app.ui.proof_colors,
|
||||
..Default::default()
|
||||
};
|
||||
(st.doc.clone(), placed, w, h, view, opts)
|
||||
@@ -533,6 +534,7 @@ fn patch_texture(app: &mut DesignApp, layout: &CanvasLayout, ppp: f64, doc_key:
|
||||
quality: app.ui.display_quality,
|
||||
rich_black: app.ui.rich_black,
|
||||
overprint_preview: app.ui.overprint_preview,
|
||||
blend_space_view: !app.ui.proof_colors,
|
||||
..Default::default()
|
||||
};
|
||||
let img = app.canvas.patcher.render(&new, &app.session.cache, &placed, (x1 - x0) as u32, (y1 - y0) as u32, view, &opts);
|
||||
|
||||
+3
-3
@@ -10,7 +10,7 @@ Each row of the InDesign feature catalogue is scored **D** (done), **P** (partia
|
||||
| Priority | Features | Done | Partial | Missing | Parity |
|
||||
|---|---|---|---|---|---|
|
||||
| P0 | 102 | 100 | 2 | 0 | 99% |
|
||||
| P1 | 97 | 96 | 1 | 0 | 99% |
|
||||
| P1 | 97 | 97 | 0 | 0 | 100% |
|
||||
| P2 | 76 | 72 | 4 | 0 | 97% |
|
||||
|
||||
| Area | Parity |
|
||||
@@ -21,7 +21,7 @@ Each row of the InDesign feature catalogue is scored **D** (done), **P** (partia
|
||||
| Frames, shapes & paths | 100% |
|
||||
| Transform | 100% |
|
||||
| Fill, stroke, colour | 100% |
|
||||
| Effects & transparency | 92% |
|
||||
| Effects & transparency | 100% |
|
||||
| Placing & links | 93% |
|
||||
| Type & text frames | 99% |
|
||||
| Typography | 100% |
|
||||
@@ -164,7 +164,7 @@ Open P0 items: Vector/page formats (PDF/AI/SVG/EPS place), Export to PDF (print)
|
||||
| Effects & transparency | Inner Shadow, Outer Glow, Inner Glow, Bevel, Satin | P2 | D |
|
||||
| Effects & transparency | Basic/Directional/Gradient Feather | P2 | D |
|
||||
| Effects & transparency | Global Light | P2 | D |
|
||||
| Effects & transparency | Transparency blend space | P1 | P |
|
||||
| Effects & transparency | Transparency blend space | P1 | D |
|
||||
| Effects & transparency | Transparency flattener | P2 | D |
|
||||
| Placing & links | Place | P0 | D |
|
||||
| Placing & links | Place into frame / by click or drag | P0 | D |
|
||||
|
||||
Reference in new issue
Block a user