Commit Graph
263 Commits
Author SHA1 Message Date
David MannandClaude Opus 5.5 0b3cab2eba CMYK: Dissolve and post-adjustment rounding match the RGB compositor (#9033)
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>
2026-10-24 03:34:07 +02:00
David Mann 8c04b175ec Merge remote-tracking branch 'origin/main' into integration-batch-7
# Conflicts:
#	crates/automation/src/rpc.rs
#	crates/automation/src/server.rs
#	crates/gpu/src/health.rs
#	crates/text/src/tests.rs
#	crates/ui-egui/src/i18n/zh-hans.tsv
#	crates/ui-egui/src/panels.rs
#	docs/control-protocol.md
#	docs/ui-parity.md
2026-10-24 00:23:15 +02:00
FinnNkandClaude Opus 5.5 89d127d259 fix(curves): 32-bit Curves continue past 0..1 like Photoshop (#8782)
* fix(curves): 32-bit Curves continue past 0..1 like Photoshop

In a 32-bit RGB document an edited Curves clipped every sample to the
curve's table, so HDR values above 1 (and negative ones) were flattened.
Photoshop extends the curve beyond its end points along its end segments:
the straight curve [[0,0],[255,128]] maps -0.5, 0.5 and 2 to
-0.250980407, 0.250980407 and 1.003921628 (measured in Photoshop 27.11 on
the issue). The CPU compositor, Image > Adjustments and the GPU kernel
now do the same, with the end slopes measured over 16 table steps.
8/16-bit documents and unedited curves are unchanged.

Fixes #7157

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(curves): extend 32-bit curves in RGB documents only

The curve's space is not the document's: a grayscale, CMYK or Lab
32-bit document with an RGB-space curve keeps the table's ends. The
document mode now reaches the decision on the CPU (apply_in, the
compositor) and the GPU (adjustment_program).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 17:40:56 -04:00
HEOJUNFOandClaude Opus 5.5 44db720b22 Black & White: keep 32-bit superwhites (#8537) (#8748)
The grey was clamped to 0..1 at every depth, so a 32-bit (2, 2, 2) came
out as (1, 1, 1). In 32-bit documents it now clamps to 0..16, as
Photoshop 27.11 does; 8 and 16-bit still clip at 1. CPU and GPU both.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-23 17:40:00 -04:00
Gert Pellin - GPE 10a20743ea Soft Light: dark backdrops follow Photoshop's W3C curve (#8804) (#8817)
* fix(blend): Soft Light uses the W3C curve over dark backdrops (#8804)

Photoshop's Soft Light moves the backdrop towards D(cb) in the upper half,
and D(cb) is the cubic ((16·cb − 12)·cb + 4)·cb below a quarter, not
sqrt(cb). With sqrt the Soft Light cell of psd-tools rgb-blend-modes.psd
was up to 20 levels too light over the dark rainbow; with the cubic it is
within 4 of Photoshop's merged image.

Same change in compose.wgsl so the GPU compositor stays at CPU parity.
Krita's own soft_light (the sqrt curve) now opens as the approximation and
svg:soft-light / krita:soft_light_svg as the exact match, without warning.

Fixes #8804

* compose: the see-through Soft Light test expects the W3C curve over a dark backdrop (#8804)

The #6232 test computed Photoshop's Soft Light of yellow as sqrt(b) for red and
green. Its backdrop #04170F is below a quarter in both channels, where the W3C
curve (Photoshop's, measured in #8804) uses ((16b - 12)b + 4)b instead.
2026-10-23 17:39:40 -04:00
aiongg 7aaa59712a Merge main into integration-batch-7 2026-10-23 20:37:05 +00:00
Dylan M. Taylor 6ca5bf359d security(compose): bound dense working sets and nested Blend If evaluation (#8949)
* security(compose): bound dense working sets and repeated group evaluation

Smart-filter mixing and stroke fitting work tile by tile instead of over the
union of sparse surfaces; render_layer admits its rect like render; hidden
layers no longer disable the proxy and uniform-area shortcuts; Blend If
groups nested deeper than two are judged by their result, so nesting costs
polynomial instead of exponential work; effect builds past an absolute byte
cap are skipped.

* CodeRabbit #8949: exact tile skips in filter_mask, allocated-tile scan in fit_stroke, contrasting backdrop in the nested-group test

- filter_mask mix/unmix: bare tiles (no tiles on either side) skip for
  non-alpha formats too when the two surfaces' default pixels are equal;
  differing defaults still get processed, so the clone's default pixel is
  never left wrong under a non-unit mask
- shape_split fit_stroke: scan the union of the three surfaces' allocated
  tiles (exact skip when all three defaults are transparent) instead of
  every tile of a 300k-canvas document; non-transparent defaults fall
  back to the full scan
- tests: the nested-groups assertion now uses a black backdrop so the
  asserted white can only come from rendering through the group chain

fixes CodeRabbit review comments (major/minor) on filter_mask.rs:27,29,
shape_split.rs:126, tests.rs:2354

* compose: skip a BTreeSet in fit_stroke — a sorted Vec compiles under the wasm toolchain (E0599 on ::drain there)
2026-10-23 15:33:01 -04:00
David MannandClaude Opus 5.5 c88691c0b8 Address CodeRabbit review: CMYK ink paths convert colours through the document profile (#3356)
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>
2026-10-23 00:26:54 +02:00
David MannandClaude Opus 5.5 ae8809c029 Integration: Levels gamma just above 1 no longer turns black into NaN (#5143)
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>
2026-10-22 23:40:21 +02:00
David Mann a79d8cb9f6 Merge PR #5937: fix(compose): prevent outside stroke from bleeding into opaque character interior (#5861) 2026-10-22 23:40:19 +02:00
David MannandClaude Opus 5.5 6c7e920d5a Merge PR #7335: Gradient Overlay: add Method selector and retain editable source stops
- 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>
2026-10-22 23:39:14 +02:00
2e29033b50 Integration: no Outside stroke share under opaque pixels on the GPU too (#5937)
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>
2026-10-22 21:41:01 +02:00
David Mann f9efad49d6 Integration: merge main (#5937) 2026-10-22 21:16:38 +02:00
David MannandClaude Opus 5.5 f5533e5d82 Integration: merge main into batch 6
Main merged #5940, #6472, #7838 and #7922 directly. Kept from the batch:
the snapshot/History-follow identity at the history limit (#5940,
c7ef3676a; main still had the 3-tuple HistorySpot) and the strokeless
shape at a 0 px width step (#6472, 962539607; main still added the
stroke). Main already carries the other two fix-ups (34c9d0ea4, dff8bfd0e).

Overlaps with main resolved:
- #5842 vs main's #5568: one path, apply_color, tool-gated as #5842
  has it, with #5568's float fill, kept fill opacity and lock skip;
  shape_fill_ui's duplicate foreground_changed/gesture dropped; #5568's
  tests run with a shape tool.
- #6230 vs main's #5180: one Type tool path click (path_target), no
  second type.attachToPath; vertical type does not start type on a path,
  as main has it; main's path_at/path_cursor and canvas arm dropped.
- #5452 moved the shape swatch to shape_paint_ui.rs: main's hex field
  (#6201) ported there.
- type_extra_cmds::with_text takes main's dpi form, pub(crate) for
  type_area_cmds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 19:20:26 +02:00
b526378de4 Integration: 32-bit Hue/Saturation with Photoshop's saturation factor, finite at lightness 1 (#5781)
The 32-bit branch cut the slider to floor(|s| * 255) / 255, which only matches
Photoshop near 25 and 50 %: measured at every whole percent from -100 to +100
on a 32-bit document, Photoshop lowers by 1 - floor(|s| * 256) / 256 and raises
by 255 / (255 - floor(s * 254)) taken to 1/256 and capped at 32767/256 (201 of
201 exact). +100 gave 512x where Photoshop gives 128x, +90 9.8x for 9.44x.

It also kept the HSL saturation unclipped, which is inf at HSL lightness 1 and
0 and negative above 1: (2, 0, 0), red at +1 stop, turned NaN with any edit,
Hue +40 alone included, where Photoshop gives (2, 1.333, 0). The 32-bit branch
now scales the chroma around the lightness instead (same result in gamut),
on the CPU and in the shader, in whole numbers on both. Colorize clamps its
saturation before the 32-bit scaling, so a PSD's out-of-range value renders
the same on the GPU.

Tests (fail without this): thirty_two_bit_saturation_steps_match_photoshop,
thirty_two_bit_hue_saturation_keeps_bright_colours, the GPU parity
hdr_hue_saturation_bright_colours. Photoshop-oracle floor 188 -> 190 of 258,
measured after merging main.

Co-authored-by: Timothy Daly <100327160+timothydaly1@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 15:42:03 +00:00
f1d947de8a Integration: test that a and b round after the opacity mix (#5771)
The PR's test checks L at full opacity only, so it still passes when a and b are left unrounded or
the rounding moves before the opacity mix, or truncates. New test: Curves taking a 140 to 151.4 at
50% opacity in an 8-bit Lab document must show a 146 (145.7 unrounded, 145.5 rounded before the
mix, 145 truncated: all three fail).

Co-authored-by: timothydaly1 <100327160+timothydaly1@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 15:41:21 +00:00
aiongg 64fb74f1e7 Integration: #8143 the branchless floor test accepts any NaN for NaN inputs
MSVC's floor quiets a signalling NaN while branchless_floor keeps its payload, so the Windows job failed the bitwise compare; every other input still compares bit for bit.
2026-10-22 15:40:11 +00:00
aiongg 4819f5b7d4 Merge PR #8372: Compose: effect maps that don't fit in the effect cache are built over the render rect, not rebuilt whole on every render (#8330) (new head) 2026-10-22 15:40:02 +00:00
aiongg 934eb71d84 Integration: merge main
crates/engine/src/file_cmds.rs: batch keeps the save-point closure (takes the output path) and also copies the session's authorize hook into each scratch session, as main now does.
crates/raw/src/sensor.rs: both sides cap the memory of retained tiles; kept the existing retained_segment_bytes check (checked multiplication, same limit) and dropped main's duplicate inline loop.
2026-10-22 15:39:51 +00:00
aiongg 576ba0e02b Integration: track t12
# Conflicts:
#	contributors/people.toml
#	crates/compose/src/lib.rs
#	crates/compose/src/tests.rs
#	crates/engine/src/print_cmds.rs
#	crates/engine/src/print_cmds/tests.rs
#	docs/target-app-parity.md
2026-10-22 15:38:33 +00:00
aiongg f72c3c2187 Integration: track t08
# Conflicts:
#	crates/ui-egui/src/camera_raw_ui.rs
#	crates/ui-egui/src/jobs_ui.rs
#	crates/ui-egui/src/widgets.rs
#	docs/file-format-parity.md
#	docs/gaps.md
2026-10-22 15:38:23 +00:00
aiongg 446b74a8de Integration: track t07
# Conflicts:
#	crates/ui-egui/src/i18n/cs.tsv
#	crates/ui-egui/src/i18n/de.tsv
#	crates/ui-egui/src/i18n/el.tsv
#	crates/ui-egui/src/i18n/es.tsv
#	crates/ui-egui/src/i18n/fr.tsv
#	crates/ui-egui/src/i18n/hu.tsv
#	crates/ui-egui/src/i18n/id.tsv
#	crates/ui-egui/src/i18n/it.tsv
#	crates/ui-egui/src/i18n/ja.tsv
#	crates/ui-egui/src/i18n/ko.tsv
#	crates/ui-egui/src/i18n/nl.tsv
#	crates/ui-egui/src/i18n/pl.tsv
#	crates/ui-egui/src/i18n/pt-br.tsv
#	crates/ui-egui/src/i18n/ru.tsv
#	crates/ui-egui/src/i18n/sr.tsv
#	crates/ui-egui/src/i18n/tr.tsv
#	crates/ui-egui/src/i18n/uk.tsv
#	crates/ui-egui/src/i18n/vi.tsv
#	crates/ui-egui/src/i18n/zh-hans.tsv
#	crates/ui-egui/src/i18n/zh-hant.tsv
#	crates/ui-egui/src/layer_shortcut_tests.rs
2026-10-22 15:38:21 +00:00
aiongg 0e80156397 Integration: track t16
Resolutions: CLI usage, flag lists and flag parsing carry both --tiff-compression (t15) and
--jpeg-format (t16); file.saveACopy, batch, imageProcessor and layersToFiles param specs carry
both tiffCompression and jpegFormat; file_cmds keeps tiff_compression_param and
jpeg_format_param; atn_cmds keeps flag_set/applies_at_canvas_bounds (t15) and UNHOLDABLE (t16),
edit.transform uses the pixel_target guard (t15) with layer_ref(p)? (t16); ui-egui open paths
build ImportOptions (t16) and apply raw_defaults (t15); docs headers/rows unioned; atn tests keep
both sides; tr.tsv "Keep Using CPU" kept once.
2026-10-22 15:38:13 +00:00
aiongg dc0094fa78 Merge PR #5781: 32-bit Hue/Saturation as the reference app does it: unclipped saturation and Colorize (two rgb32 oracles pass)
crates/io/tests/corpus.rs: kept main's pass_floor 188 (the PR's 187 would lower it; not re-measured here)
docs/file-format-parity.md: kept main's header and PSD/PSB rows; added the PR's change-log row without its floor figure
docs/gaps.md: kept main's header and evidence line; added the PR's change-log row without its floor figure
docs/scorecard.md: ours
2026-10-22 15:37:35 +00:00
aiongg 5ecfa852f4 Merge PR #5771: Lab: adjustment results are whole samples, as the reference app stores them (lab8 curves passes)
crates/io/tests/corpus.rs: kept main's pass_floor 188 (TAKE-FIX: drop the PR's 186 hunk)
docs/file-format-parity.md: kept main's header and PSD/PSB rows; added the PR's change-log row without its floor figure
docs/gaps.md: kept main's header and evidence line; added the PR's change-log row without its floor figure
docs/scorecard.md: ours
2026-10-22 15:37:35 +00:00
Dylan M. Taylor 7016152f22 security(compose): bound dense mask working set
build_mask derives its working area from sparse mask geometry grown by
feather, so two distant changed pixels could span a canvas-sized
rectangle. It then allocated f32 planes (coverage, pixel-mask values,
blur scratch) over that area, gigabytes during even a one-pixel render.

Check the estimated working bytes before the dense allocation and fall
back to the constant default mask above 512 MiB, so pathological
geometry degrades feather detail instead of exhausting memory.

Finding: 8c04c5ef-a5bd-48f4-bfcf-83615bcf11c0 (CWE-770)
2026-10-22 07:10:08 -04:00
Dylan M. Taylor f65832931b security(compose): cap uncached pattern conversion
When a pattern exceeded the preparation cache budget, every lookup still
called Tile::new_in, expanding the whole sparse pattern into RGBA floats
on each output tile (and possibly concurrently). The cache budget only
controlled retained entries, not this conversion work.

Reject patterns whose full RGBA conversion would exceed an absolute
256 MiB cap on the uncached path, so oversized patterns are skipped
instead of re-expanded per tile. The cap is independent of the cache
budget, so small patterns still convert on demand when nothing is cached.

Finding: cc0b2f77-5578-48ed-aba0-1f3beccd21cd (CWE-400)
2026-10-22 07:10:03 -04:00
David MannandClaude Opus 5.5 7a54fa9c77 Integration: merge main into batch 6
Conflicts, both sides kept: main's .act swatches next to the batch's .asl
styles (Open filters, preset extensions), main's Render smart filters next
to the batch's Distort ones (53 mapped), parity doc status lines.
docs/scorecard.md regenerated.

Follow-ups the merge needed: the Distort smart filter readers pass their
enum type to main's stricter enum_name (the type their writers use), and
the CMYK effect tests set main's new StrokeFx overprint and Bevel
gloss_anti_alias.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 12:09:25 +02:00
Yonghyuk Choi dc029b93c5 Merge main into Gradient Overlay method branch 2026-10-22 12:38:20 +09:00
Ian Swope b0ed288292 Compose: the effect cache estimate counts the maps the builder makes (strokes' distance fields, none for overlays)
fx_region_bytes counted the shape plus one map per enabled effect. Strokes
also build an inside distance and one or two outside fields, and overlays
and strokes build no map of their own, so a stroke-heavy document could
stay under the estimate while thrashing, and an overlay-only one could be
clipped though it fits. effects::map_count, next to build_maps_prepared,
counts what it builds; the test checks it equals FxMaps::bytes per layer.
2026-10-21 21:58:39 -05:00
aiongg 754692c45c Integration: #7638 test's Bevel literal sets gloss_anti_alias, a field main added 2026-10-22 02:28:48 +00:00
aiongg d8865fc682 Merge PR #7638: Map the bevel's shading through its gloss contour, not its light (#7360)
crates/compose/src/effects.rs: kept main's par_units row loop in bevel_maps and ported the PR's change into it: k maps shade and the flat-face level through the gloss contour (lut_at), and the closure returns (hi, sh) without the apply_contour pair.
2026-10-22 02:27:06 +00:00
aiongg 569cf07753 Integration: #7204 test's StrokeFx literal sets overprint 2026-10-22 02:24:13 +00:00
aiongg bd4e1267a7 Merge PR #8372: Compose: effect maps that don't fit in the effect cache are built over the render rect, not rebuilt whole on every render (#8330) 2026-10-22 02:23:46 +00:00
aiongg 74a2cd3baf Merge PR #8143: Compose: adjustment layers blend back and round without per-sample calls 2026-10-22 02:23:08 +00:00
aiongg a1274ac043 Merge PR #8135: Compose: tone and hue-range lookups truncate instead of calling floor 2026-10-22 02:23:07 +00:00
aiongg b43ba11beb Merge PR #8109: Compose: Color Balance works out each channel's curve once, not per pixel 2026-10-22 02:23:07 +00:00
aiongg 2b3af0f815 Merge PR #3193: fix(compose): resolve Dissolve coverage across adjustment, effects, a…
Conflicts:
crates/compose/src/tests.rs: appended tests, kept both
crates/gpu/tests/parity.rs: appended tests, kept both
2026-10-22 02:23:06 +00:00
aiongg 7b7628adaa Integration: #7204 outside-stroke paint composites as Normal in Dissolve mode (share carries the speckle; CPU and GPU agree) 2026-10-22 02:22:13 +00:00
aiongg ec663f4981 Merge PR #7204: Layer effects in Dissolve mode speckle, as in the reference app (#6995) 2026-10-22 02:22:05 +00:00
aiongg 250ade19b6 Merge PR #6253: fix(compose): interior effects see the backdrop through a Normal layer's fill 2026-10-22 02:21:59 +00:00
aiongg 22e55823f9 Merge PR #6112: fix(compose): stop effect noise from painting empty blur tails 2026-10-22 02:21:58 +00:00
aiongg f32f882f3a Merge main into integration-10
# Conflicts:
#	apps/photocraft-web/src/web.rs
#	apps/photocraft/src/services.rs
#	crates/engine/src/file_cmds/tests.rs
#	crates/engine/src/float_cmds.rs
#	crates/engine/src/float_cmds/tests.rs
#	xtask/src/corpus.rs
#	xtask/src/corpus_pins.rs
#	xtask/src/main.rs
2026-10-21 21:56:26 +00:00
David Mann c7585e0ee8 Merge main 2026-10-21 23:22:56 +02:00
skylar taylor-barrick 18e2d45420 Merge pull request #6318 from robertoffmoura/fix/floating-selection-move-lag
Fix lag when dragging a floating selection with the Move tool
2026-10-21 14:25:55 -06:00
Ian Swope 0edfe05f2c Compose: effect maps that don't fit in the effect cache are built over the render rect, not rebuilt whole on every render (#8330)
A render needs every layer's effect maps. On a large document whose
ordinary layers' whole-region maps take more than the effect cache budget
together, building one evicted the next one needed, so every render, of
any size, built them all again: 1.8-4.4 s per render on a 9000x6000
document with three full drop-shadowed layers. When they don't fit, local
maps are now built over the render rect padded by twice the effects'
reach, as a shapeless layer's already are (#1909), which gives the same
pixels there.
2026-10-21 13:42:06 -05:00
aiongg d5bf6f0398 Merge PR #7846: Scale layer-effect noise by coverage so it follows the glow 2026-10-21 18:15:10 +00:00
David Mann 1d72d6e17d Merge PR #3356: CMYK documents keep their exact inks: export, PSD merged image and Flatten (follow-up to #4199) 2026-10-21 17:47:55 +02:00
aiongg e027e048fc Merge PR #5847: Shapes: Density and Feather on shape layers (#5846)
# Conflicts:
#	docs/scorecard.md
2026-10-21 15:45:38 +00:00
aiongg 77557d75f1 Merge PR #8229: Integration batch: 8 PRs on main
# Conflicts:
#	crates/engine/src/layer_style.rs
#	crates/io/src/psd_export.rs
#	crates/io/src/smart_map.rs
#	docs/file-format-parity.md
2026-10-21 15:45:07 +00:00