# PSD Layer Style (fx) Test Files — Handoff ## Goal Otomatik PSD layer style test pipeline: her efekt tipi için doğru formatta PSD dosyası → Photopea referans render → engine çıktısı ile karşılaştırma. ## Output 16 test PSD dosyası `/home/hc/Pictures/_psd_stil_test/` altında: - `fx_baseline.psd` — efektsiz base - `fx_drop_shadow.psd`, `fx_inner_shadow.psd`, `fx_outer_glow.psd`, `fx_inner_glow.psd` - `fx_bevel_inner.psd`, `fx_bevel_outer.psd`, `fx_emboss.psd`, `fx_pillow_emboss.psd` - `fx_satin.psd`, `fx_color_overlay.psd`, `fx_gradient_overlay.psd`, `fx_pattern_overlay.psd` - `fx_stroke_inside.psd`, `fx_stroke_center.psd`, `fx_stroke_outside.psd` ## Changes Made ### `src/psd_saver.rs` — lfx2 descriptor writer (replaces lrFX) - **Tag**: `"lrFX"` → `"lfx2"` (line 188) - **`write_lfx2_block()`**: Writes lfx2 descriptor format matching Photopea output: - Header: `[ver=0 u32][dver=16 u32]` - `_read_body`: name(U+0000), classID('null'), count(2+n_effects) - Fixed items: `Scl`(UntF #Prc 100), `masterFXSwitch`(bool true) - Per-effect: Objc with effect-specific fields (see effect field lists below) - **Effect descriptors** (`write_fx_descriptor`): - `DrSh` (DropShadow): 14 fields — enab, Md, Opct, Clr, uglg, lagl, Dstn, Ckmt, blur, Nose, AntA, TrnS, layerConceals, Cntn - `IrSh` (InnerShadow): 14 fields (same as DrSh) - `OrGl` (OuterGlow): 10 fields — enab, Md, Opct, Clr, GlwT, blur, Ckmt, AntA, TrnS, Nose - `IrGl` (InnerGlow): 10 fields — enab, Md, Opct, Clr, GlwS, blur, Ckmt, AntA, TrnS, Nose - `ebbl` (BevelEmboss): 29 fields — enab, hglM, hglC, hglO, sdwM, sdwC, sdwO, bvlT, bvlS, uglg, lagl, Lald, srgR, blur, bvlD, TrnS, antialiasGloss, Sftn, useShape, MpgS, AntA, Inpr, useTexture, InvT, Algn, Scl, textureDepth, Ptrn, phase - `lagl` (Satin): 10 fields — enab, Md, Opct, Clr, lagl, Dstn, blur, MpgS, Invr, AntA - `sofi` (ColorOverlay): 5 fields — enab, Md, Opct, Clr, Cntn - `GrFl` (GradientOverlay): 10 fields — enab, Md, Opct, Angl, Type, Rvrs, Algn, Scl, Ofst, Grad(Grdn) - `PtFl` (PatternOverlay): 5 fields + Ptrn Objc with Nm, Idnt, phase - `FrFX` (Stroke): 8 fields — enab, Styl, PntT, Md, Opct, Sz, Clr (+ TrnS if not Color fill) ### Descriptor helper functions - `w_key4(buf, &[u8; 4])` — 4-byte OSType key (length prefix = 0) - `w_key_var(buf, &[u8])` — variable-length key (length prefix = N) - `w_ustr_null(buf)` — Unicode name: length=1, U+0000 - `w_ustr(buf, &str)` — Unicode name: length=N, UTF-16BE bytes - `w_class_id(buf, &[u8; 4])` — classID with length=0 prefix - `w_bool(buf, bool)`, `w_enum(buf, type, val)`, `w_untf(buf, unit, f64)`, `w_doub(buf, f64)`, `w_long(buf, i32)` - `w_objc_start(buf)` — writes "Objc" tag - `w_objc_body_start(buf, class_id, count)` — _read_body: name(U+0000) + classID + count - `w_rgbc_color(buf, &[u8; 4])` — writes RGBC Objc with Rd/Grn/Bl doub - `w_default_contour(buf)` — standard linear contour (TrnS/MpgS) - `w_contour_with_key(buf, key)` — key + default contour ### `write_shmd_block()` — Photopea sheet metadata - Structure: `[ver=1][8BIMcmls][sub_size=0][desc_size][descriptor...]` - Descriptor items: - `LyrI`: long 0 - `layerSettings`: VlLs → Objc null with 6 items: - `compList`: VlLs[long 0] - `enab`: bool true - `Ofst`: Objc {Hrzn long 0, Vrtc long 0} - `FXRefPoint`: Objc {Hrzn long 0, Vrtc long 0} - `blendOptions`: Objc {Md enum Nrml, Opct UntF 100, fillOpacity UntF 100} - `Lefx`: Objc — FULL effect descriptor copy (same as lfx2: Scl + masterFXSwitch + effects) - Total: ~1038 bytes per single-effect layer ### Layer extra data additions - **Blending ranges**: 40 bytes → 10 pairs of (u16=0, u16=65535) - **iOpa block**: `8BIMiOpa` size=4, data=`[0xff, 0x00, 0x00, 0x00]` - **brst block**: `8BIMbrst` size=0 - **lfx2 block**: `8BIMlfx2` size=N, descriptor data - **shmd block**: `8BIMshmd` size=M, Photopea metadata ### `examples/generate_style_tests.rs` - Generates all 16 PSD files: 15 single-effect + 1 baseline - 512×512 canvas, asymmetric trapezoidal shape per layer - Baseline has no effects; all others have exactly 1 effect ### Imports - Added `GradientDef` to `use hcie_fx::types::{..., GradientDef}` - Uses `hcie_blend::BlendMode` for blend mode mapping ## Key Format Decisions ### Objc format Photopea Objc descriptor: NO outer wrapper. Direct `_read_body`: ``` "Objc" + name_len(u32) + name(u16×N) + classID_len(u32) + classID + count(u32) + items... ``` where name is always `U+0000` (len=1), and classID is length-prefixed (len=0 → 4-byte key). ### enum format ```"enum" + classID_len(u32=0) + type_id(4B) + classID_len(u32=0) + value(4B)``` ### Nested Objc Contour (TrnS/MpgS), Color (RGBC), Pattern (Ptrn), Gradient (Grdn) all use the same format recursively. ## How to Regenerate ```bash cd /home/hc/Documents/00_PROJECTS/hcie-rust-v4 cargo run -p hcie-io --example generate_style_tests ``` Files go to `/home/hc/Pictures/_psd_stil_test/fx_*.psd`. ## Verification - All 15 effect files have: lfx2 + shmd + iOpa + brst blocks - fx_baseline.psd has no effect blocks (baseline reference) - lfx2 binary format byte-identical in structure to working `hc_drop_shadow.psd` - shmd mirrors the lfx2 descriptor inside `Lefx` section ## Remaining / Known Issues - lfx2 format verified against Photopea output (`hc_drop_shadow.psd`) - Files open in Photopea — effects should render based on shmd+lfx2 metadata - GradientOverlay and PatternOverlay use minimal default gradient/pattern data - BevelEmboss texture (Ptrn) uses hardcoded default UUID - Contour names use ASCII "Linear" (Photopea uses localized names like "Lineární")