mirror of
https://github.com/storytold/photocraft.git
synced 2026-10-27 03:17:05 -06:00
Photoshop's Background is the bottom layer of a file without transparency: a pixel layer that is
transparency- and position-locked, whatever the file's language calls it ("Hintergrund", "背景",
"Arrière-plan"). We matched the name "Background" in compose (deep / shallow knockout), the
engine (cut/clear, merge visible, transforms, warps, distort, erasers, styles, masks, crops,
straighten, smart-object unpack) and the UI (Properties, layer row, tab title), so a localized
file's deep knockout revealed transparency instead of the Background.
- `Document::background` / `background_mut` / `is_background` and `Layer::has_background_traits`
in the doc model replace every name check; `extra_cmds::background_to_layer` is the one
"Background → Layer 0" conversion.
- PSD import marks the bottom record position-locked when it is transparency-protected and has
no transparency channel, with or without an `lspf` block. Affinity import and HDR Toning now
lock the Background's position like every other place that creates one.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
40 lines
1.7 KiB
Rust
40 lines
1.7 KiB
Rust
//! A PSD's Background is recognised by its structure (the bottom record, transparency-protected,
|
|
//! stored without a transparency channel), whatever its name in the Photoshop language that
|
|
//! saved the file.
|
|
|
|
use photocraft_io::psd_to_document;
|
|
use photocraft_psd::{ColorMode, Compression, PsdFile, TaggedBlock, Version, testgen};
|
|
|
|
/// `testgen::layered` with its bottom layer turned into a Photoshop Background named `name`.
|
|
fn file(name: &str, transparency_channel: bool) -> PsdFile {
|
|
let mut f = testgen::layered(Version::Psd, ColorMode::Rgb, 8, Compression::Rle);
|
|
let bottom = f.layers_mut().first_mut().unwrap();
|
|
bottom.name = name.bytes().filter(u8::is_ascii).collect();
|
|
bottom.blocks.retain(|b| &b.key != b"luni" && &b.key != b"lspf");
|
|
bottom.blocks.push(TaggedBlock::unicode_name(name));
|
|
bottom.flags.set_transparency_protected(true);
|
|
if !transparency_channel {
|
|
bottom.channels.retain(|c| c.id != -1);
|
|
}
|
|
f
|
|
}
|
|
|
|
#[test]
|
|
fn a_localized_background_is_the_background() {
|
|
for name in ["Hintergrund", "背景", "Arrière-plan"] {
|
|
let (d, _) = psd_to_document(&file(name, false));
|
|
let bg = d.background().unwrap_or_else(|| panic!("{name}: no Background"));
|
|
assert_eq!(bg.name, name);
|
|
assert!(bg.locks.transparency && bg.locks.position, "{name}");
|
|
assert_eq!(bg.id, d.layers[0].id);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn a_protected_layer_with_transparency_is_not_the_background() {
|
|
// Lock Transparent Pixels on an ordinary bottom layer: it keeps its transparency channel.
|
|
let (d, _) = psd_to_document(&file("Background", true));
|
|
assert!(d.layers[0].locks.transparency);
|
|
assert!(d.background().is_none());
|
|
}
|