Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/PIL/ImImagePlugin.py: 57%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1#
2# The Python Imaging Library.
3# $Id$
4#
5# IFUNC IM file handling for PIL
6#
7# history:
8# 1995-09-01 fl Created.
9# 1997-01-03 fl Save palette images
10# 1997-01-08 fl Added sequence support
11# 1997-01-23 fl Added P and RGB save support
12# 1997-05-31 fl Read floating point images
13# 1997-06-22 fl Save floating point images
14# 1997-08-27 fl Read and save 1-bit images
15# 1998-06-25 fl Added support for RGB+LUT images
16# 1998-07-02 fl Added support for YCC images
17# 1998-07-15 fl Renamed offset attribute to avoid name clash
18# 1998-12-29 fl Added I;16 support
19# 2001-02-17 fl Use 're' instead of 'regex' (Python 2.1) (0.7)
20# 2003-09-26 fl Added LA/PA support
21#
22# Copyright (c) 1997-2003 by Secret Labs AB.
23# Copyright (c) 1995-2001 by Fredrik Lundh.
24#
25# See the README file for information on usage and redistribution.
26#
27from __future__ import annotations
29__lazy_modules__ = {"PIL._util"}
31import os
32import re
34from . import Image, ImageFile, ImagePalette
35from ._deprecate import deprecate
36from ._util import DeferredError
38TYPE_CHECKING = False
39if TYPE_CHECKING:
40 from typing import IO, Any
43# --------------------------------------------------------------------
44# Standard tags
46COMMENT = "Comment"
47DATE = "Date"
48EQUIPMENT = "Digitalization equipment"
49FRAMES = "File size (no of images)"
50LUT = "Lut"
51NAME = "Name"
52SCALE = "Scale (x,y)"
53SIZE = "Image size (x*y)"
54MODE = "Image type"
56TAGS = {
57 COMMENT: 0,
58 DATE: 0,
59 EQUIPMENT: 0,
60 FRAMES: 0,
61 LUT: 0,
62 NAME: 0,
63 SCALE: 0,
64 SIZE: 0,
65 MODE: 0,
66}
68OPEN = {
69 # ifunc93/p3cfunc formats
70 "0 1 image": ("1", "1"),
71 "L 1 image": ("1", "1"),
72 "Greyscale image": ("L", "L"),
73 "Grayscale image": ("L", "L"),
74 "RGB image": ("RGB", "RGB;L"),
75 "RLB image": ("RGB", "RLB"),
76 "RYB image": ("RGB", "RLB"),
77 "B1 image": ("1", "1"),
78 "B2 image": ("P", "P;2"),
79 "B4 image": ("P", "P;4"),
80 "X 24 image": ("RGB", "RGB"),
81 "L 32 S image": ("I", "I;32"),
82 "L 32 F image": ("F", "F;32"),
83 # old p3cfunc formats
84 "RGB3 image": ("RGB", "RGB;T"),
85 "RYB3 image": ("RGB", "RYB;T"),
86 # extensions
87 "LA image": ("LA", "LA;L"),
88 "PA image": ("LA", "PA;L"),
89 "RGBA image": ("RGBA", "RGBA;L"),
90 "RGBX image": ("RGB", "RGBX;L"),
91 "CMYK image": ("CMYK", "CMYK;L"),
92 "YCC image": ("YCbCr", "YCbCr;L"),
93}
95# ifunc95 extensions
96for i in ["8", "8S", "16", "16S", "32", "32F"]:
97 OPEN[f"L {i} image"] = ("F", f"F;{i}")
98 OPEN[f"L*{i} image"] = ("F", f"F;{i}")
99for i in ["16", "16L", "16B"]:
100 OPEN[f"L {i} image"] = (f"I;{i}", f"I;{i}")
101 OPEN[f"L*{i} image"] = (f"I;{i}", f"I;{i}")
102for i in ["32S"]:
103 OPEN[f"L {i} image"] = ("I", f"I;{i}")
104 OPEN[f"L*{i} image"] = ("I", f"I;{i}")
105for j in range(2, 33):
106 OPEN[f"L*{j} image"] = ("F", f"F;{j}")
109# --------------------------------------------------------------------
110# Read IM directory
112split = re.compile(rb"^([A-Za-z][^:]*):[ \t]*(.*)[ \t]*$")
115def number(s: Any) -> float:
116 deprecate("IM image format", 15)
117 try:
118 return int(s)
119 except ValueError:
120 return float(s)
123##
124# Image plugin for the IFUNC IM file format.
127class ImImageFile(ImageFile.ImageFile):
128 format = "IM"
129 format_description = "IFUNC Image Memory"
130 _close_exclusive_fp_after_loading = False
132 def _open(self) -> None:
133 # Quick rejection: if there's not an LF among the first
134 # 100 bytes, this is (probably) not a text header.
136 assert self.fp is not None
137 if b"\n" not in self.fp.read(100):
138 msg = "not an IM file"
139 raise SyntaxError(msg)
140 self.fp.seek(0)
142 n = 0
144 # Default values
145 self.info[MODE] = "L"
146 self.info[SIZE] = (512, 512)
147 self.info[FRAMES] = 1
149 self.rawmode = "L"
151 while True:
152 s = self.fp.read(1)
154 # Some versions of IFUNC uses \n\r instead of \r\n...
155 if s == b"\r":
156 continue
158 if not s or s == b"\0" or s == b"\x1a":
159 break
161 # FIXME: this may read whole file if not a text file
162 s = s + self.fp.readline()
164 if len(s) > 100:
165 msg = "not an IM file"
166 raise SyntaxError(msg)
168 if s.endswith(b"\r\n"):
169 s = s[:-2]
170 elif s.endswith(b"\n"):
171 s = s[:-1]
173 try:
174 m = split.match(s)
175 except re.error as e:
176 msg = "not an IM file"
177 raise SyntaxError(msg) from e
179 if m:
180 k, v = m.group(1, 2)
182 # Don't know if this is the correct encoding,
183 # but a decent guess (I guess)
184 k = k.decode("latin-1", "replace")
185 v = v.decode("latin-1", "replace")
187 # Convert value as appropriate
188 if k in [FRAMES, SCALE, SIZE]:
189 v = v.replace("*", ",")
190 v = tuple(map(number, v.split(",")))
191 if len(v) == 1:
192 v = v[0]
193 elif k == MODE and v in OPEN:
194 v, self.rawmode = OPEN[v]
196 # Add to dictionary. Note that COMMENT tags are
197 # combined into a list of strings.
198 if k == COMMENT:
199 if k in self.info:
200 self.info[k].append(v)
201 else:
202 self.info[k] = [v]
203 else:
204 self.info[k] = v
206 if k in TAGS:
207 n += 1
209 else:
210 msg = f"Syntax error in IM header: {s.decode('ascii', 'replace')}"
211 raise SyntaxError(msg)
213 if not n:
214 msg = "Not an IM file"
215 raise SyntaxError(msg)
217 deprecate("IM image format", 15)
219 # Basic attributes
220 self._size = self.info[SIZE]
221 self._mode = self.info[MODE]
223 # Skip forward to start of image data
224 while s and not s.startswith(b"\x1a"):
225 s = self.fp.read(1)
226 if not s:
227 msg = "File truncated"
228 raise SyntaxError(msg)
230 if LUT in self.info:
231 # convert lookup table to palette or lut attribute
232 palette = self.fp.read(768)
233 greyscale = 1 # greyscale palette
234 linear = 1 # linear greyscale palette
235 for i in range(256):
236 if palette[i] == palette[i + 256] == palette[i + 512]:
237 if palette[i] != i:
238 linear = 0
239 else:
240 greyscale = 0
241 if self.mode in ["L", "LA", "P", "PA"]:
242 if greyscale:
243 if not linear:
244 self.lut = list(palette[:256])
245 else:
246 if self.mode in ["L", "P"]:
247 self._mode = self.rawmode = "P"
248 elif self.mode in ["LA", "PA"]:
249 self._mode = "PA"
250 self.rawmode = "PA;L"
251 self.palette = ImagePalette.raw("RGB;L", palette)
252 elif self.mode == "RGB":
253 if not greyscale or not linear:
254 self.lut = list(palette)
256 self.frame = 0
258 self.__offset = offs = self.fp.tell()
260 self._fp = self.fp # FIXME: hack
262 if self.rawmode.startswith("F;"):
263 # ifunc95 formats
264 try:
265 # use bit decoder (if necessary)
266 bits = int(self.rawmode[2:])
267 if bits not in [8, 16, 32]:
268 self.tile = [
269 ImageFile._Tile("bit", None, offs, (bits, 8, 3, 0, -1))
270 ]
271 return
272 except ValueError:
273 pass
275 if self.rawmode in ["RGB;T", "RYB;T"]:
276 # Old LabEye/3PC files. Would be very surprised if anyone
277 # ever stumbled upon such a file ;-)
278 size = self.size[0] * self.size[1]
279 self.tile = [
280 ImageFile._Tile("raw", None, offs, ("G", 0, -1)),
281 ImageFile._Tile("raw", None, offs + size, ("R", 0, -1)),
282 ImageFile._Tile("raw", None, offs + 2 * size, ("B", 0, -1)),
283 ]
284 else:
285 # LabEye/IFUNC files
286 self.tile = [ImageFile._Tile("raw", None, offs, (self.rawmode, 0, -1))]
288 @property
289 def n_frames(self) -> int:
290 return self.info[FRAMES]
292 @property
293 def is_animated(self) -> bool:
294 return self.info[FRAMES] > 1
296 def seek(self, frame: int) -> None:
297 if not self._seek_check(frame):
298 return
299 if isinstance(self._fp, DeferredError):
300 raise self._fp.ex
302 self.frame = frame
304 if self.mode == "1":
305 bits = 1
306 else:
307 bits = 8 * len(self.mode)
309 size = ((self.size[0] * bits + 7) // 8) * self.size[1]
310 offs = self.__offset + frame * size
312 self.fp = self._fp
314 self.tile = [ImageFile._Tile("raw", None, offs, (self.rawmode, 0, -1))]
316 def tell(self) -> int:
317 return self.frame
320#
321# --------------------------------------------------------------------
322# Save IM files
325SAVE = {
326 # mode: (im type, raw mode)
327 "1": ("0 1", "1"),
328 "L": ("Greyscale", "L"),
329 "LA": ("LA", "LA;L"),
330 "P": ("Greyscale", "P"),
331 "PA": ("LA", "PA;L"),
332 "I": ("L 32S", "I;32S"),
333 "I;16": ("L 16", "I;16"),
334 "I;16L": ("L 16L", "I;16L"),
335 "I;16B": ("L 16B", "I;16B"),
336 "F": ("L 32F", "F;32F"),
337 "RGB": ("RGB", "RGB;L"),
338 "RGBA": ("RGBA", "RGBA;L"),
339 "RGBX": ("RGBX", "RGBX;L"),
340 "CMYK": ("CMYK", "CMYK;L"),
341 "YCbCr": ("YCC", "YCbCr;L"),
342}
345def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
346 deprecate("IM image format", 15)
347 try:
348 image_type, rawmode = SAVE[im.mode]
349 except KeyError as e:
350 msg = f"Cannot save {im.mode} images as IM"
351 raise ValueError(msg) from e
353 frames = im.encoderinfo.get("frames", 1)
355 fp.write(f"Image type: {image_type} image\r\n".encode("ascii"))
356 if filename:
357 # Each line must be 100 characters or less,
358 # or: SyntaxError("not an IM file")
359 # 8 characters are used for "Name: " and "\r\n"
360 # Keep just the filename, ditch the potentially overlong path
361 if isinstance(filename, bytes):
362 filename = filename.decode("ascii")
363 name, ext = os.path.splitext(os.path.basename(filename))
364 name = "".join([name[: 92 - len(ext)], ext])
366 fp.write(f"Name: {name}\r\n".encode("ascii"))
367 fp.write(f"Image size (x*y): {im.size[0]}*{im.size[1]}\r\n".encode("ascii"))
368 fp.write(f"File size (no of images): {frames}\r\n".encode("ascii"))
369 if im.mode in ["P", "PA"]:
370 fp.write(b"Lut: 1\r\n")
371 fp.write(b"\000" * (511 - fp.tell()) + b"\032")
372 if im.mode in ["P", "PA"]:
373 im_palette = im.im.getpalette("RGB;L")
374 colors = len(im_palette) // 3
375 palette = b""
376 for i in range(3):
377 palette += im_palette[colors * i : colors * (i + 1)]
378 palette += b"\x00" * (256 - colors)
379 fp.write(palette) # 768 bytes
380 ImageFile._save(im, fp, [ImageFile._Tile("raw", args=(rawmode, 0, -1))])
383#
384# --------------------------------------------------------------------
385# Registry
388Image.register_open(ImImageFile.format, ImImageFile)
389Image.register_save(ImImageFile.format, _save)
391Image.register_extension(ImImageFile.format, ".im")