From 8f6863f536e69039747ba04f042ff92b9c031bef Mon Sep 17 00:00:00 2001 From: David Mann Date: Sun, 25 Oct 2026 16:14:33 +0100 Subject: [PATCH] Integration: a kept 1053 doesn't hide an earlier PhotoCraft's spot ink (#8885) Earlier PhotoCraft versions kept the 1053 of a Photoshop file they opened and wrote it back unchanged, while storing spot channels added there straight. A transparent Photoshop document carries 1053 = [0], so a white plate added to it in 0.16.0 or earlier sat behind a 1053, and the rule read that file in Photoshop's polarity: the added spot opened inverted. A 1053 that lists fewer channels (non-zero ids; 0 is the transparency's) than the file has after its transparency now counts as kept, with the PhotoCraft 1057 and DisplayInfo spot conditions unchanged. Planes the kept 1053 lists stay in Photoshop's polarity (they are the Photoshop file's own); the ones after them are read straight. Photoshop and this version write one id per extra channel, so their files are unaffected. Known limit, as before: channels reordered there, or as many deleted as added, can't be told apart from the Photoshop file's own. The earlier versions are 0.1.0 to 0.16.0, not 0.11.1: every release since writes the same 1057 and straight spot planes without a 1053. Co-authored-by: jasallen <615848+jasallen@users.noreply.github.com> Co-Authored-By: Claude Opus 5.5 --- crates/io/src/psd_import.rs | 129 ++++++++++++++++++++++++++++++++---- docs/file-format-parity.md | 4 +- docs/gaps.md | 4 +- docs/scorecard.md | 2 +- scorecard/file_compat.toml | 2 +- 5 files changed, 122 insertions(+), 19 deletions(-) diff --git a/crates/io/src/psd_import.rs b/crates/io/src/psd_import.rs index 8f2bcf9c79..090f6e1aba 100644 --- a/crates/io/src/psd_import.rs +++ b/crates/io/src/psd_import.rs @@ -876,14 +876,18 @@ pub fn psd_to_document_with(file: &PsdFile, ctl: &photocraft_raster::Interrupt) psd_to_document_decoded(file, ctl, None) } -/// Whether `file` stores its spot planes the way earlier PhotoCraft versions (releases 0.1.0 to -/// 0.11.1) wrote them: straight (the maximum = solid ink), the opposite of Photoshop, which +/// Whether `file` stores spot planes the way earlier PhotoCraft versions (releases 0.1.0 to +/// 0.16.0) wrote them: straight (the maximum = solid ink), the opposite of Photoshop, which /// stores 0 = solid ink in every PSD and TIFF it saves. Their files are recognised by three /// things together: /// /// - DisplayInfo (1077) marks at least one spot channel; -/// - there is no Alpha Identifiers resource (1053). Photoshop has written it in every file with -/// an extra channel since version 6, and PhotoCraft writes it now, but no earlier PhotoCraft did; +/// - there is no Alpha Identifiers resource (1053), or one that lists fewer channels than the +/// file has ([`photoshop_identified_channels`]). Photoshop has written it, one identifier per +/// extra channel, in every file with an extra channel since version 6, and PhotoCraft writes it +/// now. No earlier PhotoCraft wrote one of its own, but each kept the 1053 of a Photoshop file +/// it opened (a transparent document's `[0]`, for one) and wrote it back unchanged, after the +/// channels added in it, which it stored straight; /// - the version info (1057) is byte for byte what those versions wrote: writer "Adobe Photoshop" /// with reader "Adobe Photoshop CS6", both NUL-terminated (0.1.0 to 0.7.0), or reader "Adobe /// Photoshop 2026" without a NUL (0.8.2 on), with either merged-data flag. @@ -891,7 +895,8 @@ pub fn psd_to_document_with(file: &PsdFile, ctl: &photocraft_raster::Interrupt) /// Any other file is read in Photoshop's polarity. psd-tools' `cmyk-spot.psd`, for one, also has /// spots and no 1053, but names psd-tools in 1057 and stores Photoshop's polarity. A Photoshop /// file that an earlier PhotoCraft opened and saved again kept Photoshop's 1053 and its planes as -/// Photoshop stored them, so it is read in Photoshop's polarity too. +/// Photoshop stored them, so it is read in Photoshop's polarity too; when channels were added +/// there, only the planes after the ones the kept 1053 lists are read straight. /// /// Keeping these files' ink is the only reason this exists: deleting it (and its one caller) /// reads every file in Photoshop's polarity, at the cost of inverting the spot ink of PSDs saved @@ -901,7 +906,15 @@ pub(crate) fn earlier_photocraft_spot_planes(file: &PsdFile) -> bool { use photocraft_psd::resources::VersionInfo; let marks_spot = file.resource(crate::channel_map::DISPLAY_INFO).is_some_and(|r| crate::channel_map::display_info_marks_spot(&r.data)); - if !marks_spot || file.resource(crate::channel_map::ALPHA_IDENTIFIERS).is_some() { + let identifies_every_channel = match file.header.color_mode.color_channels() { + Some(color) => { + let channels = file.header.channels.saturating_sub(color).saturating_sub(u16::from(file.merged_has_alpha())); + photoshop_identified_channels(file) >= usize::from(channels) + } + // Multichannel has no fixed number of colour channels to count from. + None => file.resource(crate::channel_map::ALPHA_IDENTIFIERS).is_some(), + }; + if !marks_spot || identifies_every_channel { return false; } let Some(r) = file.resource(ids::VERSION_INFO) else { return false }; @@ -917,6 +930,15 @@ pub(crate) fn earlier_photocraft_spot_planes(file: &PsdFile) -> bool { v.version == 1 && v.file_version == 1 && writers.iter().any(|(writer, reader)| v.writer == *writer && v.reader == *reader) } +/// How many extra channels, after the merged transparency, the Alpha Identifiers resource (1053) +/// lists: its identifiers other than 0, which Photoshop gives the merged transparency (first, and +/// in some files with no transparency plane at all). 0 without a 1053. In a file from an earlier +/// PhotoCraft ([`earlier_photocraft_spot_planes`]) these are the channels of the Photoshop file +/// it opened, stored as Photoshop stored them; the channels after them were added there. +pub(crate) fn photoshop_identified_channels(file: &PsdFile) -> usize { + file.resource(crate::channel_map::ALPHA_IDENTIFIERS).map_or(0, |r| r.data.as_chunks::<4>().0.iter().filter(|id| **id != [0; 4]).count()) +} + /// Whether the import reads the merged composite: for a flattened or multichannel file (it *is* /// the image), a file without layers, or extra alpha/spot channels behind the colour ones. pub(crate) fn merged_needed(file: &PsdFile) -> bool { @@ -1244,10 +1266,10 @@ pub(crate) fn psd_to_document_decoded_integrity( let names = names.get(skip..).unwrap_or_default(); // Which planes hold spot ink in Photoshop's polarity (0 = solid): the DisplayInfo records // the channel options are read from below, the transparency's first when `skip` is 1. - // A file from an earlier PhotoCraft stores them straight and is read as it was written. - let photoshop_spots = if earlier_photocraft_spot_planes(file) { - Vec::new() - } else if let Some(r) = file.resource(crate::channel_map::DISPLAY_INFO) { + // A file from an earlier PhotoCraft stores them straight and is read as it was written, + // past the channels of the Photoshop file it opened, if any. + let straight_from = if earlier_photocraft_spot_planes(file) { photoshop_identified_channels(file) } else { usize::MAX }; + let photoshop_spots = if let Some(r) = file.resource(crate::channel_map::DISPLAY_INFO) { crate::channel_map::spot_flags(&r.data, true, skip) } else if let Some(r) = file.resource(crate::channel_map::DISPLAY_INFO_OLD) { crate::channel_map::spot_flags(&r.data, false, skip) @@ -1260,7 +1282,7 @@ pub(crate) fn psd_to_document_decoded_integrity( break; }; let mut raw = be_to_ne_plane(p, depth, n); - if photoshop_spots.get(k).copied().unwrap_or(false) { + if k < straight_from && photoshop_spots.get(k).copied().unwrap_or(false) { crate::channel_map::photoshop_spot_plane(&mut raw, depth); } let mut s = Surface::new(cx.mask_fmt); @@ -1454,7 +1476,7 @@ mod tests { doc.channels.iter().map(|c| c.spot.is_some()).collect() } - /// `doc` (opaque, 8 or 16-bit) saved the way PhotoCraft 0.1.0 to 0.11.1 saved it: spot planes + /// `doc` (opaque, 8 or 16-bit) saved the way PhotoCraft 0.1.0 to 0.16.0 saved it: spot planes /// straight (the maximum = solid ink), no 1053, and `version_info` as resource 1057. `padded` /// lays DisplayInfo out in the 14-byte records of 0.1.0 to 0.7.0. fn as_earlier_photocraft_saved(doc: &Document, version_info: Vec, padded: bool) -> Vec { @@ -1483,7 +1505,7 @@ mod tests { file.to_bytes().unwrap() } - /// PSDs that PhotoCraft 0.1.0 to 0.11.1 saved with spot channels store them straight. They + /// PSDs that PhotoCraft 0.1.0 to 0.16.0 saved with spot channels store them straight. They /// open with the ink they had in those versions (which read every plane straight), whichever /// 1057 they carry. Saved again, they get Photoshop's polarity and a 1053, and reopen with the /// same ink. (A 0.1.0 to 0.7.0 file's padded records misread every channel's options after @@ -1593,4 +1615,85 @@ mod tests { let alpha_only = PsdFile::from_bytes(&as_earlier_photocraft_saved(&doc, version_info_2026(true), false)).unwrap(); assert!(!earlier_photocraft_spot_planes(&alpha_only), "alpha channels only"); } + + /// `doc` as an earlier PhotoCraft saved a Photoshop file it had opened with the first `kept` + /// of `doc`'s channels: it wrote back the Photoshop file's 1053, `stale_ids`, unchanged and + /// those channels' planes as Photoshop stored them (it had read and written them straight), + /// and stored the channels added after them straight. + fn as_earlier_photocraft_resaved(doc: &Document, stale_ids: &[u32], kept: usize) -> Vec { + let mut file = crate::psd_export::document_to_psd(doc); + file.resources.retain(|r| r.id != crate::channel_map::ALPHA_IDENTIFIERS && r.id != ids::VERSION_INFO); + file.resources.push(ImageResource::new(crate::channel_map::ALPHA_IDENTIFIERS, stale_ids.iter().flat_map(|id| id.to_be_bytes()).collect())); + file.resources.push(ImageResource::new(ids::VERSION_INFO, version_info_2026(true))); + let mut planes = file.decode_merged().unwrap(); + let plane = planes.len() / usize::from(file.header.channels); + let first = usize::from(file.header.channels) - doc.channels.len(); + for (k, c) in doc.channels.iter().enumerate().skip(kept) { + if c.spot.is_some() { + planes[(first + k) * plane..(first + k + 1) * plane].iter_mut().for_each(|b| *b = !*b); + } + } + file.image_data = ImageData::encode(Compression::Raw, &planes, &file.header).unwrap(); + file.to_bytes().unwrap() + } + + /// An earlier PhotoCraft kept the 1053 of a Photoshop file it opened and wrote it back + /// unchanged, so a spot channel added there sits behind a 1053 that doesn't list it: a + /// transparent Photoshop document's `[0]` (the white plate of a DTF print, added before + /// saving), the `[0, 3]` of one with a spot channel already, or a `[0]` kept through a + /// flatten that removed the transparency. The added spot is stored straight and keeps its + /// ink; the Photoshop file's own spot keeps Photoshop's polarity. Saved again, both reopen + /// the same. + #[test] + fn a_spot_added_behind_a_kept_alpha_identifiers_resource_keeps_its_ink() { + let transparent = |names: &[&str]| { + let mut doc = channels_document(SampleType::U8); + doc.layers = vec![Layer::raster("Layer 1", doc.pixel_format())]; + doc.channels.retain(|c| names.contains(&c.name.as_str())); + doc + }; + let mut flattened = channels_document(SampleType::U8); + flattened.channels.retain(|c| c.name == "Spot A"); + let cases = [ + ("transparent, one spot added", transparent(&["Spot A"]), vec![0], 0), + ("transparent, a Photoshop spot and one added", transparent(&["Spot A", "Spot B"]), vec![0, 3], 1), + ("flattened, one spot added", flattened, vec![0], 0), + ]; + for (what, doc, stale_ids, kept) in cases { + let bytes = as_earlier_photocraft_resaved(&doc, &stale_ids, kept); + let file = PsdFile::from_bytes(&bytes).unwrap(); + assert_eq!(file.merged_has_alpha(), what.starts_with("transparent"), "{what}"); + assert!(earlier_photocraft_spot_planes(&file), "{what}"); + let opened = crate::import("old.psd", &bytes).unwrap().document; + assert_eq!(ink(&opened), ink(&doc), "{what}: the ink earlier versions opened"); + assert_eq!(spots(&opened), spots(&doc), "{what}"); + let saved = crate::export(&opened, "old.psd", &crate::ExportOptions::default()).unwrap(); + let again = crate::import("again.psd", &saved.bytes).unwrap().document; + assert_eq!(ink(&again), ink(&doc), "{what}: saved again"); + } + } + + /// A 1053 that lists every extra channel, as Photoshop and this version write it, keeps the + /// file in Photoshop's polarity, with PhotoCraft's (Photoshop 2026's) 1057 too: a transparent + /// file with a spot, and Photoshop's own `[0]` in a file without a transparency plane. + #[test] + fn a_complete_alpha_identifiers_resource_keeps_photoshop_polarity() { + let mut doc = channels_document(SampleType::U8); + doc.layers = vec![Layer::raster("Layer 1", doc.pixel_format())]; + let bytes = as_earlier_photocraft_resaved(&doc, &[0, 3, 4, 5], 3); + let file = PsdFile::from_bytes(&bytes).unwrap(); + assert!(file.merged_has_alpha()); + assert!(!earlier_photocraft_spot_planes(&file)); + assert_eq!(ink(&crate::import("ps.psd", &bytes).unwrap().document), ink(&doc)); + let mut opaque = channels_document(SampleType::U8); + opaque.channels.clear(); + let mut file = PsdFile::from_bytes(&as_earlier_photocraft_resaved(&opaque, &[0], 0)).unwrap(); + file.resources.retain(|r| r.id != crate::channel_map::DISPLAY_INFO); + file.resources.push(ImageResource::new( + crate::channel_map::DISPLAY_INFO, + crate::channel_map::display_info(&channels_document(SampleType::U8).channels.iter().collect::>(), false), + )); + assert_eq!(photoshop_identified_channels(&file), 0, "0 is the transparency's"); + assert!(!earlier_photocraft_spot_planes(&file), "no channel left unlisted"); + } } diff --git a/docs/file-format-parity.md b/docs/file-format-parity.md index b6c3ee89fd..682b3881da 100644 --- a/docs/file-format-parity.md +++ b/docs/file-format-parity.md @@ -21,7 +21,7 @@ XL are missing (gap G10). Remaining: 300–500 h including PSD fidelity. | Format | Photoshop | PhotoCraft read | PhotoCraft write | Fidelity and tests | |---|---|---|---|---| -| PSD | R/W | yes | yes | 1/8/16/32-bit; Bitmap, Gray, Indexed, RGB, CMYK, Multichannel, Lab, Duotone; layers, groups, masks, vector masks, adjustment and fill layers, effects, text, shapes, smart objects (SoLd/lnk2 with their warp: the bend presets, a custom mesh and, since #4112, Cylinder; CS2 PlLd/lnkD read with their warp, kept as is or re-saved as SoLd once edited), smart filters (53 mapped, among them Invert, the Distort filters but Shear, Clouds, Difference Clouds, Fibers and Lens Flare, plus Hue/Saturation, Perspective Warp, Filter Gallery with Chrome and a Camera Raw subset, rest verbatim), slices, comps, Advanced Blending, each layer's XMP packet (`layer.xmpMetadata`); alpha and spot channels as Photoshop stores them (spot ink 0 = solid, channel identifiers in 1053), and PSDs with spot channels from PhotoCraft 0.1.0–0.11.1, which stored the ink the other way round, keep their ink. Oracle floors: Photoshop-authored 190/258, io 148/170, psd-tools 249/309; round trip ≥ 169/170, 308/309, 258/256. Known failures: #1281, #2469 (#2374, text layers, closed 2026-10-10; #2465, CS2 `PlLd`-only smart objects, fixed 2026-10-12) | +| PSD | R/W | yes | yes | 1/8/16/32-bit; Bitmap, Gray, Indexed, RGB, CMYK, Multichannel, Lab, Duotone; layers, groups, masks, vector masks, adjustment and fill layers, effects, text, shapes, smart objects (SoLd/lnk2 with their warp: the bend presets, a custom mesh and, since #4112, Cylinder; CS2 PlLd/lnkD read with their warp, kept as is or re-saved as SoLd once edited), smart filters (53 mapped, among them Invert, the Distort filters but Shear, Clouds, Difference Clouds, Fibers and Lens Flare, plus Hue/Saturation, Perspective Warp, Filter Gallery with Chrome and a Camera Raw subset, rest verbatim), slices, comps, Advanced Blending, each layer's XMP packet (`layer.xmpMetadata`); alpha and spot channels as Photoshop stores them (spot ink 0 = solid, channel identifiers in 1053), and PSDs with spot channels from PhotoCraft 0.1.0–0.16.0, which stored the ink the other way round, keep their ink. Oracle floors: Photoshop-authored 190/258, io 148/170, psd-tools 249/309; round trip ≥ 169/170, 308/309, 258/256. Known failures: #1281, #2469 (#2374, text layers, closed 2026-10-10; #2465, CS2 `PlLd`-only smart objects, fixed 2026-10-12) | | PSB | R/W | yes | yes | Auto-switches above 2 GB (REL-212-2); generated 20 MP, 150-layer, 2 GB PSD and 4.4 GB PSB fixtures opened, saved and re-opened by io tests/it/large_files.rs (FILE-216-2, 2026-10-13; the 4.4 GB PSB opens on Windows); > 4 GB from the app's File › Open on Windows: #375 | | Cloud PSD (.psdc) | R/W | no | no | Out of scope (Adobe cloud) | | TIFF | R/W | yes | yes | 8/16/32f, Gray/RGB/CMYK, BigTIFF, every IFD, banded parallel decode; layered TIFF both byte orders, including Photoshop mixed-order type blocks (#4758); LZW/ZIP for scripted saves by `tiffCompression`; undecodable type keeps saved pixels (FILE-215-7); alpha and spot channels as extra samples (at most 16) with Photoshop's names, ink colours and solidity, spot ink stored 0 = solid (#7415); no Save Transparency choice yet (channels are written as Photoshop does with it off) | @@ -142,7 +142,7 @@ remaining gaps in #1606 stay tracked with per-sample corpus ceilings. | Date | Change | Summary | |---|---|---| -| 2026-10-23 | minor | PSD: spot channels are stored and read as Photoshop 27.11's PSDs store them (ink 0 = solid; all 7 of its spot PSDs store the 100/25/75/0 % columns as 0/191/64/255), and resource 1053 numbers the extra channels 3, 4, 5, … after 0 for the merged transparency. A PSD with spot channels and no 1053 whose 1057 is exactly what PhotoCraft 0.1.0–0.11.1 wrote is read as those versions stored it (straight), so it keeps its ink. Multichannel is unchanged (#7415 split, part 4) | +| 2026-10-23 | minor | PSD: spot channels are stored and read as Photoshop 27.11's PSDs store them (ink 0 = solid; all 7 of its spot PSDs store the 100/25/75/0 % columns as 0/191/64/255), and resource 1053 numbers the extra channels 3, 4, 5, … after 0 for the merged transparency. A PSD with spot channels whose 1057 is exactly what PhotoCraft 0.1.0–0.16.0 wrote, and with no 1053 or one kept from an opened Photoshop file that doesn't list every channel, is read as those versions stored it (straight past the channels that 1053 lists), so it keeps its ink. Multichannel is unchanged (#7415 split, part 4) | | 2026-10-23 | minor | TIFF: alpha and spot channels are written and read as extra samples (at most 16) with Photoshop's channel resources (1006, 1045, 1077, 1053), spot ink stored 0 = solid as Photoshop 27.11's TIFFs store it; PSD output is unchanged. FILE-215-7 done, FILE-215-17 partial (its TIFF part) (#7415, supersedes #3121) | | 2026-10-23 | patch | PSD and layered TIFF: the `MTrn` block Photoshop writes in a layered TIFF saved with Save Transparency (a positive layer count, `nrTM` in an `II` file) marks the merged image's first extra channel as its transparency, like `Mtrn`/`Mt16`/`Mt32`: such a file no longer opens with its transparency also as an alpha channel (named "Transparency" in Photoshop 27.11's file), and a PSD save no longer writes it twice. A PSD save with an opaque composite drops any of these markers kept from the opened file, so its first alpha channel no longer reopens as transparency and vanishes (split from #7415; credit #3121) | | 2026-10-23 | minor | Amiga IFF write: Save As `.iff` stores a 24-plane ILBM (ByteRun1, alpha as a 1-bit mask plane), and 24-plane ILBMs open | diff --git a/docs/gaps.md b/docs/gaps.md index f80e40abe5..b6c859017f 100644 --- a/docs/gaps.md +++ b/docs/gaps.md @@ -310,7 +310,7 @@ what an agent can't finish alone. .shc, .csh presets (.asl landed 2026-10-20) (FILE-215-15); C2PA (#1050). - **Detail:** [`file-format-parity.md`](file-format-parity.md). - **Progress (2026-10-23, #7415 split):** PSD stores and reads spot channels as Photoshop does (ink - 0 = solid) and writes their identifiers (1053); PSDs from PhotoCraft 0.1.0–0.11.1, which stored + 0 = solid) and writes their identifiers (1053); PSDs from PhotoCraft 0.1.0–0.16.0, which stored spot ink the other way round, keep their ink. - **Progress (2026-10-23, #7415):** TIFF writes and reads alpha and spot channels as extra samples with Photoshop's names, ink colours and solidity (FILE-215-7 done; FILE-215-17's TIFF part). @@ -364,7 +364,7 @@ what an agent can't finish alone. | Date | Change | Summary | |---|---|---| -| 2026-10-23 | minor | G10: PSD stores and reads spot channels as Photoshop does (ink 0 = solid) with their identifiers (1053); PSDs from PhotoCraft 0.1.0–0.11.1 keep their ink (#7415 split, part 4); FILE-215-17 stays partial; estimate unchanged | +| 2026-10-23 | minor | G10: PSD stores and reads spot channels as Photoshop does (ink 0 = solid) with their identifiers (1053); PSDs from PhotoCraft 0.1.0–0.16.0 keep their ink (#7415 split, part 4); FILE-215-17 stays partial; estimate unchanged | | 2026-10-23 | minor | G10: TIFF alpha and spot channels written and read as extra samples with Photoshop's channel resources, PSD output unchanged (#7415); FILE-215-7 leaves the list, FILE-215-17 narrows to RGB/CMYK spot preview and PDF Separation | | 2026-10-23 | minor | G10: Amiga IFF write landed (24-plane ILBM, Save As `.iff`) and 24-plane ILBMs open | | 2026-10-22 | minor | G17: Help › Check for Updates and an opt-out check once per launch (#5485), DIST-220-1 partial; estimate unchanged | diff --git a/docs/scorecard.md b/docs/scorecard.md index 89a964116a..8fe2a9f751 100644 --- a/docs/scorecard.md +++ b/docs/scorecard.md @@ -122,7 +122,7 @@ Corpus floors (`crates/io/tests/it/corpus.rs`): the enforced minimums; the corpu | FILE-215-14 | User swatch model with ACO/ASE import and export | done | [#215](https://github.com/storytold/photocraft/issues/215) | engine presets/swatches.rs (RGB/HSB/CMYK/Lab/Gray + verbatim colour books, persisted in prefs) + swatch_cmds.rs (swatches.*); psd aco.rs (v1/v2) and ase.rs (groups, spot/global) read+write; ui swatches_ui.rs, Color Picker Add to Swatches | | FILE-215-15 | Layer Style contour picker, curve editor, presets, .shc import/export | partial | [#2912](https://github.com/storytold/photocraft/issues/2912) | Layer Style dialog: contour thumbnail + preset picker (12 standard shapes, own control points) and Anti-aliased on Drop/Inner Shadow, Outer/Inner Glow, Satin, Bevel Gloss Contour and Contour element; Contour Editor (add/drag/delete, Corner, Input/Output, Preset, Load…/Save… .shc per Adobe's public spec, psd shc.rs); corner points render (compose contour_curve_lut) and map to PSD Cnty. User contour library: Contour Editor New… saves the curve under a name (layer.layerStyle.contours.new/list/delete/load/save, kept with the preferences), the picker lists user contours after the standard ones and has Load/Replace/Save Contours… (.shc). Missing: Anti-aliased doesn't change the render yet, .shc not yet checked against a Photoshop-written file | | FILE-215-16 | Advanced Blending (knockout, blend as group, mask hides effects) end to end | partial | [#215](https://github.com/storytold/photocraft/issues/215) | doc advanced.rs + .pcraft + PSD/layered TIFF (knko infx clbl tsly lmgm vmgm), CPU compose advanced.rs (GPU falls back), blendingOptions command + dialog; 7 psd-tools knockout oracles now pass; knockout-isolated-groups (0.45 % AA edges), advanced-blending, blend-and-clipping still differ | -| FILE-215-17 | CMYK/RGB documents preview spot inks; spot plates export (TIFF extra samples, PDF) | partial | [#215](https://github.com/storytold/photocraft/issues/215) | TIFF: spot channels written and read as extra samples with DisplayInfo (1077) ink colour and solidity, ink stored 0 = solid (#7415). PSD: spot planes stored and read 0 = solid as Photoshop does, identifiers in 1053; PhotoCraft 0.1.0–0.11.1 PSDs (straight planes) keep their ink. Missing: spot ink preview in RGB/CMYK documents (compose multichannel.rs:1 previews Multichannel only) and PDF Separation | +| FILE-215-17 | CMYK/RGB documents preview spot inks; spot plates export (TIFF extra samples, PDF) | partial | [#215](https://github.com/storytold/photocraft/issues/215) | TIFF: spot channels written and read as extra samples with DisplayInfo (1077) ink colour and solidity, ink stored 0 = solid (#7415). PSD: spot planes stored and read 0 = solid as Photoshop does, identifiers in 1053; PhotoCraft 0.1.0–0.16.0 PSDs (straight planes) keep their ink. Missing: spot ink preview in RGB/CMYK documents (compose multichannel.rs:1 previews Multichannel only) and PDF Separation | | FILE-215-18 | Save for Web per-slice settings remembered; Export As remembers and offers nearest scaling | done | [#215](https://github.com/storytold/photocraft/issues/215) | Save for Web settings survive a restart through the remembered dialog settings in prefs.dialogs (web_cmds::remember_web_settings stores them on a complete export, never the destination; file_ui.rs open_web seeds from the session's last export, else the preferences). export_dialog seeds Export As the same way (seed_remembered: format, quality, transparency, scale, resample, metadata, TGA bits) and confirm remembers them as used. Export As › Image Size › Resample offers Photoshop's six methods (Bicubic Automatic default, Nearest Neighbor included; export_preset_cmds::RESAMPLE, unknown values are errors) for the exported copy only; also layer.exportAs `resample` and export presets (#5838). Tests: export_dialog nearest_neighbor_doubles_each_pixel_into_an_exact_block (8/16-bit), resample_choices_are_checked, resample_is_remembered_and_a_bad_saved_value_falls_back; layer_menu_cmds export_layer_resamples_with_the_chosen_method; tests/it/export_as_scaling.rs export_as_resample_menu_writes_nearest_neighbor_blocks | | FILE-215-19 | Save As JPEG opens a JPEG Options dialog (quality 0-12, format options) | partial | [#215](https://github.com/storytold/photocraft/issues/215) | jpeg_options_ui.rs: File > Save, Save As and Save a Copy to a JPEG ask for Quality 0-12 (Low/Medium/High/Maximum) and Baseline / Baseline Optimized / Progressive before writing, starting at an opened JPEG's own quality (file_encoding) or else the Export preference; scripts pass quality/jpegFormat (file.saveAs, file.saveACopy, app.save, CLI --jpeg-format) and recorded actions replay the answer (discard_ui/tests/jpeg_tests.rs, codecs roundtrip jpeg_format_options_round_trip). Baseline Optimized fits the Huffman tables only at 4:4:4 (Gray, CMYK) or progressive: RGB is written 4:2:0, which zune-jpeg 0.5.15 decodes wrongly with fitted tables, as in Save for Web, so RGB Baseline Optimized writes the same file as Baseline. Missing: Matte, progressive Scans (3-5) and the size preview | | FILE-215-20 | Artboard tool with handles and + buttons, Duplicate Artboard beside, vector PDF pages | partial | [#215](https://github.com/storytold/photocraft/issues/215) | artboard_tool.rs: Artboard tool draws, selects, moves and resizes boards by their handles (⇧ keeps proportions), its + buttons add a board or (⌥) a copy beside the selected one (#3033); Duplicate Artboard places the copy beside (#1584); raster PDF pages only | diff --git a/scorecard/file_compat.toml b/scorecard/file_compat.toml index f3b5adbd17..977712b611 100644 --- a/scorecard/file_compat.toml +++ b/scorecard/file_compat.toml @@ -122,7 +122,7 @@ id = "FILE-215-17" target = "CMYK/RGB documents preview spot inks; spot plates export (TIFF extra samples, PDF)" status = "partial" issue = 215 -note = "TIFF: spot channels written and read as extra samples with DisplayInfo (1077) ink colour and solidity, ink stored 0 = solid (#7415). PSD: spot planes stored and read 0 = solid as Photoshop does, identifiers in 1053; PhotoCraft 0.1.0–0.11.1 PSDs (straight planes) keep their ink. Missing: spot ink preview in RGB/CMYK documents (compose multichannel.rs:1 previews Multichannel only) and PDF Separation" +note = "TIFF: spot channels written and read as extra samples with DisplayInfo (1077) ink colour and solidity, ink stored 0 = solid (#7415). PSD: spot planes stored and read 0 = solid as Photoshop does, identifiers in 1053; PhotoCraft 0.1.0–0.16.0 PSDs (straight planes) keep their ink. Missing: spot ink preview in RGB/CMYK documents (compose multichannel.rs:1 previews Multichannel only) and PDF Separation" [[item]] id = "FILE-215-18"