CoolFace
Apppublic

Aluode/PerceptionLabPortable

sourceHugging Faceupdated 9mo agoView on Hugging Face
0likes
PsdImagePlugin.py335 linesDownload Raw Back to PIL
1#
2# The Python Imaging Library
3# $Id$
4#
5# Adobe PSD 2.5/3.0 file handling
6#
7# History:
8# 1995-09-01 fl   Created
9# 1997-01-03 fl   Read most PSD images
10# 1997-01-18 fl   Fixed P and CMYK support
11# 2001-10-21 fl   Added seek/tell support (for layers)
12#
13# Copyright (c) 1997-2001 by Secret Labs AB.
14# Copyright (c) 1995-2001 by Fredrik Lundh
15#
16# See the README file for information on usage and redistribution.
17#
18from __future__ import annotations
19
20import io
21from functools import cached_property
22from typing import IO
23
24from . import Image, ImageFile, ImagePalette
25from ._binary import i8
26from ._binary import i16be as i16
27from ._binary import i32be as i32
28from ._binary import si16be as si16
29from ._binary import si32be as si32
30from ._util import DeferredError
31
32MODES = {
33    # (photoshop mode, bits) -> (pil mode, required channels)
34    (0, 1): ("1", 1),
35    (0, 8): ("L", 1),
36    (1, 8): ("L", 1),
37    (2, 8): ("P", 1),
38    (3, 8): ("RGB", 3),
39    (4, 8): ("CMYK", 4),
40    (7, 8): ("L", 1),  # FIXME: multilayer
41    (8, 8): ("L", 1),  # duotone
42    (9, 8): ("LAB", 3),
43}
44
45
46# --------------------------------------------------------------------.
47# read PSD images
48
49
50def _accept(prefix: bytes) -> bool:
51    return prefix.startswith(b"8BPS")
52
53
54##
55# Image plugin for Photoshop images.
56
57
58class PsdImageFile(ImageFile.ImageFile):
59    format = "PSD"
60    format_description = "Adobe Photoshop"
61    _close_exclusive_fp_after_loading = False
62
63    def _open(self) -> None:
64        assert self.fp is not None
65        read = self.fp.read
66
67        #
68        # header
69
70        s = read(26)
71        if not _accept(s) or i16(s, 4) != 1:
72            msg = "not a PSD file"
73            raise SyntaxError(msg)
74
75        psd_bits = i16(s, 22)
76        psd_channels = i16(s, 12)
77        psd_mode = i16(s, 24)
78
79        mode, channels = MODES[(psd_mode, psd_bits)]
80
81        if channels > psd_channels:
82            msg = "not enough channels"
83            raise OSError(msg)
84        if mode == "RGB" and psd_channels == 4:
85            mode = "RGBA"
86            channels = 4
87
88        self._mode = mode
89        self._size = i32(s, 18), i32(s, 14)
90
91        #
92        # color mode data
93
94        size = i32(read(4))
95        if size:
96            data = read(size)
97            if mode == "P" and size == 768:
98                self.palette = ImagePalette.raw("RGB;L", data)
99
100        #
101        # image resources
102
103        self.resources = []
104
105        size = i32(read(4))
106        if size:
107            # load resources
108            end = self.fp.tell() + size
109            while self.fp.tell() < end:
110                read(4)  # signature
111                id = i16(read(2))
112                name = read(i8(read(1)))
113                if not (len(name) & 1):
114                    read(1)  # padding
115                data = read(i32(read(4)))
116                if len(data) & 1:
117                    read(1)  # padding
118                self.resources.append((id, name, data))
119                if id == 1039:  # ICC profile
120                    self.info["icc_profile"] = data
121
122        #
123        # layer and mask information
124
125        self._layers_position = None
126
127        size = i32(read(4))
128        if size:
129            end = self.fp.tell() + size
130            size = i32(read(4))
131            if size:
132                self._layers_position = self.fp.tell()
133                self._layers_size = size
134            self.fp.seek(end)
135        self._n_frames: int | None = None
136
137        #
138        # image descriptor
139
140        self.tile = _maketile(self.fp, mode, (0, 0) + self.size, channels)
141
142        # keep the file open
143        self._fp = self.fp
144        self.frame = 1
145        self._min_frame = 1
146
147    @cached_property
148    def layers(
149        self,
150    ) -> list[tuple[str, str, tuple[int, int, int, int], list[ImageFile._Tile]]]:
151        layers = []
152        if self._layers_position is not None:
153            if isinstance(self._fp, DeferredError):
154                raise self._fp.ex
155            self._fp.seek(self._layers_position)
156            _layer_data = io.BytesIO(ImageFile._safe_read(self._fp, self._layers_size))
157            layers = _layerinfo(_layer_data, self._layers_size)
158        self._n_frames = len(layers)
159        return layers
160
161    @property
162    def n_frames(self) -> int:
163        if self._n_frames is None:
164            self._n_frames = len(self.layers)
165        return self._n_frames
166
167    @property
168    def is_animated(self) -> bool:
169        return len(self.layers) > 1
170
171    def seek(self, layer: int) -> None:
172        if not self._seek_check(layer):
173            return
174        if isinstance(self._fp, DeferredError):
175            raise self._fp.ex
176
177        # seek to given layer (1..max)
178        _, mode, _, tile = self.layers[layer - 1]
179        self._mode = mode
180        self.tile = tile
181        self.frame = layer
182        self.fp = self._fp
183
184    def tell(self) -> int:
185        # return layer number (0=image, 1..max=layers)
186        return self.frame
187
188
189def _layerinfo(
190    fp: IO[bytes], ct_bytes: int
191) -> list[tuple[str, str, tuple[int, int, int, int], list[ImageFile._Tile]]]:
192    # read layerinfo block
193    layers = []
194
195    def read(size: int) -> bytes:
196        return ImageFile._safe_read(fp, size)
197
198    ct = si16(read(2))
199
200    # sanity check
201    if ct_bytes < (abs(ct) * 20):
202        msg = "Layer block too short for number of layers requested"
203        raise SyntaxError(msg)
204
205    for _ in range(abs(ct)):
206        # bounding box
207        y0 = si32(read(4))
208        x0 = si32(read(4))
209        y1 = si32(read(4))
210        x1 = si32(read(4))
211
212        # image info
213        bands = []
214        ct_types = i16(read(2))
215        if ct_types > 4:
216            fp.seek(ct_types * 6 + 12, io.SEEK_CUR)
217            size = i32(read(4))
218            fp.seek(size, io.SEEK_CUR)
219            continue
220
221        for _ in range(ct_types):
222            type = i16(read(2))
223
224            if type == 65535:
225                b = "A"
226            else:
227                b = "RGBA"[type]
228
229            bands.append(b)
230            read(4)  # size
231
232        # figure out the image mode
233        bands.sort()
234        if bands == ["R"]:
235            mode = "L"
236        elif bands == ["B", "G", "R"]:
237            mode = "RGB"
238        elif bands == ["A", "B", "G", "R"]:
239            mode = "RGBA"
240        else:
241            mode = ""  # unknown
242
243        # skip over blend flags and extra information
244        read(12)  # filler
245        name = ""
246        size = i32(read(4))  # length of the extra data field
247        if size:
248            data_end = fp.tell() + size
249
250            length = i32(read(4))
251            if length:
252                fp.seek(length - 16, io.SEEK_CUR)
253
254            length = i32(read(4))
255            if length:
256                fp.seek(length, io.SEEK_CUR)
257
258            length = i8(read(1))
259            if length:
260                # Don't know the proper encoding,
261                # Latin-1 should be a good guess
262                name = read(length).decode("latin-1", "replace")
263
264            fp.seek(data_end)
265        layers.append((name, mode, (x0, y0, x1, y1)))
266
267    # get tiles
268    layerinfo = []
269    for i, (name, mode, bbox) in enumerate(layers):
270        tile = []
271        for m in mode:
272            t = _maketile(fp, m, bbox, 1)
273            if t:
274                tile.extend(t)
275        layerinfo.append((name, mode, bbox, tile))
276
277    return layerinfo
278
279
280def _maketile(
281    file: IO[bytes], mode: str, bbox: tuple[int, int, int, int], channels: int
282) -> list[ImageFile._Tile]:
283    tiles = []
284    read = file.read
285
286    compression = i16(read(2))
287
288    xsize = bbox[2] - bbox[0]
289    ysize = bbox[3] - bbox[1]
290
291    offset = file.tell()
292
293    if compression == 0:
294        #
295        # raw compression
296        for channel in range(channels):
297            layer = mode[channel]
298            if mode == "CMYK":
299                layer += ";I"
300            tiles.append(ImageFile._Tile("raw", bbox, offset, layer))
301            offset = offset + xsize * ysize
302
303    elif compression == 1:
304        #
305        # packbits compression
306        i = 0
307        bytecount = read(channels * ysize * 2)
308        offset = file.tell()
309        for channel in range(channels):
310            layer = mode[channel]
311            if mode == "CMYK":
312                layer += ";I"
313            tiles.append(ImageFile._Tile("packbits", bbox, offset, layer))
314            for y in range(ysize):
315                offset = offset + i16(bytecount, i)
316                i += 2
317
318    file.seek(offset)
319
320    if offset & 1:
321        read(1)  # padding
322
323    return tiles
324
325
326# --------------------------------------------------------------------
327# registry
328
329
330Image.register_open(PsdImageFile.format, PsdImageFile, _accept)
331
332Image.register_extension(PsdImageFile.format, ".psd")
333
334Image.register_mime(PsdImageFile.format, "image/vnd.adobe.photoshop")
335 
Aluode/PerceptionLabPortable · CoolFace