111 lines
5.5 KiB
Markdown
111 lines
5.5 KiB
Markdown
|
|
# 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í")
|