Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/PIL/PsdImagePlugin.py: 9%
169 statements
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
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#
19import io
21from . import Image, ImageFile, ImagePalette
22from ._binary import i8
23from ._binary import i16be as i16
24from ._binary import i32be as i32
25from ._binary import si16be as si16
27MODES = {
28 # (photoshop mode, bits) -> (pil mode, required channels)
29 (0, 1): ("1", 1),
30 (0, 8): ("L", 1),
31 (1, 8): ("L", 1),
32 (2, 8): ("P", 1),
33 (3, 8): ("RGB", 3),
34 (4, 8): ("CMYK", 4),
35 (7, 8): ("L", 1), # FIXME: multilayer
36 (8, 8): ("L", 1), # duotone
37 (9, 8): ("LAB", 3),
38}
41# --------------------------------------------------------------------.
42# read PSD images
45def _accept(prefix):
46 return prefix[:4] == b"8BPS"
49##
50# Image plugin for Photoshop images.
53class PsdImageFile(ImageFile.ImageFile):
55 format = "PSD"
56 format_description = "Adobe Photoshop"
57 _close_exclusive_fp_after_loading = False
59 def _open(self):
61 read = self.fp.read
63 #
64 # header
66 s = read(26)
67 if not _accept(s) or i16(s, 4) != 1:
68 raise SyntaxError("not a PSD file")
70 psd_bits = i16(s, 22)
71 psd_channels = i16(s, 12)
72 psd_mode = i16(s, 24)
74 mode, channels = MODES[(psd_mode, psd_bits)]
76 if channels > psd_channels:
77 raise OSError("not enough channels")
79 self.mode = mode
80 self._size = i32(s, 18), i32(s, 14)
82 #
83 # color mode data
85 size = i32(read(4))
86 if size:
87 data = read(size)
88 if mode == "P" and size == 768:
89 self.palette = ImagePalette.raw("RGB;L", data)
91 #
92 # image resources
94 self.resources = []
96 size = i32(read(4))
97 if size:
98 # load resources
99 end = self.fp.tell() + size
100 while self.fp.tell() < end:
101 read(4) # signature
102 id = i16(read(2))
103 name = read(i8(read(1)))
104 if not (len(name) & 1):
105 read(1) # padding
106 data = read(i32(read(4)))
107 if len(data) & 1:
108 read(1) # padding
109 self.resources.append((id, name, data))
110 if id == 1039: # ICC profile
111 self.info["icc_profile"] = data
113 #
114 # layer and mask information
116 self.layers = []
118 size = i32(read(4))
119 if size:
120 end = self.fp.tell() + size
121 size = i32(read(4))
122 if size:
123 _layer_data = io.BytesIO(ImageFile._safe_read(self.fp, size))
124 self.layers = _layerinfo(_layer_data, size)
125 self.fp.seek(end)
126 self.n_frames = len(self.layers)
127 self.is_animated = self.n_frames > 1
129 #
130 # image descriptor
132 self.tile = _maketile(self.fp, mode, (0, 0) + self.size, channels)
134 # keep the file open
135 self._fp = self.fp
136 self.frame = 1
137 self._min_frame = 1
139 def seek(self, layer):
140 if not self._seek_check(layer):
141 return
143 # seek to given layer (1..max)
144 try:
145 name, mode, bbox, tile = self.layers[layer - 1]
146 self.mode = mode
147 self.tile = tile
148 self.frame = layer
149 self.fp = self._fp
150 return name, bbox
151 except IndexError as e:
152 raise EOFError("no such layer") from e
154 def tell(self):
155 # return layer number (0=image, 1..max=layers)
156 return self.frame
159def _layerinfo(fp, ct_bytes):
160 # read layerinfo block
161 layers = []
163 def read(size):
164 return ImageFile._safe_read(fp, size)
166 ct = si16(read(2))
168 # sanity check
169 if ct_bytes < (abs(ct) * 20):
170 raise SyntaxError("Layer block too short for number of layers requested")
172 for _ in range(abs(ct)):
174 # bounding box
175 y0 = i32(read(4))
176 x0 = i32(read(4))
177 y1 = i32(read(4))
178 x1 = i32(read(4))
180 # image info
181 mode = []
182 ct_types = i16(read(2))
183 types = list(range(ct_types))
184 if len(types) > 4:
185 continue
187 for _ in types:
188 type = i16(read(2))
190 if type == 65535:
191 m = "A"
192 else:
193 m = "RGBA"[type]
195 mode.append(m)
196 read(4) # size
198 # figure out the image mode
199 mode.sort()
200 if mode == ["R"]:
201 mode = "L"
202 elif mode == ["B", "G", "R"]:
203 mode = "RGB"
204 elif mode == ["A", "B", "G", "R"]:
205 mode = "RGBA"
206 else:
207 mode = None # unknown
209 # skip over blend flags and extra information
210 read(12) # filler
211 name = ""
212 size = i32(read(4)) # length of the extra data field
213 if size:
214 data_end = fp.tell() + size
216 length = i32(read(4))
217 if length:
218 fp.seek(length - 16, io.SEEK_CUR)
220 length = i32(read(4))
221 if length:
222 fp.seek(length, io.SEEK_CUR)
224 length = i8(read(1))
225 if length:
226 # Don't know the proper encoding,
227 # Latin-1 should be a good guess
228 name = read(length).decode("latin-1", "replace")
230 fp.seek(data_end)
231 layers.append((name, mode, (x0, y0, x1, y1)))
233 # get tiles
234 i = 0
235 for name, mode, bbox in layers:
236 tile = []
237 for m in mode:
238 t = _maketile(fp, m, bbox, 1)
239 if t:
240 tile.extend(t)
241 layers[i] = name, mode, bbox, tile
242 i += 1
244 return layers
247def _maketile(file, mode, bbox, channels):
249 tile = None
250 read = file.read
252 compression = i16(read(2))
254 xsize = bbox[2] - bbox[0]
255 ysize = bbox[3] - bbox[1]
257 offset = file.tell()
259 if compression == 0:
260 #
261 # raw compression
262 tile = []
263 for channel in range(channels):
264 layer = mode[channel]
265 if mode == "CMYK":
266 layer += ";I"
267 tile.append(("raw", bbox, offset, layer))
268 offset = offset + xsize * ysize
270 elif compression == 1:
271 #
272 # packbits compression
273 i = 0
274 tile = []
275 bytecount = read(channels * ysize * 2)
276 offset = file.tell()
277 for channel in range(channels):
278 layer = mode[channel]
279 if mode == "CMYK":
280 layer += ";I"
281 tile.append(("packbits", bbox, offset, layer))
282 for y in range(ysize):
283 offset = offset + i16(bytecount, i)
284 i += 2
286 file.seek(offset)
288 if offset & 1:
289 read(1) # padding
291 return tile
294# --------------------------------------------------------------------
295# registry
298Image.register_open(PsdImageFile.format, PsdImageFile, _accept)
300Image.register_extension(PsdImageFile.format, ".psd")
302Image.register_mime(PsdImageFile.format, "image/vnd.adobe.photoshop")