Source code for psd_tools.psd.document

"""
PSD document structure module.

This module contains the main PSD class that represents the low-level
binary structure of a PSD/PSB file.
"""

import logging
from typing import Any, BinaryIO, Generator, Optional, TypeVar

from attrs import define, field

from psd_tools.constants import Tag
from .base import BaseElement
from .color_mode_data import ColorModeData
from .header import FileHeader
from .image_data import ImageData
from .image_resources import ImageResources
from .layer_and_mask import (
    LayerAndMaskInformation,
    LayerInfo,
)

logger = logging.getLogger(__name__)

T = TypeVar("T", bound="PSD")


[docs] @define(repr=False) class PSD(BaseElement): """ Low-level PSD file structure that resembles the specification_. .. _specification: https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/ Example:: from psd_tools.psd import PSD with open(input_file, 'rb') as f: psd = PSD.read(f) with open(output_file, 'wb') as f: psd.write(f) .. py:attribute:: header See :py:class:`.FileHeader`. .. py:attribute:: color_mode_data See :py:class:`.ColorModeData`. .. py:attribute:: image_resources See :py:class:`.ImageResources`. .. py:attribute:: layer_and_mask_information See :py:class:`.LayerAndMaskInformation`. .. py:attribute:: image_data See :py:class:`.ImageData`. """ header: FileHeader = field(factory=FileHeader) color_mode_data: ColorModeData = field(factory=ColorModeData) image_resources: ImageResources = field(factory=ImageResources) layer_and_mask_information: LayerAndMaskInformation = field( factory=LayerAndMaskInformation ) image_data: ImageData = field(factory=ImageData) @classmethod def read( cls: type[T], fp: BinaryIO, encoding: str = "macroman", **kwargs: Any ) -> T: header = FileHeader.read(fp) logger.debug("read %s" % header) return cls( header, ColorModeData.read(fp), ImageResources.read(fp, encoding), LayerAndMaskInformation.read(fp, encoding, header.version), ImageData.read(fp), ) def write(self, fp: BinaryIO, encoding: str = "macroman", **kwargs: Any) -> int: logger.debug("writing %s" % self.header) written = self.header.write(fp) written += self.color_mode_data.write(fp) written += self.image_resources.write(fp, encoding) written += self.layer_and_mask_information.write( fp, encoding, self.header.version, **kwargs ) written += self.image_data.write(fp) return written def _iter_layers(self) -> Generator[tuple[Any, Any], None, None]: """ Iterate over (layer_record, channel_data) pairs. """ layer_info = self._get_layer_info() if layer_info is not None: records = layer_info.layer_records channel_data = layer_info.channel_image_data if records is not None and channel_data is not None: for record, channels in zip(records, channel_data): yield record, channels def _get_layer_info(self) -> Optional[LayerInfo]: tagged_blocks = self.layer_and_mask_information.tagged_blocks if tagged_blocks is not None: for key in (Tag.LAYER_16, Tag.LAYER_32): if key in tagged_blocks: return tagged_blocks.get_data(key) return self.layer_and_mask_information.layer_info