mirror of
https://github.com/storytold/photocraft.git
synced 2026-11-11 00:11:46 +00:00
* PSD: a flat file's named extra channels are not its transparency A PSD without layer records had its first extra channel read as the merged transparency, so the first spot or alpha channel of a flattened document vanished and its ink punched a transparent hole in the image (psd-tools cmyk-alpha-spot.psd, cmyk-spot.psd). When the alpha channel names (1045, or 1006 when 1045 is missing) name every extra channel and the first is not "Transparency", there is none. The import also reads channel names from 1006 when 1045 is missing, and a spot whose DisplayInfo colour space is unknown takes its ink from the Alternate Spot Colors (1067). * channel_map: read 1067 records with as_chunks * Preview spot inks over the RGB/CMYK composite as an overprint Visible spot channels of RGB and CMYK documents print over the canvas composite with the Multichannel ink formula, in Channels panel order, instead of a tint; alpha channels and Quick Mask still tint over them. Spot channels open visible, and the PSD thumbnail (1036) prints them. flatten, export and the merged image stay free of spot inks. Ink colours go through the document's CMYK profile, and into an RGB document's own profile. Refs #10249 * channel_view: test solidity over a gray picture * Thumbnail: matte only inked pixels; test the merged planes directly Review feedback on #10276: pixels without spot ink keep their transparency in the thumbnail buffer, and the thumbnail test checks the saved merged process planes instead of a re-imported flatten. * Thumbnail spot inks: sample big boxes on a 4x4 grid Review feedback on #10276: reducing each spot channel no longer scans the whole document; a thumbnail pixel averages at most 4x4 samples, so a huge document costs about h x 4 row reads per spot. * Spot overlay: composite a proxy for the first draw of big documents The overprint needs the composite under the inks. Rendering the full-resolution composite to sample every factor-th pixel cost ~1 s for a 24 MP CMYK document. A proxy document picks the same pixels (nearest-neighbour downsample) for about 1/factor² of the work. Documents with layer effects, vector masks or feathered masks keep the full-resolution path. * Proxy: downsample rows in parallel; the overlay proxy leaves out unused channels The spot overlay's proxy shrank the spot channels, selection and Quick Mask that an RGB or CMYK composite never reads: about two thirds of its cost. downsample now reads its rows in parallel, 64 at a time. * Spot overlay proxy: keep the full-resolution composite under uncached pattern fills A pattern fill renders its tiles at their own size on a proxy, so the proxy would sample other pattern pixels. Unless the fill's matching cache is shrunk with the proxy, use the full-resolution composite. * Spot overlay proxy: hidden layers don't decide the composite path Hidden layers and the contents of hidden groups are not composited, so a hidden feathered mask or pattern fill no longer forces the full-resolution composite (the same rule as proxy_faithful). * Update crates/compose/src/multichannel.rs Co-authored-by: aiongg <67543387+aiongg@users.noreply.github.com> * Spot overlay: only inked pixels replace the canvas; inks through the display transform Where every visible spot is 0 the overlay stays transparent, so the normal canvas (display LUT, proofing, 32-bit preview, full resolution) shows; an empty spot renders no composite. Printed pixels go through the canvas display transform (sRGB encoding of linear composites, monitor profile, Proof Colors), keyed into the texture. Ink colours come from one InkSpace per render: the document's profiles are parsed once, not per spot per damage rect. * test(compose): cover hidden spot preview and document approximation * fix(ui-egui): preserve solo spot channel display when process colours are hidden Clicking a spot channel row in the Channels panel targets the channel alone and hides all process colour channels (`vis == 0`). Overprint inks were previously extracted from `overlays` before checking `vis == 0`, leaving `overlays` empty and causing the solo channel view to fall back to an active layer mask (or return `None` if the layer had no mask). Keep the spot channel in `overlays` so `vis == 0` can select it as the solo grayscale picture, separating overprint inks only when process colours are visible (`vis > 0`). Adds a regression test targeting an existing spot channel in RGB and CMYK on an unmasked layer and editing it. * channel_view: document the spot overprint preview as an approximation * test(ui-egui): solo spot channel wins over an unrelated layer mask * channel_view: sRGB spot inks, lazy display transform, inked-area composite Review fixes for the spot overprint preview: - RGB spot colours are sRGB, like the CMYK, Lab and gray ones: InkSpace now converts them into an RGB document's own profile too. In a linear (EXR/HDR), Adobe RGB or ProPhoto document an RGB ink showed the wrong colour (brightened in a linear one once the canvas sRGB-encoded it). - The display transform is looked up only when the view is redrawn and inks are shown, not every frame before the cache check; display_transform_for borrows the proof state (proof_ref) instead of cloning a default CMYK profile. - Visible spot channels without ink, with nothing else shown, draw no texture: render returns None and ensure remembers it, instead of uploading an all-transparent document-sized texture. A damage re-render that finds nothing clears its rect, so erased ink never lingers. - Printing a few inked pixels composites only their bounding box when that is cheaper than the whole (proxy) composite, and a cached composite is borrowed instead of copied. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: aiongg <67543387+aiongg@users.noreply.github.com> Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
196 lines
15 KiB
TOML
196 lines
15 KiB
TOML
# Scorecard checklist: file compatibility (#215 formats, #216 measurement).
|
||
# Corpus numbers come from crates/io/tests/it/corpus.rs, not from here.
|
||
# Statuses must be true on main. Verified against 4b53f62 on 2026-10-05 (smart objects: #215 PR).
|
||
area = "file"
|
||
title = "File compatibility"
|
||
order = 2
|
||
|
||
[[item]]
|
||
id = "FILE-215-1"
|
||
target = "Smart Objects export as embedded Smart Objects (SoLd + lnk2), not flat pixels"
|
||
status = "done"
|
||
issue = 215
|
||
note = "psd_export.rs smart_blocks: PlLd + SoLd, source in lnk2 (.pcraft -> PSB); engine tests/it/smart_psd.rs; Photoshop 2026 opens 8/16-bit exports as live smart objects. Linked files are embedded"
|
||
|
||
[[item]]
|
||
id = "FILE-215-2"
|
||
target = "Smart Filters survive PSD save and re-open (filterFX descriptor read and written)"
|
||
status = "partial"
|
||
issue = 215
|
||
note = "smart_map.rs: 49 filters plus Hue/Saturation, Perspective Warp, Filter Gallery (Chrome) and a verified Camera Raw subset mapped both ways, others kept verbatim, mask in FEid; io tests/it/smart_corpus.rs. PhotoCraft-only filters (Twirl, Liquify…) and unmapped Camera Raw settings are left out of filterFX with a warning"
|
||
|
||
[[item]]
|
||
id = "FILE-215-3"
|
||
target = "Filter mask can be painted directly, with undo"
|
||
status = "done"
|
||
issue = 215
|
||
note = "Clicking the filter mask thumbnail on the Smart Filters row targets it (ui.filterMaskTarget, smart_ui.rs). Painting, fills, filters and adjustments with \"target\":\"filterMask\" then edit it (a white mask starts on the first stroke) and re-render the smart object, one undo step per stroke (channel_cmds Target::FilterMask, Session::edit). ⌥-click shows it (view.layerMask \"mask\":\"filter\"), ⇧-click disables it. Kept through .pcraft and PSD FEid (smart_cmds/tests/filter_mask_paint.rs)"
|
||
|
||
[[item]]
|
||
id = "FILE-215-4"
|
||
target = "Adjustments (Hue/Sat + Colorize, Curves, Levels, Shadows/Highlights) apply as Smart Filters"
|
||
status = "done"
|
||
issue = 215
|
||
note = "Image › Adjustments on a smart object records image.adjustments.<kind> as a smart filter (adjust_cmds.rs adjust_as_smart_filter; the selection becomes the filter mask), re-rendered from the source by adjust_as_filter and re-editable with layer.smartFilter.setParams; PSD maps Levels/Curves/Shadows-Highlights both ways (smart_map.rs). Opening the editor from the smart-filter row is FILE-215-5's row work"
|
||
|
||
[[item]]
|
||
id = "FILE-215-5"
|
||
target = "Smart Filter rows: collapse, mask thumb, blend icon, drag reorder, context menu, live preview"
|
||
status = "partial"
|
||
issue = 215
|
||
note = "smart_ui.rs:31 rows + visibility + editor; no collapse, drag or context menu"
|
||
|
||
[[item]]
|
||
id = "FILE-215-6"
|
||
target = "Smart Object corner skew and perspective (quad or homography transform)"
|
||
status = "done"
|
||
issue = 215
|
||
note = "#426: SmartObject.perspective holds the projective map (transform keeps its affine approximation); Distort / Perspective on a smart object set it (transform_cmds.rs, smart_cmds::set_placement), it re-renders from the source through the homography, Warp composes with it, and the PSD placement reads and writes nonAffineTransform / Trnf (io/blocks.rs placed_projective, smart_map.rs); tests distort_survives_edit_contents, warp_on_a_distorted_smart_object_follows_the_corners, placement survives the save"
|
||
|
||
[[item]]
|
||
id = "FILE-215-7"
|
||
target = "Layered TIFF read and write (tag 37724), honouring the ask-before-saving pref"
|
||
status = "done"
|
||
issue = 215
|
||
note = "psd tiff.rs (ImageSourceData, both byte orders), io tiff_layers.rs, ui tiff_options_ui.rs (Discard Layers and Save a Copy); extra alpha/spot channels and a Quick Mask are written as TIFF extra samples (at most 16) plus Photoshop resources for name, colour and solidity; Photoshop's MTrn marker is read (#7415). TIFF Options (ui tiff_options_ui.rs) asks on every TIFF Save As and Save a Copy: compression, Save Image Pyramid, Save Transparency (on writes MTrn next to channels, as Photoshop does) and the layers choice (#8827)"
|
||
|
||
[[item]]
|
||
id = "FILE-215-8"
|
||
target = "Large and BigTIFF import with banded decode across all IFDs"
|
||
status = "done"
|
||
issue = 215
|
||
note = "codecs tiff_ifd.rs (bounds-checked IFD chain + SubIFD walk, classic and BigTIFF, cycle-safe; tiff_info / decode_tiff_page), tiff.rs banded parallel strip/tile decode, planar 1 and 2, BigTIFF write (tiff_bigtiff or > 4 GiB); io import_tiff_page; 30 MP RGB8 Deflate 345 -> 32 ms; flat pages up to 300000 px a side decode a group of rows at a time into the layer (codecs TiffBands, #8169): the 117504 x 22464 px banner from #8456 opens in 3.3 s at 11.45 GB peak; refused with an error past free memory (io memory.rs); layered TIFFs still decode whole; no page picker in the UI yet"
|
||
|
||
[[item]]
|
||
id = "FILE-215-9"
|
||
target = "SVG opens and places as shape layers or a vector Smart Object"
|
||
status = "done"
|
||
issue = 215
|
||
note = "io svg.rs: paths, groups, text outlines as shape layers (gradients approximated; clip/mask/filter/pattern/image subtrees rasterised); smart_cmds.rs renders an SVG source at its placement scale"
|
||
|
||
[[item]]
|
||
id = "FILE-215-10"
|
||
target = "Multi-page PDF save, PDF presentation, layer comps to PDF, vector/text output, PDF import"
|
||
status = "partial"
|
||
issue = 215
|
||
note = "io pdf.rs: Save As / Save a Copy / CLI write one flattened RGB/Gray/CMYK page with ICC (#3401); print PDFs in print_cmds. Multi-page save, presentation, comps, vector/text output and PDF import (#2541) remain"
|
||
|
||
[[item]]
|
||
id = "FILE-215-11"
|
||
target = "RAW: lossy DNG, highlight modes, .xmp sidecars, RAW prefs wired, per-camera corpus"
|
||
status = "partial"
|
||
issue = 215
|
||
note = "2026-10-13: lossy DNG (34892) decodes stored 8-bit samples with padded edge tiles (raw sensor.rs lossy_jpeg, #5112); a raw's sidecar IMG.xmp fills the open-time dialog and Open writes it back (camera_raw_ui.rs read_sidecar/write_sidecar, pref rawDefaults.writeXmpSidecars); every Camera Raw Defaults preference is read (colour space, bit depth, resolution, Sharpen For, Open as Smart Object, Apply auto tone: apply_raw_defaults); Calibration panel (shadow tint, primaries hue/saturation) in the dialog, the filter and the crs: keys. Still missing: highlight recovery modes, the absolute (kelvin) white balance of raw sidecars, a per-camera corpus"
|
||
|
||
[[item]]
|
||
id = "FILE-215-12"
|
||
target = "Camera Raw dialog shows a histogram; settings persist as a PSD Smart Filter"
|
||
status = "partial"
|
||
issue = 215
|
||
note = "Interactive RGB/Lab scopes. PSD WB/Light/Presence, curves, HSL, grading, sharpening/noise detail, grain and numeric vignette controls mapped with descriptor preservation. Active manual Optics and unverified vignette style keep the updated ACR 18.4 fixture opaque; PSD/.pcraft saves preserve it. HDR, remaining settings and Photoshop acceptance still open"
|
||
|
||
[[item]]
|
||
id = "FILE-215-13"
|
||
target = "Video layer frames persist in .pcraft; PSD video layers mapped"
|
||
status = "partial"
|
||
issue = 215
|
||
note = "format convert.rs:89 + tests/it/roundtrip.rs:282 persist frames; no PSD video mapping"
|
||
|
||
[[item]]
|
||
id = "FILE-215-14"
|
||
target = "User swatch model with ACO/ASE import and export"
|
||
status = "done"
|
||
issue = 215
|
||
note = "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"
|
||
|
||
[[item]]
|
||
id = "FILE-215-15"
|
||
target = "Layer Style contour picker, curve editor, presets, .shc import/export"
|
||
status = "partial"
|
||
issue = 2912
|
||
note = "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"
|
||
|
||
[[item]]
|
||
id = "FILE-215-16"
|
||
target = "Advanced Blending (knockout, blend as group, mask hides effects) end to end"
|
||
status = "partial"
|
||
issue = 215
|
||
note = "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"
|
||
|
||
[[item]]
|
||
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.16.0 PSDs (straight planes) keep their ink. RGB/CMYK documents preview visible spot channels as inks printed over the canvas composite (compose multichannel::overprint, the Multichannel formula, in Channels panel order; ui-egui canvas/channel_view.rs), open with spot eyes on, and print them in the PSD thumbnail (multichannel::thumbnail_with_inks); flatten, export and the merged image stay free of them (#10249). Missing: PDF Separation"
|
||
|
||
[[item]]
|
||
id = "FILE-215-18"
|
||
target = "Save for Web per-slice settings remembered; Export As remembers and offers nearest scaling"
|
||
status = "done"
|
||
issue = 215
|
||
note = "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"
|
||
|
||
[[item]]
|
||
id = "FILE-215-19"
|
||
target = "Save As JPEG opens a JPEG Options dialog (quality 0-12, format options)"
|
||
status = "partial"
|
||
issue = 215
|
||
note = "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"
|
||
|
||
[[item]]
|
||
id = "FILE-215-20"
|
||
target = "Artboard tool with handles and + buttons, Duplicate Artboard beside, vector PDF pages"
|
||
status = "partial"
|
||
issue = 215
|
||
note = "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"
|
||
|
||
[[item]]
|
||
id = "FILE-215-21"
|
||
target = "Local libraries of colours, patterns, images and layers, as linked items or copies"
|
||
status = "missing"
|
||
issue = 215
|
||
note = "Only preset libraries (preset_store.rs)"
|
||
|
||
[[item]]
|
||
id = "FILE-216-1"
|
||
target = "Every corpus fetched and enforced in CI, cached by pin"
|
||
status = "done"
|
||
issue = 216
|
||
note = "ci.yml has a corpus job (name: corpus tests) on every PR and on main: the corpora are pinned in xtask/src/corpus_pins.rs, sha256-verified after both a cache restore and a download, and a missing corpus fails the tests. The cache key follows the pins, so it is cached by pin as this row asks (#190)"
|
||
|
||
[[item]]
|
||
id = "FILE-216-2"
|
||
target = "Large-file fixtures: 20 MP, 150-layer, generated 2 GB PSD and 4-5 GB PSB stress tests"
|
||
status = "done"
|
||
issue = 216
|
||
note = "Generated at test time by psd fixtures.rs (feature testgen; never committed). io tests/it/large_files.rs: layered_20_mp_psd_opens_and_saves (5472x3648, 5 layers in 2 groups, mask, drop shadow) and layout_with_150_layers_opens_and_saves (A4 300 ppi, 150 layers in 15 groups, masks, hidden layers, closed groups, drop shadows) run in every cargo test (~2 s each); opt-in psd_just_under_2_gb_opens_and_saves (17,000 px, 2.02 GB PSD) and psb_of_4_gb_opens_and_saves (25,000 px, 4.38 GB PSB, data past 4 GiB), each opened, pixel-checked, saved and re-opened; plus large_psb.rs (2.5 GB merged image). Open times: ui-egui large_file_bench (P20, P63). The 2 GB and 4 GB files are not run in CI (6.4 GB on disk; ~12 GB of memory for the PSD test, ~25 GB for the PSB test) (#5107)"
|
||
|
||
[[item]]
|
||
id = "FILE-216-3"
|
||
target = "TIFF, RAW and EXR corpora read by tests; per-camera RAW coverage reported"
|
||
status = "partial"
|
||
issue = 216
|
||
note = "EXR: corpus/exr (the OpenEXR deep test images, pinned) is read by codecs tests/it/deep_exr.rs in the CI corpus job, against the ID manifests of the upstream sidecars. corpus/tiff and corpus/raw are still copied in by hand and no test reads them; no per-camera RAW report"
|
||
|
||
[[item]]
|
||
id = "FILE-216-4"
|
||
target = "Exports re-open in a third-party oracle editor"
|
||
status = "missing"
|
||
issue = 216
|
||
note = "The round trip is our export -> our import"
|
||
|
||
[[item]]
|
||
id = "FILE-216-5"
|
||
target = "Save-path tests: layered TIFF, PDF, SVG, ACO/ASE, .shc, Smart Filters, atomic-save failure"
|
||
status = "partial"
|
||
issue = 216
|
||
note = "Every save path that exists is tested by saving and reading back: layered TIFF io tests/it/tiff_layers.rs (every_compression_keeps_the_layers, advanced_blending_survives_layered_tiff_in_both_byte_orders), print PDF engine print_cmds/tests.rs (print_dry_run_renders_a_pdf_and_reports_the_platform_backend decodes the image back), ACO/ASE engine swatch_cmds/tests.rs (aco_round_trip_keeps_16_bit_values_and_books, ase_round_trip_keeps_groups_floats_and_kinds), .shc psd shc.rs shc_round_trips_and_rejects_hostile_input and engine layer_style.rs contour_presets_and_custom_curves_apply_in_one_undo_step, Smart Filters engine tests/it/smart_psd.rs (smart_filters_survive_psd_in_every_colour_model), atomic-save failure format/src/atomic/tests.rs (BUG-203-6). There is no SVG writer (SVG opens only) and no Photoshop PDF save (FILE-215-10), so those have nothing to test yet; the row stays partial until they exist"
|
||
|
||
[[item]]
|
||
id = "FILE-216-6"
|
||
target = "30-50 end-to-end workflow acceptance tasks driven through MCP or commands"
|
||
status = "partial"
|
||
issue = 216
|
||
note = "automation/tests/it/agent_tasks.rs, agent_tasks_extended.rs and agent_tasks_more.rs: 36 MCP tasks"
|