mirror of
https://github.com/storytold/photocraft.git
synced 2026-10-29 18:52:15 +01:00
118 lines
4.0 KiB
Python
118 lines
4.0 KiB
Python
#!/usr/bin/env python3
|
||||
|
|
"""Generates the layered-TIFF fixtures under corpus/layered-tiff (gitignored) with an independent
|
|||
|
|
implementation (psdtags + tifffile, BSD-3), so PhotoCraft's reader is checked against a second
|
|||
|
|
writer rather than only against itself:
|
|||
|
|
|
|||
|
|
layered-{le,be}-{8,16}bit.tif 2 layers (one offset, one with transparency), RLE and ZIP
|
|||
|
|
channels, an Intel-order and a Motorola-order variant.
|
|||
|
|
|
|||
|
|
Run with: python scripts/layered_tiff_fixtures.py
|
|||
|
|
Needs: pip install psdtags tifffile numpy
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
from pathlib import Path
|
|||
|
|
|
|||
|
|
import numpy
|
|||
|
|
import tifffile
|
|||
|
|
from psdtags import (
|
|||
|
|
PsdBlendMode,
|
|||
|
|
PsdChannel,
|
|||
|
|
PsdChannelId,
|
|||
|
|
PsdClippingType,
|
|||
|
|
PsdColorSpaceType,
|
|||
|
|
PsdCompressionType,
|
|||
|
|
PsdFormat,
|
|||
|
|
PsdKey,
|
|||
|
|
PsdLayer,
|
|||
|
|
PsdLayerFlag,
|
|||
|
|
PsdLayerMask,
|
|||
|
|
PsdLayers,
|
|||
|
|
PsdRectangle,
|
|||
|
|
PsdString,
|
|||
|
|
PsdUserMask,
|
|||
|
|
TiffImageSourceData,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
OUT = Path(__file__).resolve().parent.parent / "corpus" / "layered-tiff"
|
|||
|
|
W, H = 6, 4
|
|||
|
|
|
|||
|
|
|
|||
|
|
def gradient(dtype, seed):
|
|||
|
|
"""A deterministic W×H×4 RGBA ramp on the 8-bit grid (exact at 16 bits too)."""
|
|||
|
|
maxv = 255 if dtype == numpy.uint8 else 65535
|
|||
|
|
img = numpy.zeros((H, W, 4), dtype=numpy.uint32)
|
|||
|
|
for y in range(H):
|
|||
|
|
for x in range(W):
|
|||
|
|
img[y, x] = [(x * 40 + seed) % 256, (y * 60 + seed * 3) % 256, (x * y * 17 + seed) % 256, 255]
|
|||
|
|
img = img * (maxv // 255)
|
|||
|
|
return img.astype(dtype)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def layer(name, data, left, top, blend, opacity, compression):
|
|||
|
|
h, w = data.shape[:2]
|
|||
|
|
rect = PsdRectangle(top, left, top + h, left + w)
|
|||
|
|
channels = []
|
|||
|
|
for cid, idx in [(PsdChannelId.TRANSPARENCY_MASK, 3), (PsdChannelId.CHANNEL0, 0), (PsdChannelId.CHANNEL1, 1), (PsdChannelId.CHANNEL2, 2)]:
|
|||
|
|
channels.append(PsdChannel(channelid=cid, compression=compression, data=numpy.ascontiguousarray(data[:, :, idx])))
|
|||
|
|
return PsdLayer(
|
|||
|
|
name=name,
|
|||
|
|
rectangle=rect,
|
|||
|
|
channels=channels,
|
|||
|
|
mask=PsdLayerMask(),
|
|||
|
|
opacity=opacity,
|
|||
|
|
blendmode=blend,
|
|||
|
|
blending_ranges=(),
|
|||
|
|
clipping=PsdClippingType.BASE,
|
|||
|
|
flags=PsdLayerFlag.PHOTOSHOP5,
|
|||
|
|
info=[PsdString(PsdKey.UNICODE_LAYER_NAME, name)],
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def build(dtype, psdformat):
|
|||
|
|
lower = gradient(dtype, 1)
|
|||
|
|
upper = gradient(dtype, 7)[:3, :4].copy()
|
|||
|
|
# A soft transparency ramp on the upper layer.
|
|||
|
|
maxv = 255 if dtype == numpy.uint8 else 65535
|
|||
|
|
for y in range(3):
|
|||
|
|
for x in range(4):
|
|||
|
|
upper[y, x, 3] = (x * 80 % 256) * (maxv // 255)
|
|||
|
|
layers = PsdLayers(
|
|||
|
|
key=PsdKey.LAYER if dtype == numpy.uint8 else PsdKey.LAYER_16,
|
|||
|
|
has_transparency=False,
|
|||
|
|
layers=[
|
|||
|
|
layer("Background", lower, 0, 0, PsdBlendMode.NORMAL, 255, PsdCompressionType.RLE),
|
|||
|
|
layer("Upper é", upper, 1, 1, PsdBlendMode.MULTIPLY, 200, PsdCompressionType.ZIP),
|
|||
|
|
],
|
|||
|
|
)
|
|||
|
|
usermask = PsdUserMask(colorspace=PsdColorSpaceType.RGB, components=(65535, 0, 0, 0), opacity=50)
|
|||
|
|
return TiffImageSourceData(name="Layered", psdformat=psdformat, layers=layers, usermask=usermask)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def main():
|
|||
|
|
OUT.mkdir(parents=True, exist_ok=True)
|
|||
|
|
for dtype, bits in [(numpy.uint8, 8), (numpy.uint16, 16)]:
|
|||
|
|
for order, psdformat in [("le", PsdFormat.LE32BIT), ("be", PsdFormat.BE32BIT)]:
|
|||
|
|
isd = build(dtype, psdformat)
|
|||
|
|
composite = gradient(dtype, 1)
|
|||
|
|
path = OUT / f"layered-{order}-{bits}bit.tif"
|
|||
|
|
byteorder = "<" if order == "le" else ">"
|
|||
|
|
tifffile.imwrite(
|
|||
|
|
path,
|
|||
|
|
composite,
|
|||
|
|
byteorder=byteorder,
|
|||
|
|
photometric="rgb",
|
|||
|
|
extrasamples=["unassalpha"],
|
|||
|
|
resolution=(300, 300),
|
|||
|
|
resolutionunit="inch",
|
|||
|
|
extratags=[isd.tifftag()],
|
|||
|
|
metadata=None,
|
|||
|
|
)
|
|||
|
|
back = TiffImageSourceData.fromtiff(path)
|
|||
|
|
names = [l.name for l in back.layers]
|
|||
|
|
assert names == ["Background", "Upper é"], names
|
|||
|
|
print(path.name, path.stat().st_size, "bytes", names, back.psdformat)
|
|||
|
|
|
|||
|
|
|
|||
|
|
if __name__ == "__main__":
|
|||
|
|
main()
|