mirror of
https://github.com/storytold/photocraft.git
synced 2026-11-10 10:10:34 -06:00
be9f0c250a19891496ccd71942bc200964627c52
24
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
ad96df566b |
File > Save As / Save a Copy / CLI can write PDF (#3401) (#3441)
* TIFF: read and write CIE L*a*b* (CIELab, ICCLab); Lab documents stay Lab Photoshop saves Lab documents as CIELab TIFFs (PhotometricInterpretation 8, a* and b* signed), which PhotoCraft could not open, and PhotoCraft saved Lab documents as sRGB TIFFs. - codecs: a Lab channel layout (Lab, LabA) holding a* and b* as PhotoCraft's Lab documents do ((a + 128) / 255; 16 bits like the PSD importer), with its own Lab <-> sRGB conversion (same formulas as photocraft-color; codecs stays standalone) for formats without Lab, which now warn LabConverted. - TIFF read: CIELab and ICCLab (8 and 16 bits, alpha) are handed to the tiff crate as RGB and labelled Lab; a* and b* are converted from the file's encoding (signed a / 256a, or offset). - TIFF write: Lab and LabA as CIELab, as Photoshop writes it; float Lab is written with 16 bits (DepthReduced). - io: a Lab TIFF opens as a Lab document; a Lab document with one plain layer is written to TIFF in Lab (other formats still use sRGB). Photoshop 2025's Lab TIFFs open with the colours Photoshop shows (its own RGB conversion matches within one level at 8 bits and exactly at 16 bits), and Photoshop reads PhotoCraft's Lab TIFFs back with the same L*a*b* values. Fixes #4936 * PSD: a smart object's filter mask shows when the file opens Photoshop stores a smart object's layer pixels with its filters applied but not its filter mask. When it composites, it mixes the unfiltered pixels (cached in FEid) back in through the mask. We showed the stored pixels as they were, so a PSD whose smart filter has a mask opened with the filter everywhere. Only after something re-rendered the smart object did the mask appear, because our re-render mixes it. - compose::filter_mask holds the mix, moved from the engine, and its inverse. - Import decodes the unfiltered pixels from FEid (smart_map::unfiltered_from_item) and keeps the mixed result as the smart object's pixels, as the engine's re-render does. - Export writes the layer record the way Photoshop does: unmixed with the unfiltered pixels it writes (or keeps) in FEid, so Photoshop mixes back to what the document showed. Transparent pixels stay empty. Photoshop oracles: sf-filter-mask-hard, sf-filter-mask-gradient and sf-stack-blend-and-mask pass (145 -> 148 of 258); floor 145 -> 148. psd-tools and io corpora unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * DNG: decode lossy (JPEG-compressed) DNG tiles (FILE-215-11) DNG 1.4 lossy JPEG (compression 34892) stores each tile or strip as a baseline-DCT JPEG of 8-bit samples: a gray stream for CFA data, a YCbCr stream for three-sample LinearRaw. The decoder arm from #2021 read the streams but widened every sample by 257, so the values no longer matched the file's levels: an 8-bit WhiteLevel (or its default, 255) clipped everything to white and a 256-entry LinearizationTable was indexed past its end. It also required the JPEG frame to match the clipped row count, which rejected the padded edge tiles real tiled files carry. The arm is now `lossy_jpeg()` in sensor.rs. The decoded 8-bit values are the stored samples, and BlackLevel, WhiteLevel (default 255 for 8-bit data) and the LinearizationTable apply to them exactly as for every other compression. A tile may be coded at the full tile size (edge tiles padded, as the spec says) or at the image's row count; a strip at its row count; the width must be the segment's. BitsPerSample must be 8 and the JPEG's component count must equal SamplesPerPixel (1 or 3); anything else is an error, never a silent conversion. The frame header is checked before any pixel is decoded and the JPEG decoder's own size cap is the segment the IFD declares (already charged against the limits), so a hostile SOF cannot size a buffer past the tile. testgen writes true 8-bit lossy fixtures (`LossyJpegTiles`, and strips via the new `LossyJpegStrips`), accepts pre-encoded `lossy_segments` for hostile-input tests, exposes `baseline_jpeg()`, and leaves WhiteLevel out when `white` is 0. The DNG tests cover CFA and LinearRaw RGB from tiles and strips at 37x23 (partial edge tiles, a short last strip), the default white level, black/white scaling and a LinearizationTable against the uncompressed path, and hostile inputs: a truncated tile, frame-size and component-count mismatches, a 65535x65535 SOF under default and tight limits, a tile over the allocation budget, and 12/16-bit or 4-sample files. The hostile mutation set gains a lossy CFA and a lossy LinearRaw file. The parity doc's DNG row is updated; the scorecard row is left for the coordinating branch. * Raw: measure the Nikon D90 image area (4288x2848 at (10, 10)) Aligning the decoded D90 sensor plane with the camera's own JPEG puts the image at (10, 10) in the 4352x2868 sensor plane, giving the camera's 4288x2848 frame (10-pixel margins; the right border reads the camera's flat 255 from column 4310). Add the measured ImageArea so a D90 NEF opens on the camera's frame instead of the uncropped plane, and make a measured profile leave a file that carries its own colour calibration (a DNG) untouched. * File > Save As / Save a Copy / CLI can write PDF: one flattened page with the embedded profile (#3401) * Codecs: open Cineon (.cin) film frames read-only Cineon is the 10-bit logarithmic film-scan format DPX grew from. Read it from the same Kodak header family: the generic header (big-endian, orientation at 192, channel count at 193, the channel-1 bits/dimensions), one or three channels, three 10-bit samples packed into each 32-bit word on a word boundary. The 10-bit code values open as U16 (round(c x 65535 / 1023)); the file's log black/white points are a display choice and are not applied. A depth other than 10 bits or a channel count other than 1/3 is Unsupported; truncation errors rather than panicking. Read-only (no Cineon writer), so .cin joins the open filters and Cineon read closes part of #1049's G10 gap. * Vector: bound flattening memory, keep over-budget dashes, cheaper edge build (#5195) - flatten: a path's flattened points are capped at 1 << 20; past it every curve keeps a proportional share of its pieces (at least one), so hostile handles from files (vmsk, SVG, effect outlines) cannot exhaust memory. fill_rasterizer flattens the whole path under one budget. - stroke: past the dash step budget (or below coordinate precision), dash_polyline keeps the dashes made so far and draws the rest solid instead of dropping the stroke. Open polylines are no longer copied. - raster: coordinates are clamped to +-1e15 and edges with a non-finite slope (vanishingly short) are skipped, so huge finite coordinates no longer produce NaN coverage. fill_rasterizer sorts edges once instead of after every component (stable sort, identical order). - Cleanups: dead miter `half`, dead loop-length check in trace_mask, shared mask render body, map_knots helper, shared straight-segment test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * PSD: cap pattern memory across a parse, reject mixed-depth pattern channels (#5198) - Patterns: one PatternBudget (decoded bytes + zero padding) is shared by every pattern of a block, a .pat file and the pattern blocks of one document; .abr patt sections are charged to the samples' budget. - Patterns: a channel whose depth differs from the first is rejected (the parsed pattern could not be written back). - Slices: descriptor numbers no longer wrap; out-of-range ids are no id, coordinates clamp to i32, and a v6 trailing entry without a usable id attaches its outsets to no slice. - LayerInfo::unpadded_len is computed from field sizes instead of serializing every record into a scratch buffer. - abr desc presets are moved out instead of cloned; UnFl and shmd counts use the bounded preallocation; ImageData::decode_channel (decoded every plane to return one, unused outside its test) is removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Vector: bound stroke outline points (#5211) - stroke_polygons dashes all lines first, then counts round caps and joins. When their full-resolution tessellation would exceed 1 << 20 outline points, every round cap and join gets the same smaller segment count (down to 4 per circle, 1 per join; area-exact radius kept). Strokes under the budget are unchanged; no piece is dropped. - The dash step budget (100000) is now shared by all lines of a stroke instead of per polyline, so many subpaths cannot multiply the dash (and cap) count. Past it the rest is drawn solid, as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * PSD: write tiff blocks without copies, inflate merged ZIP once - ImageSourceData::to_bytes writes the global mask and global blocks straight from the model with 4-byte alignment instead of cloning each block (data included) to set its padding. Same bytes. - The tiff transcoder's nested regions and blocks write into the parent's buffer (taken and handed back, error or not) instead of building their own output and copying it up per nesting level. Bodies that fail or are not transcoded are truncated before being copied as stored or dropped. Same bytes. - PsdFile::from_bytes_and_merged decodes the merged image with the parse when asked: a ZIP stream is inflated once (the decode's stream check is the parse's); the parse-only check runs only when the decode fails, to keep from_bytes' errors. PSD import uses it with merged_needed. - PsdBuilder::composite documents that Gray/CMYK composites stay straight. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Content-Aware Fill: measure a thin hole by its thickness, not its bounds (#1366) Removing long, thin objects (coiled guitar strings, wires) left their outline as darker and lighter blotches and copied pieces of other strings into the fill. The pyramid shrank until the hole's bounding box was four patches across: for a string across the image that is a level where the hole has swallowed its surroundings, so the matches found there copy whatever looks like the blur, and the finer levels (refine-only for wide bounds) keep them. A hole at most a quarter as thick as its bounds are long now counts as thin: the pyramid stops while it is still about two patches thick, and that level starts from a membrane across it (over a few pixels, the surroundings themselves) instead of the onion peel, whose exhaustive search is what made a shallow pyramid unaffordable. Compact holes are unchanged bit for bit. Refs #1366 * perf(compose,gpu): effect caches reserve tile addresses instead of pinning layers Effect map cache entries kept a clone of their layer (a deep clone for groups) so the tile addresses the cache key hashes couldn't be reused. That kept an edited or deleted layer's pixels alive, uncounted, until the entry was evicted. Keep a Weak to each hashed tile instead: it reserves the address without keeping the pixels. The GPU compositor's styled-group pins and shape-source tile snapshots do the same. Part of #4530. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(history): count snapshot-only tiles against the history budget History panel snapshots outlived every budget: a Merged Layers snapshot is a full flattened copy that neither the history budget, the GPU budget's document bytes nor Purge accounted for. Trim now counts the tiles only snapshots hold as fixed cost, the GPU budget includes them, and Purge > Histories no longer reports tiles a kept snapshot still holds as freed. Part of #4530. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Content-Aware Fill: every part of a thin selection keeps its surroundings (#1366) Two more causes of ghosts when removing strings and the small knobs selected with them: * Auto sampling summarises the selection's surroundings by a few clusters. Around a long selection those come from the strings' surroundings, so a small part elsewhere (a tuning knob in a differently coloured corner) lost its own surroundings and was filled from far away in another colour. Around a thin selection the Auto area now always reaches as far as the selection is thick. * The membrane that starts a thin hole now starts a patch radius out, past the anti-aliased fringe or halo that a selection drawn close to the object leaves outside it. Refs #1366 * Content-Aware Fill: peel a thin hole where that stays affordable (#1366) Starting every thin hole from a membrane smoothed Spot Healing on film grain (spot_healing_keeps_film_grain_through_overlapping_strokes: grain 9.0 against 12.1 around). The onion peel copies the grain; it was only given up because its exhaustive search is too slow on the big level a long thin hole stops at. It now starts the thin hole whenever hole cells times sources stay within 2^28 comparisons, and the membrane only beyond. Refs #1366 * Select Subject: keep subject parts coloured like the backdrop, leave enclosed gaps open Users on #2788 reported Select Subject dropping clothes that are close in colour to a plain wall and bridging the gap between an arm and the torso. Both come from the same place: the saliency pass labels low-contrast parts of the subject as background, the background colour model then learns them and explains them away, and the final blind hole fill closes every enclosed gap up to 5 % of the mask. Two changes in crates/algo: - A second GrabCut pass whose background model only knows the backdrop. A colour model fitted to the image frame (minus a margin around the salient blob, in case the subject runs out of the picture) marks every pixel it explains as probable background; the blob and whatever the backdrop cannot account for start as probable foreground. Only components that touch the first pass's result survive. - Hole filling that consults the colour models: specks (<= 0.2 % of the mask) always fill, larger holes up to 5 % fill only when their pixels fit the foreground model better than the background's. Tests: two regressions (a cream-shouldered figure on a near-white wall keeps its shoulders; a ring keeps its backdrop-coloured hole open), plus ignored timing and picture-dump helpers. The 6 MP first run goes from about 100 ms to about 200 ms on one core; the P29 repeat path is untouched. * Keyboard shortcut sets: Set menu, New Set, Save, Delete, .kys Export and Summarize (K2) Edit › Keyboard Shortcuts gets Photoshop's set management. A set is a saved copy of the bindings (Preferences.shortcutSets, the one in use in shortcutSet; "" is PhotoCraft Defaults), and the Set menu shows "(modified)" when the bindings differ from it. New Set…, Save and Delete wait for OK like the rest of the dialog. Export… writes the dialog's bindings as a .kys file (commands then tools, Photoshop's macOS key spelling), each row carrying our id as pc-id so PhotoCraft imports its own sets back exactly, whatever the label; Summarize… writes Photoshop's HTML list of every bound command by menu. edit.keyboardShortcuts takes useSet (before the call's edits), saveSet, deleteSet, sets and activeSet (after them), exportKys and summarize; every reply reports the sets, the set in use and whether it is modified. Bad names, keys or counts are errors that change nothing. * .kys import: tool keys and sets saved on Windows (K3) Photoshop's <tool> rows now import: each matches our tool by name (our tool names are Photoshop's) or by pc-id in sets PhotoCraft exported, and a letter other than the tool's own becomes its override, while its own letter keeps the tool cycling with the others (#5036). A set saved on Windows spells the primary modifier Ctrl where a Mac set writes Cmd and keeps Control for the Control key. A set with no Cmd or Opt anywhere but some Ctrl is read as a Windows set, its Ctrl as our Cmd: Ctrl+L is the Cmd+L default, not a Control-key override that would also mean Control on a Mac. The import reports `windows`. * cargo fmt the extension lists Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Gert Pellin <gert@pellin.be> Co-authored-by: timothydaly1 <tadaly04@protonmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Fabio Lissi <fabio.lissi@gmail.com> Co-authored-by: vangel76 <vangel1976@googlemail.com> Co-authored-by: Finn Newick <finnnk@gmail.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> Co-authored-by: Diego Chagas <drochachagas@gmail.com> Co-authored-by: Tanish Manku <tmanku.dev@gmail.com> |
||
|
|
c7a34b04ec |
Scorecard: Smart Object corner skew and perspective is done (FILE-215-6, #426) (#5094)
#426 gave smart objects a projective placement (`SmartObject.perspective`, with `transform` as its affine approximation): Distort and Perspective set it, the layer re-renders from its source through the homography, Warp composes with it, and the PSD placement reads and writes `nonAffineTransform` / `Trnf`. The scorecard row still said "missing" with the pre-#426 note, as did the ui-parity row and the #579 mention in gaps.md. |
||
|
|
1412ac26e2 |
Save for Web and Export As remember their settings across restarts (FILE-215-18) (#4184)
* engine: remember the last Save for Web settings in the preferences * engine: test that a complete export remembers its settings for the next launch * ui: seed the Save for Web dialog from the remembered preferences * ui: Export As opens with the settings it was used with last * scorecard: FILE-215-18 done, the export dialogs remember their settings * docs: scorecard, File compatibility 7 done of 27 * Regenerate the scorecard Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Brandon Thomas <echelon@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
5fe4d09478 | Merge current main while preserving craft-print transport contracts | ||
|
|
857a4f7d8a |
M11: ten more MCP acceptance tasks (11-20) for FILE-216-6 (#3562)
* test(automation): ten more MCP acceptance tasks (11-20) * docs(scorecard): FILE-216-6 note counts 20 tasks |
||
|
|
c6407cac07 | Keep scorecard test reference current | ||
|
|
d523405627 |
scorecard: FILE-216-5 save-path tests are in the tree (#3476)
Every existing save path (layered TIFF, print PDF, ACO/ASE, .shc, Smart Filters in PSD, atomic-save failure) already has a save-and-read-back test; SVG and Photoshop PDF have no writer yet (FILE-215-10). Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
911f69d7b5 |
Artboard tool: '+' buttons add a board beside the selected one; ⇧ keeps proportions (#3033) (#3046)
* Tools: Artboard tool (#2858) Photoshop's Artboard tool in the Move flyout (V, ⇧V cycles Move ↔ Artboard): dragging on an empty spot draws a new board (`layer.new.artboard`), clicking a board selects it, dragging it moves it with its contents, and the selected board's edge and corner handles resize it (`layer.artboard.set`, moveContents: false). Each gesture is one undo step. Options bar: Size preset, W/H, Make Portrait, Make Landscape, Add New Artboard. Original toolbar glyph (assets/icons/artboard.svg, ATTRIBUTION.md row). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Artboard tool: '+' buttons add a board beside the selected one; ⇧ keeps proportions (#3033) With the Artboard tool, the selected board shows a "+" button beside each side. A click adds an empty board of the same size (background and preset copied) on that side, the 100 px artboard gap away and past any board in the way; ⌥-click adds a copy with its contents instead. A board left of or above the canvas origin shifts the whole document right/down and grows the canvas, so it stays visible. Both go through the new engine command `layer.artboard.addBeside` {layer?, side, duplicate} (one undo step). ⇧ while dragging a corner handle keeps the board's aspect ratio: the opposite corner stays and the board scales by the larger axis factor. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
7c96e9e120 |
Layer Style: contour picker and Contour Editor (#2912) (#2949)
Every effect with a contour (Drop/Inner Shadow, Outer/Inner Glow, Satin,
Bevel & Emboss Gloss Contour and Contour element) now shows a contour
thumbnail with a preset picker and an Anti-aliased checkbox. Clicking the
thumbnail opens the Contour Editor: add/drag/delete points (the shared
point-curve editor), Corner per point, Input/Output, Preset, and Load…/Save…
of .shc contour files, read and written from Adobe's public File Formats
Specification (Contours). Edits go to the dialog's pending params, so the
preview follows and OK applies them in the one layer.layerStyle.replace.
- doc: Contour::Custom gains per-point corner flags (serde default).
- compose: contour_curve_lut splits the spline at corner points; CPU and
GPU effects use it.
- engine: the 12 standard presets with our own control points (older names
still accepted); `contour` params also take a curve object
{name, points, corners}; antiAlias params; Satin contour; the bevel
Contour element edits in place; contours_from_shc / contour_to_shc.
- io: PSD contour points read and write the Cnty (continuity) flag.
- psd: shc.rs parser/writer.
- scorecard FILE-215-15 missing -> partial.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
984bc5c715 |
scorecard: twenty rows describe work that is already in the tree (#2894)
* scorecard: eight Open bugs rows describe work that is already in the tree AGENTS.md sends contributors to the scorecard's missing and partial rows when picking work, so a stale row costs someone a session: they sit down to write a test that is already there. These eight ask for work that exists and is covered by passing tests. #205 / #207, mask and channel transforms: - BUG-205-4 ("no raster-mask test"): linked_raster_mask_moves_with_the_pixels. - BUG-205-5 ("the other two not implemented"): all three cases are covered, by linked_mask_with_a_selection_moves_only_the_selected_region, unlinked_mask_stays_put and targeted_unlinked_mask_transforms_alone. - BUG-207-4 ("transform() has no target handling"): edit.transform takes target (pixels, mask, quickMask or {channel: i}) and documents it in its params doc; targeted_alpha_channel_transforms_alone covers the alpha channel. #206, kerning — the whole section: - BUG-206-1: CharStyle has both `kerning: Kerning` and `kern: f32`. - BUG-206-2 / BUG-206-3: io/tests/psd_kerning.rs round-trips manual, Off and Optical kerning, and reads the AutoKerning flag and Kerning value of every character back out of the written TySh. - BUG-206-4: Alt+Left/Right at a collapsed caret calls kern_pair, and the Character panel field parses Metrics / Optical / a manual value; both tested. - BUG-206-5 stays partial, not done: the round trips are covered, but no oracle corpus file is dedicated to kerning values, which that row also asks for. FILE-216-5 keeps its missing status (no save-path tests for layered TIFF, PDF, SVG, ACO/ASE, .shc or Smart Filters), but its note claimed there is no atomic-save failure-injection test; format/src/atomic/tests.rs has two, and BUG-203-6 already records them as done. docs/scorecard.md regenerated with cargo xtask scorecard; Open bugs moves from 22/2/8 to 29/2/1. * scorecard: the Type section's kerning and Thai rows are out of date too TYPE-218-6 ("Optical renders as Metrics; Alt+arrow moves by word") is done: Alt+Left/Right at a collapsed caret calls kern_pair, optical kerning is computed from the glyph outlines in text/src/optical.rs and applied by layout.rs when the run's mode is Optical, and both modes round-trip through PSD. Covered by type_tool_alt_arrows_kern_the_pair, optical_matches_photoshop, optical_kerning_tightens_open_pairs, txt2_carries_optical_kerning and io/tests/psd_kerning.rs. The row points at #206, whose own rows are in the same state. TYPE-218-3 goes to partial, not done: Thai is covered (THAI_FAMILIES in fonts.rs and thai_in_latin_font_falls_back_to_installed_thai_font, #1909), but Khmer and Indic have no fallback list and there are still no per-script golden tests. Unchanged because they read correctly: TYPE-218-1 (TextShape is still Point and Box), TYPE-218-2 (no direction UI), TYPE-218-4 (the axes in the shell are the brush's), TYPE-218-8 (no triple or quadruple click) and TYPE-218-9. * scorecard: Tools and Distribution rows are out of date too TOOL-213-7 ("Live stroke only for Brush and Eraser"): canvas.rs strokes_live covers Brush, Pencil, Eraser, Blur, Sharpen, Smudge, Dodge, Burn and Sponge, and live_retouch_command adds the Clone Stamp, Healing Brush and History Brush. TOOL-214-8 ("ruler only feeds straighten"): image.rotation.arbitrary opens seeded with the measured ruler angle and the direction that straightens it; arbitrary_rotation_starts_at_the_ruler_angle checks 5.7106 deg within 1e-3 and the direction for three lines. TOOL-214-5 goes to partial, not done ("No context menu in canvas.rs or transform_tool.rs"): TRANSFORM_MENU does open on a right click during a transform, with Free Transform, Scale, Rotate, Skew, Distort, Perspective and Warp, and three tests cover it — but the row also asks for Flip and Rotate 90/180, which are not there. DIST-220-2 ("release.yml only x86_64 and i686 msvc"): the Windows matrix has { arch: arm64, target: aarch64-pc-windows-msvc }, and windows-arm64.yml installs that MSI on a Windows 11 ARM64 runner, checks the binaries are ARM64 and runs the CLI natively. Signing takes the same path as x64 (DIST-2). Unchanged because they read correctly: the whole Automation section (ABI_VERSION is still 1 with Kind::Filter only, there is no ONNX dependency, no such xtask check, and agent_tasks.rs really does run ten scripted tasks), TOOL-213-12, TOOL-214-4, TOOL-214-6, DIST-220-1 and DIST-220-3. * scorecard: Workspace and UI and File compatibility rows are out of date too UI-217-9 ("prefs.rs UiScale unused"): prefs::UiScale offers Auto plus 75 to 300 percent, display_scale resolves it and sync_display_scale applies it with ctx.set_zoom_factor; being a preference, it is remembered. The zoom_factor assert the note cites is in a canvas-zoom test, checking that zooming the document does not scale the interface. FILE-216-1 ("No corpus step in ci.yml; in progress in #190"): ci.yml has a corpus job on every PR and on main, pinned, sha256-verified and cached by the pins. #190 is merged. UI-217-22 and UI-217-23 go to partial, not done: - layer.stampVisible merges the visible layers into a new layer and leaves the originals, and layer.stampDown exists, but neither has a menu entry and Alt+Merge is still missing. - layer.copyToDocument copies layers into another document keeping masks, styles and blending (#589), but there is still no copy/paste layer clipboard. UI-217-11 keeps missing; only its note changes, from "five themes" to the eight ThemeKind has. No Soft dark/Soft light, previews or outlined knobs. Unchanged because they read correctly: UI-217-20 (guides are still Vec<f32> and there is no rulers preference), FILE-215-6 (SmartObject.transform is still an Affine), FILE-215-19 (no JPEG Options dialog), and the rest of both sections. --------- Co-authored-by: Brandon Thomas <echelon@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
2e0c426692 |
Tools: Artboard tool (#2858) (#2862)
Photoshop's Artboard tool in the Move flyout (V, ⇧V cycles Move ↔ Artboard): dragging on an empty spot draws a new board (`layer.new.artboard`), clicking a board selects it, dragging it moves it with its contents, and the selected board's edge and corner handles resize it (`layer.artboard.set`, moveContents: false). Each gesture is one undo step. Options bar: Size preset, W/H, Make Portrait, Make Landscape, Add New Artboard. Original toolbar glyph (assets/icons/artboard.svg, ATTRIBUTION.md row). Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
4c06b9fe4d |
fix(engine): adjustments on a smart object record smart filters (FILE-215-4); strokes honour doc CMYK (#2229)
On a smart-object layer, Image > Adjustments now records an editable
smart filter instead of committing disaster.
- new engine helpers:
- adjust_cmds::has_adjustable / adjust_as_smart_filter route
image.adjustments.<kind> onto the source, with the selection as the
filter mask, through smart_cmds::add_smart_filter
- brush/eraser/pencil/live preview strokes now render inside
with_cmyk_space(doc_cmyk_space(session)), so painted colours follow the
document profile the same way the compositor does
- docs/scorecard.md: FILE-215-4 partial -> done
- tests: adjust_cmds/tests + brush_cmds/tests, plus panic_hunt and
the lib suite all green on the committed tree
Known follow-up, already filed:
- FILE-215-5 rows: double-clicking an adjustment smart-filter row is still
a no-op. smart_ui::open_editor only proceeds when
filter_dialog::has_dialog returns true, and has_dialog accepts
filter.*, a fixed non-adjustment set and PREVIEWED - but not
image.adjustments.* (those open through adjust_dialog::open, which isn't
wired into the smart-filter row).
This is one commit on top of the latest upstream/main (rebased to fix the
stale fork ancestry that was blocking PR creation).
Co-authored-by: Brandon Thomas <echelon@gmail.com>
|
||
|
|
5bef14dbdc |
Swatches: user swatch model with .aco/.ase import and export (FILE-215-14, part of #215) (#886)
- photocraft-psd: clean-room .aco (v1/v2, UTF-16 names, RGB/HSB/CMYK/Lab/Gray at 16 bits, other spaces kept verbatim) and .ase (groups, global/spot/process, RGB/CMYK/LAB/Gray floats, unknown models kept) readers and writers; byte-stable round trips, hostile input -> Err. - engine: presets::swatches (colours in their own model, display/foreground conversion through photocraft-cms working spaces, persisted with the prefs, default 40 colours in 4 groups) and swatch_cmds (swatches.list/add/delete/rename/move/newGroup/renameGroup/deleteGroup/use/reset/ import/export) with graceful-failure tests. - ui: swatches_ui.rs replaces the const grid (groups, tooltips, click = foreground and selected type, Alt/Ctrl-click = background, rename/delete/move, panel menu Import/Replace/Export/Reset), Color Picker "Add to Swatches", .aco/.ase via File > Open and drag-and-drop; strings translated in every complete catalog. - Scorecard FILE-215-14 -> done. Co-authored-by: leon <claude.udder842@passmail.net> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
b0680fd421 |
Advanced Blending: knockout, blend as group, transparency shapes, masks hide effects (FILE-215-16, part of #215) (#903)
* Advanced Blending: knockout, blend as group, transparency shapes, masks hide effects (part of #215) Blending Options > Advanced Blending now works end to end: - doc: AdvancedBlending on every layer (knockout none/shallow/deep, blend interior effects as group, blend clipped layers as group, transparency shapes layer, layer/vector mask hides effects), saved in .pcraft. - PSD and layered TIFF: knko, infx, clbl, tsly, lmgm, vmgm read into the model and written back (in place, or added when not at the defaults); the TIFF byte-order transcoder handles lmgm/vmgm as byte flags. - CPU compositor (compose/src/advanced.rs): shallow and deep knockouts (isolated groups and clipping groups stop a deep knockout), clipped layers blended individually, effects over the whole layer when Transparency Shapes is off, effects built from an unmasked copy when a mask hides them, interior effects under fill opacity. - GPU: documents using Advanced Blending fall back to the CPU compositor. - engine: layer.layerStyle.blendingOptions takes channels, knockout and the five switches; inspect reports them; Copy/Paste Layer Style carries them. - UI: the Blending Options page laid out like Photoshop's (General, Advanced Blending, Blend If with split sliders), translated. psd-tools corpus oracle 229 -> 236 (seven knockout files now pass; floor raised); other corpora and all round trips unchanged. Scorecard FILE-215-16 -> partial. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Polish catalog rows for Advanced Blending; regenerate docs/scorecard.md pl.tsv landed on main and the coverage test needs every catalog to translate new UI strings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Undo damage: compare Layer.advanced in layer_pixels_equal; regenerate scorecard #810's exhaustive Layer destructure must classify the new advanced-blending field; it changes the composite, so it is compared. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Regenerate docs/scorecard.md Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: leon <claude.udder842@passmail.net> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
4cb7cf3931 |
Layered TIFF: the review changes of #672 (keep unknown blocks with their byte order, io behind the engine, fixture attribution) (#835)
* Review: keep unknown blocks with their byte order, io behind the engine, fixtures documented Addresses the review on #672: * Unknown layer blocks read from a little-endian TIFF are no longer dropped. Their bytes are kept verbatim with the signature `MIB8` (`46B8` for `8B64`) marking the little-endian layout (`TaggedBlock::is_foreign_order`), so a save back to TIFF restores them exactly. A malformed known block is kept the same way. Only a save to the other byte order has to drop such a block, with a warning: a PSD export strips them (`strip_foreign_order_blocks`), and a big-endian block of unknown layout still cannot go into a little-endian TIFF. * The UI no longer depends on `photocraft-io`: the layer predicate is exposed by the engine (`file_cmds::tiff_would_write_layers`); io is a dev-dependency again. * The layered TIFF path ends in the same `encode_image` as every flat export, so export-wide metadata policies (such as #733's XMP choice) apply to its composite too. * Scorecard FILE-215-7 is `partial`: extra alpha channels and a Quick Mask are not written. * The committed fixtures get an ATTRIBUTION.md row and a README describing their contents and how they were generated (psdtags + tifffile); the Python generator script is removed. Co-Authored-By: Claude Mythos 5.1 <noreply@anthropic.com> * tiff_corpus: ask for layers explicitly (headless default is flat since #825) Co-Authored-By: Claude Mythos 5.1 <noreply@anthropic.com> * i18n: drop the duplicate Korean "Ambient" entry that fails bundled_catalogs_are_consistent Co-Authored-By: Claude Mythos 5.1 <noreply@anthropic.com> * i18n: drop the duplicate Korean "Warmth" entry as well Co-Authored-By: Claude Mythos 5.1 <noreply@anthropic.com> * agent_tasks: tolerate Windows holding the test directory open during teardown The three tests that remove their directory right after cancelling the client failed on Windows (CI and locally) with os error 32: the spawned server task is still closing its files. The cleanup now retries briefly and otherwise leaves the directory for the next run's tmp() to clear, as it already did at the start. Co-Authored-By: Claude Mythos 5.1 <noreply@anthropic.com> * preset store: a missing directory is an empty store on Windows too DirBackend::read passed the OS message through, and Windows words a missing directory as "cannot find the path specified (os error 3)", which missing_file() did not recognise, so attaching a store on a path that does not exist yet (first launch, and two engine unit tests) reported actions.json as skipped. The backend now reports every NotFound as "<name>: not found", the wording the predicate and the memory backend already use. Regression test: opening a store on a nested missing directory warns about nothing and writes nothing. Co-Authored-By: Claude Mythos 5.1 <noreply@anthropic.com> * Review: keep scripts/layered_tiff_fixtures.py, drop the fixtures README and attribution rows The four .tif fixtures those described were never committed (binary test data stays out of the repo; the tiff_layers tests read them from the gitignored corpus/layered-tiff/, written by the script that tiff_layers.rs:181 still points to). Co-Authored-By: Claude Mythos 5.1 <noreply@anthropic.com> * automation: the Direct Selection path commands edit vector paths, not files #865 added path.moveAnchors, path.moveHandle, path.bendSegment and path.convertPoint, whose registry docs mention the vector path they return, so registry_filesystem_path_params_are_classified fails on main (Linux and Windows): they join the list of commands whose "path" is a document path. Co-Authored-By: Claude Mythos 5.1 <noreply@anthropic.com> * i18n(cs): the lower-case "{n} layer|{n} layers" plural (main red since #829) xtask's source_key_set_is_covered_by_complete_catalogs fails on main: the key reached every catalog but the Czech one. Same forms as the capitalised twin already in cs.tsv. Co-Authored-By: Claude Mythos 5.1 <noreply@anthropic.com> * Drop the duplicate path.* entries from the workspace test (main already lists them) main already has path.moveAnchors/moveHandle/bendSegment/convertPoint in this match; the duplicates trip unreachable_patterns. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: leon <claude.udder842@passmail.net> Co-authored-by: Claude Mythos 5.1 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
f41eb4f70d |
SVG opens as shape layers and places as a vector smart object (FILE-215-9, part of #215) (#1305)
* SVG opens as shape layers and places as a vector smart object (FILE-215-9, part of #215) crates/io/src/svg.rs: usvg parses (already in the tree through egui_extras); paths become ShapeLayer knots with the transform chain baked in, quadratics raised to cubics and every subpath after the first joining the component so the fill rule spans the path as SVG does; groups keep opacity and blend mode (transform-only and id-only wrapper groups vanish); text is shaped into outline shapes with the craft fonts plus the system's; gradients become the layer's gradient fill (stops and angle, with a warning). Clip paths, masks, filters, pattern paints and images are rasterised by resvg into pixel layers over their bounding box rather than dropped. Limits: 16384 px canvas, 2000 elements, 24 M px per raster, bounded recursion; malformed input is IoError::Svg. smart_cmds::render rasterises an SVG source at the placement's axis scale (1/16 steps, cached per scale) and divides the placement by it, affine and projective alike; place_bytes renders that cache up front. svg/svgz join the openable lists; book page; scorecard row done. Co-Authored-By: Claude Mythos 5.1 <noreply@anthropic.com> * SVG import: never read local files from <image href> usvg's default image resolver reads any absolute or relative path in an <image href>, so opening an untrusted SVG could embed local files (/etc/..., ~/.ssh/...) into the document. Parse with a resolver that only accepts data: URLs (resources_dir None, string hrefs ignored). Embedded sub-SVGs already get no <image> resolution from usvg. The font database (and its system-font scan) is now only loaded when the drawing may contain text. Regression test: a PNG on disk referenced by absolute path, file:// URL and relative path is not loaded; a data: URL still is. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: leon <claude.udder842@passmail.net> Co-authored-by: Claude Mythos 5.1 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
964a41dc02 |
Corpus: pin the deep OpenEXR test images and check them in CI (#881)
The deep EXR decoder (#642) was verified against the OpenEXR project's deep test images, but those lived in a hand-copied `corpus/exr/` and the test skipped when they were absent, so CI never read them. `cargo xtask corpus --exr` (and `--all`) now fetches the five `*.deep.exr` files of openexr v3.5.2 `src/test/bin/test_images`, sha256-verified against `xtask/exr-corpus.sha256`. `real_deep_files_decode` is behind the `corpus` feature and fails when the corpus is missing. It checks each file's sample total, largest per-pixel count, cumulative counts and channel lengths, and that the decoded u32 ID channels hold exactly the IDs listed by the upstream `.txt` sidecars (an independent oracle), plus the flattened decode. FILE-216-3 moves to partial (EXR read in CI; TIFF and RAW still are not). |
||
|
|
ec7302f4b3 |
TIFF: BigTIFF, every IFD and SubIFD, banded parallel decode (FILE-215-8, part of #215) (#859)
* TIFF: BigTIFF, every IFD and SubIFD, banded parallel decode (FILE-215-8) Large and multi-page TIFFs now open the way Photoshop opens them, quickly and without a second full-size buffer, and any directory of the file can be opened on request. * photocraft-codecs `tiff_ifd`: a bounds-checked directory walker for classic TIFF and BigTIFF (LONG8 / IFD8, both byte orders). It follows the whole IFD chain and the SubIFD trees (tag 330) with a seen-set, a depth limit and a 10 000-directory cap, and checks every value range against the file. `tiff_info` lists the pages (size, kind: page / reduced-resolution / mask, parent, storage); `default_page` is the first full-resolution page of the main chain, so a leading thumbnail is skipped. `decode_tiff_page`, `tiff_orientation`, `tiff_page_orientation`. * Banded decode: the `tiff` crate reads the chosen directory through a patched byte view (header -> that IFD, its next pointer -> 0, WhiteIsZero / palette -> BlackIsZero), so SubIFDs and cyclic chains decode. The image is allocated once (fallibly, zeroed in parallel) and each strip or row of tiles, across every plane, is decoded straight into it, in parallel on native targets, with one capped chunk-sized scratch buffer per worker. New: planar configuration 2 (was an error), palette images (as RGB), 64-bit integers, associated alpha made straight, non-alpha extra samples dropped. Image data cut off by the end of the file decodes with a `Truncated` warning. Before the crate sees a file: structural tag values must lie inside it, the chunk tables must match the geometry (checked in 64 bits; the crate overflowed and panicked on `(h / rows + 1) * planes`), and a decompression-bomb guard refuses files far too small for their declared size. * WhiteIsZero and BigTIFF Orientation keep upstream #746's behaviour (#691, #693) through the new decode path; regression tests cover both. * Encode: `EncodeOptions::tiff_bigtiff` writes BigTIFF; without it, BigTIFF is chosen when the file could pass 4 GiB (and a classic write that overflows 32-bit offsets is redone as BigTIFF). * photocraft-io: `import_tiff_page(name, bytes, page)`; the TIFF branch of import goes through it. * Tests: codecs `tiff_pages.rs` on hand-built files (`tests/common/tiffgen.rs`): BigTIFF LE/BE at 8/16/32f, padded tiles and planar strips/tiles, pages and SubIFDs, cycles, the IFD cap, truncated and past-EOF strips, huge dimensions / counts / tiles, WhiteIsZero, palette, wide formats, BigTIFF write, and a cut / bit-flip / random-mutation zoo (an ignored long run did 200 000 mutations without a panic); io `tiff_pages.rs`. The `decode_tiff` fuzz target walks every page. `tiff_bench.rs` (ignored) times a 30 MP decode against the previous path. * 30 MP RGB (release, 16 threads, same run): Deflate 344 -> 32 ms, LZW 428 -> 43 ms, 16-bit Deflate 497 -> 46 ms, uncompressed 15 -> 10 ms, 256 px tiles 36 -> 10 ms, planar error -> 12 ms. * Scorecard FILE-215-8 done; README and roadmap updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * rustfmt the TIFF reader and tests Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: leon <claude.udder842@passmail.net> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
8948511eeb |
Large PSBs open with their pixels: decode planes past 2 GiB, plus 4-5 GB PSB fixtures (#375) (#782)
* PSD: decode planes up to 8 GiB, so large PSBs keep their pixels (#375) MAX_DECODED_BYTES capped a single decode at 2 GiB. Real PSBs go past that in one plane set: the merged image of a 30000x30000 RGB file is 2.7 GB, a 33000x33000 16-bit layer channel 2.2 GB. Opening such a file gave a document with no layers (flattened files) or layers whose channels were "treated as empty" (layered files). Saving hit the same cap on the encode side and silently fell back to uncompressed data. The cap is now 8 GiB on 64-bit targets and 2 GiB elsewhere, the budget photocraft-codecs already uses for Limits::max_alloc. It stays a backstop: the decoders bound their output by their input on their own (RLE expands at most 64x, Raw needs every byte present). The Raw and RLE output buffers are reserved with try_reserve_exact, so a machine without that much memory gets an error instead of an abort. generated with Claude Code * PSD import: a channel that can't be decoded leaves its layer empty (#375) The warning says the channel is "treated as empty", but the importer filled a missing transparency channel with opaque alpha, so a layer whose channels all failed became an opaque black rectangle over the document. A channel that is present but undecodable now leaves the whole layer empty (transparent), as the warning says. tests/large_psb.rs: the damaged-channel case (PSD and PSB), and an opt-in release test that opens a flattened 29000x29000 PSB whose merged image decodes to 2.52 GB (16 s, 11.6 GB peak here). generated with Claude Code * Large-file fixtures: generate and check 4-5 GB PSBs (#375, #216) photocraft-psd example big_psb writes five PSBs straight from the spec, independent of this crate's writer and its limits: flattened and layered, raw (4.4 and 4.8 GB files, data past 4 GiB) and RLE (small files whose planes decode past 2 GiB, up to a 33000x33000 16-bit layer). Every sample follows one formula, so photocraft-io example big_psb_check opens a file through photocraft_io::import and checks pixels without a reference; with SAVE set it saves as PSB and checks the re-opened file. Release build, this machine: all five open with correct pixels and survive a PSB save and re-open (5-14 s per import). Before the 8 GiB cap, three of the five opened empty or black. Scorecard FILE-216-2 missing -> partial: no 2 GB PSD, 150-layer or 20 MP fixture yet, and nothing runs in CI. generated with Claude Code * psd: ZIP channels reserve fallibly and read in bounded chunks A small ZIP channel declaring a huge decoded size could make zip_decompress try a multi-GiB with_capacity/read_to_end and abort on allocation failure. Reserve with try_reserve_exact/try_reserve, read through take(expected) in 1 MiB chunks, and report LimitExceeded instead. Tests: absurd declared sizes, past MAX_DECODED_BYTES, garbage. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Brandon Thomas <echelon@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
4b3da49486 |
Camera Raw: read and write settings as a live PSD Smart Filter (part of #215) (#459)
* Camera Raw: read and write settings as a live PSD Smart Filter (part of #215) - Map Camera Raw 18.4 (process 5/6) filter settings to and from PSD: custom white balance, Light, Presence, parametric and four point curves, HSL, Color Grading, sharpening, noise, grain and numeric post-crop vignette controls. - Keep the original Photoshop descriptor; edits patch only changed fields. Unknown settings or versions stay an opaque filter, preserved byte for byte. - PSD stores integer slider levels: export rounds continuous PhotoCraft values (hue 360 -> 0) instead of dropping the filter. - Tests: field mapping, patching, opaque fallback, malformed input, rounding, and 8/16/32-bit PSD -> edit -> .pcraft -> PSD with mask, filter order and undo/redo. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Camera Raw PSD: use as_chunks for curve pairs (clippy 1.99) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Camera Raw PSD: no product names in Rust comments, strings or test names Only the descriptor class key itself keeps its literal name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
bd632e7eb3 |
Layered TIFF: open and save Photoshop layer data in TIFF files, in either byte order (part of #215) (#672)
* Layered TIFF: open and save Photoshop layer data in TIFF files, in either byte order (part of #215) Photoshop keeps a layered TIFF's layers in two private tags: 37724 (ImageSourceData, the same layer section a PSD has) and 34377 (the image resources). PhotoCraft now reads and writes both, so a TIFF opens with its groups, masks, layer styles, type, shapes, smart objects and adjustment layers exactly as the PSD path would, and saves them back. * photocraft-psd: a `tiff` module with `ImageSourceData` (parse/write, PSD- and PSB-sized), the 34377 resource helpers, and a field-by-field byte-order transcoder. In Intel-order TIFFs Photoshop byte-swaps the whole block structure (reversed keys, little-endian integers and doubles) while pixel samples stay big-endian; the transcoder knows the layer-info structure, masks, blending ranges and every block photocraft-io maps (descriptors, TySh, lrFX, the binary adjustments, paths, patterns, filter effects, metadata settings, linked layers, annotations...), recognizes descriptor-shaped unknown blocks, and drops the rest with a warning instead of passing byte-swapped data to other readers. The reader gained an optional little-endian mode. 23 unit tests; a fuzz target in the CI matrix. * photocraft-codecs: the two tags travel as opaque bytes on `Metadata`, read by a bounds-checked walk of IFD0 (classic and BigTIFF, both byte orders) and written back as BYTE / UNDEFINED tags. `tiff_writes_little_endian()` tells which order the encoder produces. * photocraft-io: `tiff_layers` rebuilds a PSD model from the TIFF's pixels plus the tags on import, and on export runs the PSD exporter (new `merged_matte` / `merged_compression` options), interleaves its merged planes for the TIFF image and stores the layer section in the encoder's byte order. `ExportOptions.tiff_layers` (default on). Lab and Multichannel documents save flat; extra alpha channels are not written (one transparency sample). * UI: the TIFF Options prompt ("Including layers will increase file size." / "Discard Layers and Save a Copy" / "Don't show again") behind Preferences > File Handling > Ask Before Saving Layered TIFF Files; `ExportSettings.tiff_layers`; catalogs updated. * Tests: io round trips at 8/16/32 bits in Gray/RGB/CMYK, equivalence with the PSD path, every TIFF compression, the flat fall-backs, malformed input, and files written by psdtags in both byte orders (fixtures from scripts/layered_tiff_fixtures.py); a corpus test (feature `corpus`) writes every corpus PSD as a layered TIFF and compares the result with the PSD round trip (700 files, 0 differences, 0 crashes). Codecs tests cover the tag walk on hand-made files. * Scorecard FILE-215-7 done, BUG-204-3 partial; README and book updated. Co-Authored-By: Claude Mythos 5.1 <noreply@anthropic.com> * Layered TIFF: de and pt-br strings; neutral wording in the psd tiff docs The four psdtags fixtures stay out of the repository (no binary test data here): the generator now writes them to the gitignored corpus/layered-tiff/, and the tests that read them skip with a message when absent. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Regenerate docs/scorecard.md Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: leon <claude.udder842@passmail.net> Co-authored-by: Claude Mythos 5.1 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
cc5de414dc |
Camera Raw: interactive histogram, clipping warnings and scopes (part of #215) (#407)
* Camera Raw: interactive histogram, clipping warnings and scopes (part of #215) - RGB histogram of the preview proxy (algo::histogram), drawn as RGB ribbons; Before/After, five draggable tonal zones, shadow/highlight clipping warnings (U/O) and Alt-drag channel diagnostics. - Pixel hover readout, up to nine RGB/Lab colour samplers (S), an ICC-managed vectorscope (algo::vectorscope) with skin-tone line, and a floating scope panel. - Settings regrouped as Light, Color, Effects, Curve, Color Mixer, Color Grading and Detail; Camera Raw labels translated through the cameraRaw context. - One point-curve editor (point_curve.rs) shared by Camera Raw, the Curves dialog and adjustment-layer Properties; fixes Camera Raw curve points not dragging. - Double-clicking a Camera Raw smart filter reopens the Camera Raw dialog with its settings and updates that filter, instead of the generic parameter form. - New point curves are validated (2-16 points, increasing inputs); stored Smart Filters keep loading. - camera_raw_bench for P31/P36 (not enforced until a baseline includes them). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Histogram tests: as_chunks for triangle indices (clippy 1.99) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Regenerate docs/scorecard.md after rebasing onto main Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * i18n tests: close the Mixer Brush test before the Camera Raw one (rebase) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
eaf92dcd9d | Smart Objects and Smart Filters survive PSD save and open (#288) (#288) | ||
|
|
0f955e6c82 | Scorecard: cargo xtask perf + scorecard, budgets, checklists, nightly perf (#221) (#239) (#239) |