Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/PIL/ImImagePlugin.py: 22%
166 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# 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#
29import os
30import re
32from . import Image, ImageFile, ImagePalette
34# --------------------------------------------------------------------
35# Standard tags
37COMMENT = "Comment"
38DATE = "Date"
39EQUIPMENT = "Digitalization equipment"
40FRAMES = "File size (no of images)"
41LUT = "Lut"
42NAME = "Name"
43SCALE = "Scale (x,y)"
44SIZE = "Image size (x*y)"
45MODE = "Image type"
47TAGS = {
48 COMMENT: 0,
49 DATE: 0,
50 EQUIPMENT: 0,
51 FRAMES: 0,
52 LUT: 0,
53 NAME: 0,
54 SCALE: 0,
55 SIZE: 0,
56 MODE: 0,
57}
59OPEN = {
60 # ifunc93/p3cfunc formats
61 "0 1 image": ("1", "1"),
62 "L 1 image": ("1", "1"),
63 "Greyscale image": ("L", "L"),
64 "Grayscale image": ("L", "L"),
65 "RGB image": ("RGB", "RGB;L"),
66 "RLB image": ("RGB", "RLB"),
67 "RYB image": ("RGB", "RLB"),
68 "B1 image": ("1", "1"),
69 "B2 image": ("P", "P;2"),
70 "B4 image": ("P", "P;4"),
71 "X 24 image": ("RGB", "RGB"),
72 "L 32 S image": ("I", "I;32"),
73 "L 32 F image": ("F", "F;32"),
74 # old p3cfunc formats
75 "RGB3 image": ("RGB", "RGB;T"),
76 "RYB3 image": ("RGB", "RYB;T"),
77 # extensions
78 "LA image": ("LA", "LA;L"),
79 "PA image": ("LA", "PA;L"),
80 "RGBA image": ("RGBA", "RGBA;L"),
81 "RGBX image": ("RGBX", "RGBX;L"),
82 "CMYK image": ("CMYK", "CMYK;L"),
83 "YCC image": ("YCbCr", "YCbCr;L"),
84}
86# ifunc95 extensions
87for i in ["8", "8S", "16", "16S", "32", "32F"]:
88 OPEN[f"L {i} image"] = ("F", f"F;{i}")
89 OPEN[f"L*{i} image"] = ("F", f"F;{i}")
90for i in ["16", "16L", "16B"]:
91 OPEN[f"L {i} image"] = (f"I;{i}", f"I;{i}")
92 OPEN[f"L*{i} image"] = (f"I;{i}", f"I;{i}")
93for i in ["32S"]:
94 OPEN[f"L {i} image"] = ("I", f"I;{i}")
95 OPEN[f"L*{i} image"] = ("I", f"I;{i}")
96for i in range(2, 33):
97 OPEN[f"L*{i} image"] = ("F", f"F;{i}")
100# --------------------------------------------------------------------
101# Read IM directory
103split = re.compile(rb"^([A-Za-z][^:]*):[ \t]*(.*)[ \t]*$")
106def number(s):
107 try:
108 return int(s)
109 except ValueError:
110 return float(s)
113##
114# Image plugin for the IFUNC IM file format.
117class ImImageFile(ImageFile.ImageFile):
119 format = "IM"
120 format_description = "IFUNC Image Memory"
121 _close_exclusive_fp_after_loading = False
123 def _open(self):
125 # Quick rejection: if there's not an LF among the first
126 # 100 bytes, this is (probably) not a text header.
128 if b"\n" not in self.fp.read(100):
129 raise SyntaxError("not an IM file")
130 self.fp.seek(0)
132 n = 0
134 # Default values
135 self.info[MODE] = "L"
136 self.info[SIZE] = (512, 512)
137 self.info[FRAMES] = 1
139 self.rawmode = "L"
141 while True:
143 s = self.fp.read(1)
145 # Some versions of IFUNC uses \n\r instead of \r\n...
146 if s == b"\r":
147 continue
149 if not s or s == b"\0" or s == b"\x1A":
150 break
152 # FIXME: this may read whole file if not a text file
153 s = s + self.fp.readline()
155 if len(s) > 100:
156 raise SyntaxError("not an IM file")
158 if s[-2:] == b"\r\n":
159 s = s[:-2]
160 elif s[-1:] == b"\n":
161 s = s[:-1]
163 try:
164 m = split.match(s)
165 except re.error as e:
166 raise SyntaxError("not an IM file") from e
168 if m:
170 k, v = m.group(1, 2)
172 # Don't know if this is the correct encoding,
173 # but a decent guess (I guess)
174 k = k.decode("latin-1", "replace")
175 v = v.decode("latin-1", "replace")
177 # Convert value as appropriate
178 if k in [FRAMES, SCALE, SIZE]:
179 v = v.replace("*", ",")
180 v = tuple(map(number, v.split(",")))
181 if len(v) == 1:
182 v = v[0]
183 elif k == MODE and v in OPEN:
184 v, self.rawmode = OPEN[v]
186 # Add to dictionary. Note that COMMENT tags are
187 # combined into a list of strings.
188 if k == COMMENT:
189 if k in self.info:
190 self.info[k].append(v)
191 else:
192 self.info[k] = [v]
193 else:
194 self.info[k] = v
196 if k in TAGS:
197 n += 1
199 else:
201 raise SyntaxError(
202 "Syntax error in IM header: " + s.decode("ascii", "replace")
203 )
205 if not n:
206 raise SyntaxError("Not an IM file")
208 # Basic attributes
209 self._size = self.info[SIZE]
210 self.mode = self.info[MODE]
212 # Skip forward to start of image data
213 while s and s[:1] != b"\x1A":
214 s = self.fp.read(1)
215 if not s:
216 raise SyntaxError("File truncated")
218 if LUT in self.info:
219 # convert lookup table to palette or lut attribute
220 palette = self.fp.read(768)
221 greyscale = 1 # greyscale palette
222 linear = 1 # linear greyscale palette
223 for i in range(256):
224 if palette[i] == palette[i + 256] == palette[i + 512]:
225 if palette[i] != i:
226 linear = 0
227 else:
228 greyscale = 0
229 if self.mode in ["L", "LA", "P", "PA"]:
230 if greyscale:
231 if not linear:
232 self.lut = list(palette[:256])
233 else:
234 if self.mode in ["L", "P"]:
235 self.mode = self.rawmode = "P"
236 elif self.mode in ["LA", "PA"]:
237 self.mode = "PA"
238 self.rawmode = "PA;L"
239 self.palette = ImagePalette.raw("RGB;L", palette)
240 elif self.mode == "RGB":
241 if not greyscale or not linear:
242 self.lut = list(palette)
244 self.frame = 0
246 self.__offset = offs = self.fp.tell()
248 self._fp = self.fp # FIXME: hack
250 if self.rawmode[:2] == "F;":
252 # ifunc95 formats
253 try:
254 # use bit decoder (if necessary)
255 bits = int(self.rawmode[2:])
256 if bits not in [8, 16, 32]:
257 self.tile = [("bit", (0, 0) + self.size, offs, (bits, 8, 3, 0, -1))]
258 return
259 except ValueError:
260 pass
262 if self.rawmode in ["RGB;T", "RYB;T"]:
263 # Old LabEye/3PC files. Would be very surprised if anyone
264 # ever stumbled upon such a file ;-)
265 size = self.size[0] * self.size[1]
266 self.tile = [
267 ("raw", (0, 0) + self.size, offs, ("G", 0, -1)),
268 ("raw", (0, 0) + self.size, offs + size, ("R", 0, -1)),
269 ("raw", (0, 0) + self.size, offs + 2 * size, ("B", 0, -1)),
270 ]
271 else:
272 # LabEye/IFUNC files
273 self.tile = [("raw", (0, 0) + self.size, offs, (self.rawmode, 0, -1))]
275 @property
276 def n_frames(self):
277 return self.info[FRAMES]
279 @property
280 def is_animated(self):
281 return self.info[FRAMES] > 1
283 def seek(self, frame):
284 if not self._seek_check(frame):
285 return
287 self.frame = frame
289 if self.mode == "1":
290 bits = 1
291 else:
292 bits = 8 * len(self.mode)
294 size = ((self.size[0] * bits + 7) // 8) * self.size[1]
295 offs = self.__offset + frame * size
297 self.fp = self._fp
299 self.tile = [("raw", (0, 0) + self.size, offs, (self.rawmode, 0, -1))]
301 def tell(self):
302 return self.frame
305#
306# --------------------------------------------------------------------
307# Save IM files
310SAVE = {
311 # mode: (im type, raw mode)
312 "1": ("0 1", "1"),
313 "L": ("Greyscale", "L"),
314 "LA": ("LA", "LA;L"),
315 "P": ("Greyscale", "P"),
316 "PA": ("LA", "PA;L"),
317 "I": ("L 32S", "I;32S"),
318 "I;16": ("L 16", "I;16"),
319 "I;16L": ("L 16L", "I;16L"),
320 "I;16B": ("L 16B", "I;16B"),
321 "F": ("L 32F", "F;32F"),
322 "RGB": ("RGB", "RGB;L"),
323 "RGBA": ("RGBA", "RGBA;L"),
324 "RGBX": ("RGBX", "RGBX;L"),
325 "CMYK": ("CMYK", "CMYK;L"),
326 "YCbCr": ("YCC", "YCbCr;L"),
327}
330def _save(im, fp, filename):
332 try:
333 image_type, rawmode = SAVE[im.mode]
334 except KeyError as e:
335 raise ValueError(f"Cannot save {im.mode} images as IM") from e
337 frames = im.encoderinfo.get("frames", 1)
339 fp.write(f"Image type: {image_type} image\r\n".encode("ascii"))
340 if filename:
341 # Each line must be 100 characters or less,
342 # or: SyntaxError("not an IM file")
343 # 8 characters are used for "Name: " and "\r\n"
344 # Keep just the filename, ditch the potentially overlong path
345 name, ext = os.path.splitext(os.path.basename(filename))
346 name = "".join([name[: 92 - len(ext)], ext])
348 fp.write(f"Name: {name}\r\n".encode("ascii"))
349 fp.write(("Image size (x*y): %d*%d\r\n" % im.size).encode("ascii"))
350 fp.write(f"File size (no of images): {frames}\r\n".encode("ascii"))
351 if im.mode in ["P", "PA"]:
352 fp.write(b"Lut: 1\r\n")
353 fp.write(b"\000" * (511 - fp.tell()) + b"\032")
354 if im.mode in ["P", "PA"]:
355 fp.write(im.im.getpalette("RGB", "RGB;L")) # 768 bytes
356 ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, -1))])
359#
360# --------------------------------------------------------------------
361# Registry
364Image.register_open(ImImageFile.format, ImImageFile)
365Image.register_save(ImImageFile.format, _save)
367Image.register_extension(ImImageFile.format, ".im")