Every undoable edit keeps the previous project for undo, so the comp it touches is shared and Arc::make_mut deep-copies it: every layer and every keyframe. An edit therefore costs time in proportion to the comp's size, and a script that sets many keyframes in a big comp goes quadratic (a 1,600-layer build with ~100k sampled keys took ~45 minutes). Comp::layers is now a Layers collection backed by Vec<Arc<Layer>>: copying the stack copies pointers, and only the layer an edit changes is copied. The API mirrors Vec<Layer>; serialized form is unchanged (a list of layers). Comp::layer_mut and the iter_mut().filter/find call sites use find_mut / iter_mut_where so they copy only the layers they actually change. - prop.addKey in a 400-layer comp: 1.36 ms -> 0.16 ms per key (flat in comp size) - the 1,600-layer script build: ~45 min -> 47 s, frames pixel-identical - regression tests: an edit leaves the other layers shared with undo; layers round-trip as a plain list Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VkpGcKfWyCudfSsQNiWAaG
effectcraft-psd
Reads Photoshop documents (.psd, and .psb large documents) for File ▸ Import (as footage, as a
composition, or as a composition with layer sizes retained), and writes a minimal subset (used to
generate test fixtures).
Implemented from Adobe's public Adobe Photoshop File Formats Specification (November 2019
edition): file header, colour mode data, image resources, layer and mask information (layer
records, channel image data, layer mask data, additional layer information: luni, lsct/lsdk,
lyid, iOpa, TySh, lfx2, SoCo, nvrt, brit, hue2, levl, expA, vmsk/vsms,
Lr16/Lr32, smart objects: SoLd/SoLE/PlLd placement and lnk2/lnkD/lnk3/lnkE
linked layer data with embedded files), the descriptor (action) structure, and the merged image data. Raw, RLE (PackBits)
and ZIP (with and without prediction, inflated with miniz_oxide) compression; 1/8/16/32-bit;
Bitmap, Grayscale, Indexed, RGB, CMYK (naive conversion) and Lab colour modes.
Text engine data (EngineData) is read best-effort for the font, size, fill colour and
justification of the first style run. Smart objects import as footage of their embedded file
(a Photoshop document's merged image, or an image) placed by the smart object's corner quad
(position, scale, rotation). A non-affine quad (nonAffineTransform: perspective) or a placed
layer warp (warp: the named styles with bend and horizontal / vertical distortion, and custom
envelope warps whose quilt mesh of bicubic Bezier patches is read from meshPoints (ObAr)
and quiltSliceX / quiltSliceY) is baked as placed over the placed bounds, at up to 4x the
document resolution (placed). Custom meshes are evaluated exactly; the
named styles' curves are not published, so they are clean-room geometric definitions matching
each style by behaviour. Externally linked files
fall back to the layer's rendered pixels. No Adobe code, sample files or assets were used; test
fixtures are generated in-test by the writer in this crate (write).