Write standard tagged blocks (lnsr/lyid/clbl/infx/knko/lspf/lclr/fxrp) per layer

Photoshop reads a layer's tagged blocks until it sees a non-8BIM
signature. With only 'luni' the next bytes are the channel data, which PS
mis-parses and the layer pixels come out empty. PS writes the standard
block set, so we now mirror it (verified: blocks list now matches the
reference PSD).
This commit is contained in:
AskaEth committed 2026-08-27 09:06:03 +08:00
1 parent e07ef4acaa
commit 00f2cbe71a
1 file changed
+29 -3
+29 -3
View File
@@ -463,7 +463,29 @@ def channel_blocks(img: Image.Image, workers: int,
return list(zip(ids, enc))
def _layer_record(top, left, bottom, right, name, opacity, visible, channels) -> bytes:
def _standard_blocks(layer_id: int) -> bytes:
"""Standard tagged blocks Photoshop writes for every layer. PS reads
tagged blocks until it hits a non-8BIM signature; with only 'luni' the
next bytes are channel data and PS mis-parses the record, so we write
the usual block set like PS itself does."""
items = [
(b"lnsr", b"\x00\x00\x00\x00"), # name source
(b"lyid", struct.pack(">I", layer_id)), # layer id
(b"clbl", b"\x00\x00\x00\x00"), # blend clipping
(b"infx", b"\x00\x00\x00\x00"), # blend interior
(b"knko", b"\x00\x00\x00\x00"), # knockout
(b"lspf", b"\x00\x00\x00\x00"), # protected
(b"lclr", b"\x00" * 8), # sheet color
(b"fxrp", b"\x00" * 16), # reference point
]
out = bytearray()
for key, payload in items:
out += b"8BIM" + key + struct.pack(">I", len(payload)) + payload
return bytes(out)
def _layer_record(top, left, bottom, right, name, opacity, visible, channels,
layer_id=1) -> bytes:
rec = bytearray()
rec += struct.pack(">4i", top, left, bottom, right)
rec += struct.pack(">H", len(channels))
@@ -489,6 +511,8 @@ def _layer_record(top, left, bottom, right, name, opacity, visible, channels) ->
# luni: unicode layer name
uni = name.encode("utf-16-be")
extra += b"8BIMluni" + struct.pack(">I", len(uni)) + uni
# standard blocks Photoshop expects (see _standard_blocks)
extra += _standard_blocks(layer_id)
rec += struct.pack(">I", len(extra))
rec += extra
return bytes(rec)
@@ -506,11 +530,13 @@ def build_psd(layers, width: int, height: int, out_path: str, workers: int,
# layer records + channel payloads
records = bytearray()
payload = bytearray()
for (name, opacity, visible, (lx, ly), img), ch in zip(layers, chan_data):
for li, (name, opacity, visible, (lx, ly), img) in enumerate(layers):
ch = chan_data[li]
l, t = lx, ly
r = l + img.width
b = t + img.height
records += _layer_record(t, l, b, r, name, opacity, visible, ch)
records += _layer_record(t, l, b, r, name, opacity, visible, ch,
layer_id=li + 1)
for _, p in ch:
payload += p