from typing import NamedTuple, Union
from typing import Generator, Iterable
from PIL import Image
from psd_tools import PSDImage
from pathlib import Path
from psd2pngs.safe_name import get_safe_name
from psd_tools.api.layers import Layer
[docs]class ImageLayerInfo(NamedTuple):
"""Layer and
Parameters
----------
absolute_path: Path
The absolute path to output the image.
layer: Layer
The layer to output.
"""
absolute_path: Path
"""The absolute path to output the image.
"""
layer: Layer
"""The layer to output.
"""
[docs]def search_all_layers(
layer: Union[Layer, PSDImage], current_absolute_path: Path
) -> Generator[ImageLayerInfo, None, None]:
"""Get all ImageLayerInfos under the given layer. Recursively get all ImageLayerInfos for all children.
Parameters
----------
layer : Layer
The base layer.
current_absolute_path : Path
The base absolute path to use for ImageLayerInfo["absolute_path"]
Yields
------
Generator[ImageLayerInfo, None, None]
ImageLayerInfos for all layers.
"""
# avoid to create 'Root' folder because it is just disruptive
if layer.kind == "psdimage":
absolute_path = current_absolute_path
else:
layer_safe_name = get_safe_name(layer.name)
absolute_path = current_absolute_path.joinpath(layer_safe_name)
if layer.is_group():
# create folder for group
absolute_path.mkdir(parents=True, exist_ok=True)
# recursively get all ImageLayerInfos for all children
for child in layer: # type: ignore
yield from search_all_layers(child, absolute_path)
else:
# add suffix
absolute_path = absolute_path.with_suffix(".png")
# yield ImageLayerInfo
yield ImageLayerInfo(absolute_path=absolute_path, layer=layer)
[docs]def save_layer(image_size: tuple[int, int], layer_info: ImageLayerInfo) -> None:
"""Save the given layer (layer_info['layer']) to the given path (layer_info['absolute_path']) using PIL.
Parameters
----------
image_size : tuple[int, int]
The size of the root layer (any layer which you want it to be based on). psd.size is Recommended.
layer_info : ImageLayerInfo
The layer and absolute path to save the layer to.
"""
# make sure that the size and position of the layer is maintained
with Image.new("RGBA", image_size, (0, 0, 0, 0)) as img:
# covert to PIL.Image
img_pil = layer_info.layer.topil()
if img_pil is not None:
with img_pil:
# paste the layer onto the image to maintain the position
img.paste(img_pil, layer_info.layer.offset) # type: ignore
img.save(layer_info.absolute_path)
[docs]def save_some_layers(psd_path: Path, out_dir_path: Path, layer_indcies: Iterable[int]):
"""Open the PSD file and save the given layers to the given path.
The expected use case of this function is to use as a multiprocessing function.
(Because heavy layers do not have to be pickled.)
Parameters
----------
psd_path : Path
The path to the PSD file.
out_dir_path : Path
The base absolute path to create a folder in which layers will be saved.
layer_indcies : Iterable[int]
Indcies (for search_all_layers()) of the layers to save.
"""
# open the PSD file
psd = PSDImage.open(psd_path)
# search layers
image_layer_infos = list(search_all_layers(psd, out_dir_path))
# save specified layers
for i in layer_indcies:
save_layer(psd.size, image_layer_infos[i])