Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes15kdownloads
PsdImagePlugin.py338 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        if layer > len(self.layers):
179            msg = "no more images in PSD file"
180            raise EOFError(msg)
181        _, mode, _, tile = self.layers[layer - 1]
182        self._mode = mode
183        self.tile = tile
184        self.frame = layer
185        self.fp = self._fp
186
187    def tell(self) -> int:
188        # return layer number (0=image, 1..max=layers)
189        return self.frame
190
191
192def _layerinfo(
193    fp: IO[bytes], ct_bytes: int
194) -> list[tuple[str, str, tuple[int, int, int, int], list[ImageFile._Tile]]]:
195    # read layerinfo block
196    layers = []
197
198    def read(size: int) -> bytes:
199        return ImageFile._safe_read(fp, size)
200
201    ct = si16(read(2))
202
203    # sanity check
204    if ct_bytes < (abs(ct) * 20):
205        msg = "Layer block too short for number of layers requested"
206        raise SyntaxError(msg)
207
208    for _ in range(abs(ct)):
209        # bounding box
210        y0 = si32(read(4))
211        x0 = si32(read(4))
212        y1 = si32(read(4))
213        x1 = si32(read(4))
214
215        # image info
216        bands = []
217        ct_types = i16(read(2))
218        if ct_types > 4:
219            fp.seek(ct_types * 6 + 12, io.SEEK_CUR)
220            size = i32(read(4))
221            fp.seek(size, io.SEEK_CUR)
222            continue
223
224        for _ in range(ct_types):
225            type = i16(read(2))
226
227            if type == 65535:
228                b = "A"
229            else:
230                b = "RGBA"[type]
231
232            bands.append(b)
233            read(4)  # size
234
235        # figure out the image mode
236        bands.sort()
237        if bands == ["R"]:
238            mode = "L"
239        elif bands == ["B", "G", "R"]:
240            mode = "RGB"
241        elif bands == ["A", "B", "G", "R"]:
242            mode = "RGBA"
243        else:
244            mode = ""  # unknown
245
246        # skip over blend flags and extra information
247        read(12)  # filler
248        name = ""
249        size = i32(read(4))  # length of the extra data field
250        if size:
251            data_end = fp.tell() + size
252
253            length = i32(read(4))
254            if length:
255                fp.seek(length - 16, io.SEEK_CUR)
256
257            length = i32(read(4))
258            if length:
259                fp.seek(length, io.SEEK_CUR)
260
261            length = i8(read(1))
262            if length:
263                # Don't know the proper encoding,
264                # Latin-1 should be a good guess
265                name = read(length).decode("latin-1", "replace")
266
267            fp.seek(data_end)
268        layers.append((name, mode, (x0, y0, x1, y1)))
269
270    # get tiles
271    layerinfo = []
272    for i, (name, mode, bbox) in enumerate(layers):
273        tile = []
274        for m in mode:
275            t = _maketile(fp, m, bbox, 1)
276            if t:
277                tile.extend(t)
278        layerinfo.append((name, mode, bbox, tile))
279
280    return layerinfo
281
282
283def _maketile(
284    file: IO[bytes], mode: str, bbox: tuple[int, int, int, int], channels: int
285) -> list[ImageFile._Tile]:
286    tiles = []
287    read = file.read
288
289    compression = i16(read(2))
290
291    xsize = bbox[2] - bbox[0]
292    ysize = bbox[3] - bbox[1]
293
294    offset = file.tell()
295
296    if compression == 0:
297        #
298        # raw compression
299        for channel in range(channels):
300            layer = mode[channel]
301            if mode == "CMYK":
302                layer += ";I"
303            tiles.append(ImageFile._Tile("raw", bbox, offset, layer))
304            offset = offset + xsize * ysize
305
306    elif compression == 1:
307        #
308        # packbits compression
309        i = 0
310        bytecount = read(channels * ysize * 2)
311        offset = file.tell()
312        for channel in range(channels):
313            layer = mode[channel]
314            if mode == "CMYK":
315                layer += ";I"
316            tiles.append(ImageFile._Tile("packbits", bbox, offset, layer))
317            for y in range(ysize):
318                offset = offset + i16(bytecount, i)
319                i += 2
320
321    file.seek(offset)
322
323    if offset & 1:
324        read(1)  # padding
325
326    return tiles
327
328
329# --------------------------------------------------------------------
330# registry
331
332
333Image.register_open(PsdImageFile.format, PsdImageFile, _accept)
334
335Image.register_extension(PsdImageFile.format, ".psd")
336
337Image.register_mime(PsdImageFile.format, "image/vnd.adobe.photoshop")
338 
codekingpro/portable-devtools · Team Ai