The CMYK proptests failed on main now and then (the_split_route_agrees_with_the_exact_path
after #8663). Three gaps, each found by the proptests at 5000 cases:
- cmyk.rs: a clipped Dissolve layer blended like Normal; it now shows each pixel fully or not
at all by the compositor's noise, as the RGB compositor does since #3052.
- cmyk.rs: a Dissolve adjustment layer mixed by its opacity; opacity and mask are now the chance
a pixel takes the adjustment, as on the RGB compositor (#3050).
- native.rs: the display/Flatten ink route didn't round to the document's levels after an
adjustment layer, where the RGB compositor and cmyk.rs do; coverage in a pass-through group
then differed by up to half a level.
The test reference model had the same two Dissolve gaps (ref_clip passed no position, so
Dissolve fell back to Normal), which is why random_documents_match_the_reference never caught
the first one. Three named regression tests cover the minimal cases.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Crop: Delete Cropped Pixels keeps type and smart objects whole (#7116)
crop_doc cropped every surface for_each_surface visits, including the
rendered caches of type layers and smart objects. Those move by shifting
their stored pixels, so the cut stayed and showed as soon as they were
moved. Like Photoshop, Delete Cropped Pixels now deletes pixel-layer data
(and fill caches) only; shapes are re-rendered as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Address review: Delete Cropped Pixels crops stored video frames again (#4956)
The pixel-layer-only crop path skipped a video layer's stored frames and
original footage, so scrubbing brought the cropped pixels back. Crop them
with the displayed content, as before, and test a shrinking crop.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Address CodeRabbit review: the turned crop keeps type and smart objects whole before rotating
The pass that drops what a turned crop deletes before the rotation still cut
every cached surface. The rotation re-renders type and smart objects, but a
smart object whose source is unavailable keeps its cache, so it stayed clipped.
That pass now uses the same pixel-layer-only rule as the crop itself.
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>
* Auto-Align: a locked layer becomes the reference instead of being moved (#7080)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Address review: Auto-Align moves the Background as a normal layer
With the Background selected and a Cylindrical or Spherical result (every
layer is reprojected), or another layer as the reference, the Background's
own position lock made Auto-Align refuse. Like Free Transform and Photoshop,
the Background's built-in position lock no longer pins it: it becomes a
normal layer ("Layer 0") when it has to move. Lock All, Lock Pixels and a
user's Lock Position on any other layer still keep it as the reference.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Address CodeRabbit review: the Auto-Align params doc names the locked layer as the default reference
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>
* Perspective Crop: the swap button swaps W and H (#7059)
The W/H swap button called crop_size::swap, which only acts in the Crop tool's W x H x Resolution mode; Perspective Crop never sets that mode, so the button did nothing there. The button now always swaps the fields; X keeps the guard.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Address review: the test clicks Perspective Crop's ⇄ button
The test now renders the real options bar with the Perspective Crop tool,
clicks ⇄ through the egui_kittest harness and checks that W and H swapped,
instead of calling `swap_fields` directly. It fails if the button goes back
to calling `swap`.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
`style.presets.new` with `includeBlending: false` and an explicit `opacity` stores only that
opacity, but `apply` with `add: true` skipped the blending block unless the preset had a blend
mode, so the layer kept its opacity. Test: an_opacity_only_style_added_sets_just_the_opacity
(also checks the layer's blend mode and Fill stay).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
b1b6bd248 opened the window whenever the status was Failed after an
error, which includes a Failed left from an earlier check, so a later call
refused for bad params opened it too. It now opens only when this call's
start failed: the status changed to Failed, or the error is the Failed
message (the same start failure again). The test covers bad params after a
failed check, bad params from Idle, and a repeated start failure.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
81bcf7f3d hid the path pointer for any drag, but every Type press starts
one, so pressing on a path showed the I-beam although a click or flat drag
still makes path type on release. While the button is held the pointer is
now what the release makes: nothing once the drag passes the box threshold,
else the path click at the press. pointer_up and the hover share the
threshold (makes_box). The test holds a press on a shape, drags flat (area
type on release) and drags a real box from inside the shape (no pointer).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
When the checker cannot start, help.checkForUpdates sets Status::Failed and
returns the error, and `?` skipped opening the window, so Help > Check for
Updates showed neither the failure nor Check Again. The window now opens
for a failed check (the command error is still returned); bad params open
nothing. Covered by a test with a checker that fails to start.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Dragging out a paragraph box with the Type tool sets app.drag and no type
edit is open yet, so the hover lookup showed the area-type or path-type
pointer over a path although the release makes a box. hover_path_target now
returns nothing during a drag or an open type edit (the edit check moved in
from canvas.rs), and a test drives a drag over a shape.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The Features row said Type 5 / 4 / 1 and "RTL paragraph direction UI
missing"; scorecard/type.toml has 5 done / 5 partial / 0 missing, as the
status line says, and TYPE-218-2 is partial: the direction buttons exist,
the visual RTL caret is missing.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
psd_recovery_generated is a module of the io crate's single `it` test
binary, so `--test psd_recovery_generated` names no target. The comment now
runs `--test it psd_recovery_generated::write_psds_for_manual_gui_test`.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Two places turned an RGB or Lab colour into ink with the built-in coated
CMYK instead of the document's embedded profile: the exact ink path's
tiles (render_tile runs on rayon workers, which never inherit a caller's
with_cmyk_space scope), e.g. a solid fill picked in RGB, and the split
path's stand-ins (fill and effect colours, converted on the calling thread
outside any scope). Both now enter cmyk_space(doc). A test with a synthetic
uncoated profile checks a solid fill and a Color Overlay get the profile's
inks; each half fails on its own without its fix.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The bridge forwards Replace Contents to the app, which runs it on its
active document, so a given `index` was silently dropped and could replace
another document's contents. It is now refused before anything reaches the
app, as bridge previews and saves already do; documented in the schema and
control-protocol.md. A fake-app test checks the indexed call never arrives.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
`index` targets one call (#7025): replace_smart_object now runs through
on_doc like place and the data-set methods, so the previously active
document is active again on success, on error and on the trusted-local path.
The index test covers a successful and a failing indexed replace.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
levels_gamma's shadow toe: for gamma ~1.003..1.03 the join point u1 is a
tiny positive f32 whose u1^2.2 underflows to 0, so b = inf and the toe
gives inf*0 = NaN at u = 0 (any 8/16-bit Levels channel at black). The
toe vanishes as gamma -> 1, so the power curve is its limit: use it when
b is not finite. Found by cmyk random_documents_match_the_reference
(proptest, unseeded; the shrunk case is kept in proptest-regressions);
the bug is on main since #5143, not from this batch.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
#5598 made File > Generate > Image Assets require Preferences > Plug-ins >
Generator, so #8254's two artboard tests got Disabled. Enable it first,
as #5598's own test does.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- io tests/it/psd_structure.rs: main's Duotone test compares the raw
block as a slice now that #5815 shares it as Arc<[u8]>.
- ru and fr catalogs: main and #6658 both added "Continue" with the same
translation; the second copy goes (bundled_catalogs_are_consistent).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Main's #8644 builds each crate's integration tests as one binary; #3356's
cmyk_export.rs and #5769's cylinder_warp.rs move into crates/io/tests/it.
docs/scorecard.md regenerated for the batch (#5485 DIST-220-1, #6055
UI-217-17, preferences).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
After merging main, the Layer Style dialog always sends From/To as 8-bit
hex (#7072). On a baked Gradient Overlay without a source record (PSD or
.asl import) the custom and no-record paths wrote those hex ends back,
so OK without a change rounded the curve's ends (a Smooth bake's white
end 0.99999994 became 1.0, Lab ends became RGB). Like edit_endpoint for
the recorded source, a hex end that only repeats the current end is now
left out. baked_overlay_is_unchanged_until_method_selection_then_edits_
the_whole_curve fails without this.
Co-authored-by: oRE-o <42003350+oRE-o@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
After merging main, the Type tool's path target fell back to the Work
Path over its whole inside, while main's #6551 only draws an unselected
Work Path for path tools. Ctrl-click Load as Selection (or deselecting
the Work Path) and a Type tool click inside it started area type in a
path nobody could see. Now the Work Path's inside counts only while it
is selected in Paths (drawn), its outline still takes type on a path
with main's 6 px band, and a missed shape path falls back to the Work
Path as on main. an_unselected_work_path_is_not_an_area_target fails
without this.
Co-authored-by: SillySilk <31193727+SillySilk@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- crates/ui-egui/src/layer_style.rs: values_of keeps the PR's method/smoothness keys and source-stop ends, and main's (#7072) unconditional From/To and geometry fields.
- crates/compose/src/cmyk_tests.rs (no textual conflict): add the PR's new `interpolation: None` field to main's CMYK test GradientOverlay literal.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
crates/io/src/lib.rs: kept main's ImportOptions; import_for_open/_with and import_stages now take &ImportOptions (plus the PR's damaged flag); import_with_options_and_interrupt delegates to import_for_open_with.
crates/engine/src/jobs.rs: open job decodes via import_for_open_with(&opts) so the damage flag reaches the held-open path; main's raw_defaults and docId kept.
crates/ui-egui/src/lib.rs: open_bytes_from uses main's import_options + raw_defaults and the PR's PSD damage check via import_for_open(&opts).
Also: moved crates/io/tests/psd_recovery_generated.rs to tests/it/ (mod added, support path fixed) and adapted PR tests to ImportOptions.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Conflicts:
- crates/ui-egui/src/i18n/{cs,de,el,es,fr,hu,id,it,ja,ko,nl,pl,pt-br,ru,sr,tr,uk,vi,zh-hans,zh-hant}.tsv: kept every key of both sides (main's block, then the PR's Color panel block); no duplicate keys.
- crates/ui-egui/src/panels.rs: took the PR's removal of color_picker/hsb_sliders/rgb_sliders (moved to color_panel_ui.rs); kept main's color_field_height doc comment; main's #5842 slider recolour tests ported to color_panel_tests.rs for the new HSB/RGB slider modes; main's black-HSB test dropped as the PR's hsb_sliders_take_values_on_black covers it.
- crates/ui-egui/src/color_panel_ui.rs (no textual conflict, adapted): set_color now goes through apply_color::foreground_changed with the gesture (was type_tool::foreground_changed), so every mode (sliders, ramp, alerts, wheel) recolours selected type/shapes tool-gated in one undo step per gesture; panel() calls apply_color::track_focus.
- crates/ui-egui/src/color_panel_tests.rs (adapted): new every_slider_mode_recolours_the_selected_shape test.
- docs/gaps.md, docs/ui-parity.md: union of both sides' status lines, rows and revision history; the PR's done row replaces the old "missing" Color panel row.
- docs/scorecard.md: generated, took b7-base's version.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- crates/engine/src/tests.rs: kept both tests (main's restore_layer_selection test and the PR's history_states_share_large_document_payloads).
- crates/io/src/psd_import.rs: kept main's parsed Duotone inks (duotone_map) with the PR's Arc<[u8]> psd_raw; also main's unknown-adjustment-key import (#6575) now builds Arc<[u8]> raw.
- crates/io/tests/adjust_warnings.rs: moved to tests/it/adjust_warnings.rs on main; PR's .into() edits applied there, plus #6322's Color Lookup test (raw.as_slice().into()).
- docs/architecture.md: union; status line names both changes, both revision-history rows kept.
- Adapted main-new sites to the PR's types: psd_export.rs (Duotone write-back via as_deref/to_vec, Unsupported block compare by slice), inspect.rs and ui-egui unsupported_marker_tests.rs (raw .into()), io tests/it/psd_unknown_adjustment.rs (slice compare).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Conflicts resolved:
- crates/ui-egui/src/i18n/hu.tsv: kept base's Bevel & Emboss keys, then the PR's update-check keys
- crates/ui-egui/src/i18n/ru.tsv: kept base's Bevel & Emboss keys, then the PR's update-check keys
- crates/ui-egui/src/i18n/sr.tsv: kept base's added sections, then the PR's update-check keys
- crates/ui-egui/src/i18n/tr.tsv: kept base's Bevel & Emboss keys, then the PR's update-check keys
- crates/ui-egui/src/menus.rs: kept base's saved-workspace entries, then the PR's Help › Check for Updates reordering
- docs/gaps.md: base status line with the PR's G17 clause prepended; PR revision row above base's rows
- docs/target-app-parity.md: base status line (updated 2026-10-22) with the PR's clause prepended; PR revision row above base's rows
- docs/scorecard.md: generated, took b7-base's version
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Conflicts:
- crates/ui-egui/src/panels.rs: took b7-base's file (keeps align_bar in the Move bar and all other main changes) and re-applied the PR's change: dropped the six `if t.pro` guards (Marquee, Lasso, Paint Bucket, Perspective Crop, Crop, Move) and deleted the eight non-Pro hint/fallback arms (incl. the Studio Move arm, now covered by the Pro arm which already has Auto-Select, Show Transform Controls and align_bar); rustfmt reindent.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Main now builds each crate's integration tests as one binary (crates/ui-egui/tests/it). The PR's
tests/eyedropper_ring.rs moves to tests/it/eyedropper_ring.rs behind `mod eyedropper_ring;`,
and both wgpu tests take photocraft_testkit::gpu_lock() like their neighbours.
ring_reads_over_a_flat_area_of_its_own_colour still fails with main's canvas.rs and passes with the PR's.
Co-authored-by: mroxso <24775431+mroxso@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
When the file on disk is already version 2 (another instance saved the
migrated "on"), the save base took this instance's current value, so
turning the switch off here before the first save matched the base and
was never written; the file kept "on". The base now holds the migrated
value (on), so only a change made here since loading is written.
Test: turning_the_shift_tool_switch_off_is_saved_over_another_instances_migration
(fails with the previous base: true kept instead of false).
Co-authored-by: Higgs32584 <55243596+Higgs32584@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Photoshop constrains a Shift-mid-drag selection move to multiples of 45°
("Move a selection border"), and the Move tool's drag of selected pixels
already uses TRANSFORM_DIRECTIONS (8). selection_drag_to used
MOVE_DIRECTIONS (4), so a near-diagonal Shift drag (delta 13,11) snapped
to the horizontal axis (13,0) instead of 45° (12,12).
Test: shift_constrains_a_selection_drag_to_an_axis gains a diagonal case
(fails with 4 directions); the floating-drag test moves to (34,22) so it
stays an axis case with 8 directions.
Co-authored-by: mroxso <24775431+mroxso@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The Apply Image dialog sends Channels-panel names in sourceChannel ("RGB" by
default, "Red", "Alpha 1"), but the export matched only lowercase keys, so a
dialog-recorded Apply Image failed strict .atn export. Channel names now map
case-insensitively (RGB/CMYK/Lab to the composite), an alpha channel is written
as a Chnl name reference and read back on import, and a numeric layer or channel
selector is refused instead of becoming the merged composite.
Tests: apply_image_dialog_channel_names_survive_a_strict_export (the dialog's
exact params) and apply_image_numeric_source_selectors_are_refused; both fail
without the fix.
Co-authored-by: VishnuByrraju <112673081+VishnuByrraju@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The two pixel/checker tests wrapped all of harness() in catch_unwind, so a
failing fixture command or a panic in the first 12 frames was reported as
"no offscreen graphics adapter" and the test passed without checking
anything (CodeRabbit thread on line 107). harness() now runs the fixture
commands first, catches only the wgpu renderer creation (the one step that
panics without an adapter) and builds and steps the harness outside it.
fixture_failures_are_not_skipped_as_a_missing_adapter feeds a 0x0 document:
with the old catch-all it was swallowed and the test fails; now it panics.
Co-authored-by: hogawish <60434858+hogawish@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The parallel build skipped a non-map pipeline whose creation raised an
error, so with new_with_format (errors logged, compositor still returned)
its key was missing from kit.pipelines and the first draw panicked on the
`self.kit.pipelines[&(kernel, acc)]` index. As before the PR, a failed
non-map pipeline now keeps its invalid handle (draws report GPU errors);
only a failed effect-map pipeline is skipped (all maps go to the CPU).
Test: a_compositor_with_failed_pipelines_draws_without_panicking builds
new_with_format(Rgb9e5Ufloat) on a device with an uncaptured-error
handler and renders: it panicked at the index without the fix. The PR's
test moves into tests/it (one test binary) and takes GPU_LOCK.
Co-authored-by: 00xJS <56056764+00xJS@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The PR gives an Outside stroke no share beneath fully covered pixels on the
CPU, but no test failed without it: under a Normal layer the opaque pixel hides
the stroke anyway. It shows with Blend Interior Effects as Group on a Multiply
layer: main multiplied the red interior with the blue stroke beneath it (black).
New test outside_stroke_under_multiply_layer_leaves_interior_to_backdrop
(raster and filled shape) fails on main's effects.rs, passes with the PR.
The GPU compositor mirrored the old math (outline_share 1 at full coverage, and
1 under an opaque pixel layer); compose.wgsl now matches the CPU. Blend Interior
Effects as Group itself renders on the CPU, so on the GPU this is consistency;
a parity case pins non-Normal layers with an Outside stroke.
Co-authored-by: seotarek <30214297+seotarek@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The PR's test only called blend_label/blend_ps. This adds one through the
import entry point: a Set Lyr step with Md enm("BlnM","softLight") and a
Make layer step with Usng Md "luminosity", written with atn::write and
imported, must give layer.setProps blend "Soft Light" and layer.new.layer
mode "Luminosity" with no unsupported steps. Without the PR's table rows
the Set step is unsupported (and the Make falls back to Normal).
Co-authored-by: justinjohn0306 <34035011+justinjohn0306@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Two instances migrate the same version 1 file (useShiftKeyForToolSwitch
false -> true), so both save bases hold false. When B turns the switch off
and saves version 2, A's next unrelated save wrote its migrated true over
that choice. write_prefs now drops the migration delta from the base when
the file on disk is already version 2 or later.
Test: another_instances_shift_tool_switch_choice_beats_this_ones_migration
(fails without the fix: true written over false).
Co-authored-by: Higgs32584 <55243596+Higgs32584@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The rpc test only checked the returned layer id, so a no-op that echoed
{"layer": id} passed, its undo check passed on doc.place's own step, and
the bad-layer case asserted on an empty reason. It now checks the smart
object's file name and rendered centre pixel (42 -> 200 after the
replace, back to 42 on undo, 200 on redo), that every refusal changes
nothing, and that layer 999999 fails with "no such layer".
The bridge row in docs/control-protocol.md claimed doc_replace_smart_object
maps onto layer.smartObjects.replaceContents; the desktop refuses that
command (ambient path), like doc_place's file.placeEmbedded. The row now
says so and points to the headless / workspace server method.
Co-authored-by: VishnuByrraju <112673081+VishnuByrraju@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
round_i32 added 0.5 in f32, which rounds up at the binade crossing:
0.49999997 (0.5 - 2^-25) + 0.5 becomes 1.0, so round_i32 gave 1 (and -1 for
the negative) where f32::round gives 0. The sum is now taken in f64, where
it is exact for every f32 the i32 cast does not saturate; still no roundf call.
rounding_matches_the_standard_library now covers +-0.49999997, the ties and
8388607.5, and a new test compares every f32 bit pattern in [-4096, 4096]
against f32::round (0.6 s). Both fail on the previous head. The code comment
and docs/oil-paint-performance.md no longer claim the f32 sum is exact.
Co-authored-by: ebosido444-ai <316167330+ebosido444-ai@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
refine_legacy_point_origin moved the layout's logical box (line x0,
baseline - ascent) onto the TySh boundingBox, but Photoshop writes
boundingBox as the tight ink box (Arial 120 pt "..." has bounds top
-102.98, boundingBox top -11). Legacy point text whose ink starts below
the ascent line ("...", "_", lowercase) opened up to ~116 pt too low.
Match like with like: use the layout's unrounded text-space ink bounds
(new render::ink_bounds). The PR's tests built boundingBox from the
logical box, so they now write bounds = layout box and boundingBox =
ink box and pin the exact anchor; legacy_origin_lines_up_ink_with_the_
ink_bounding_box covers "Name", "..." and "_" and fails without this.
Co-authored-by: bogusyogi <102761952+bogusyogi@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The save base after loading kept only the keys the file had, so a key an
older file lacks was written as this instance's default over another
instance's change (two instances on one v1 file: B sets the theme, A saves
anything, the theme reverts). A version 3 file was also written back as
version 2. The base is now what was loaded, with only the version and
tools.useShiftKeyForToolSwitch put back as the file had them (a newer
version is left alone), the way repair_invalid patches keys.
Tests: an_old_sparse_file_keeps_another_instances_change,
a_newer_files_version_is_kept_on_save (both fail with the previous base).
Co-authored-by: Higgs32584 <55243596+Higgs32584@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The PR widened the eye hit area to the whole column (#8467) but also painted
a 12x12 sunken well in a hidden layer's eye box. The maintainer decided on
#1662 that a hidden layer's eye box stays empty, as in Photoshop (and the
hidden fx-row eye still does), so the well goes; the full-column hit area and
its test stay. New test a_hidden_layers_eye_box_paints_nothing checks, in all
five themes, that nothing is drawn inside a hidden layer's eye column while a
visible layer's eye is; it fails with the well. Also drops the trailing blank
line at EOF of layer_row_ui/tests.rs (cargo fmt).
Co-authored-by: kobaltgit <159009675+kobaltgit@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A compression-7 frame may declare up to four times its segment's samples
(ljpeg::decode is capped at 4x the tile), and read_plane returned the whole
frame, so each retained tile held up to 4x what retained_segment_bytes
charges it: tiles declared 256x256 holding 512x512 frames pass a 1 MiB
budget while keeping 2 MiB. The lossless-JPEG arm moves into
lossless_jpeg(), which now truncates to the segment's rows (all the scatter
reads) and releases the spare capacity.
The PR's test only called the helper; deleting the call in read_plane kept
it green. decode_charges_the_retained_tiles_together drives decode on an
18x18 CFA in four padded 16x16 LJ92 tiles: 1500 B -> LimitExceeded, 2048 B
-> Ok. lossless_jpeg_keeps_only_the_segment pins the truncation. The doc
comment now says the output plane is alive alongside the sum.
Co-authored-by: Muhammad Saad <188063027+saad-mughal435@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>