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

196 statements  

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 

28 

29__lazy_modules__ = {"PIL._util"} 

30 

31import os 

32import re 

33 

34from . import Image, ImageFile, ImagePalette 

35from ._deprecate import deprecate 

36from ._util import DeferredError 

37 

38TYPE_CHECKING = False 

39if TYPE_CHECKING: 

40 from typing import IO, Any 

41 

42 

43# -------------------------------------------------------------------- 

44# Standard tags 

45 

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" 

55 

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} 

67 

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} 

94 

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}") 

107 

108 

109# -------------------------------------------------------------------- 

110# Read IM directory 

111 

112split = re.compile(rb"^([A-Za-z][^:]*):[ \t]*(.*)[ \t]*$") 

113 

114 

115def number(s: Any) -> float: 

116 deprecate("IM image format", 15) 

117 try: 

118 return int(s) 

119 except ValueError: 

120 return float(s) 

121 

122 

123## 

124# Image plugin for the IFUNC IM file format. 

125 

126 

127class ImImageFile(ImageFile.ImageFile): 

128 format = "IM" 

129 format_description = "IFUNC Image Memory" 

130 _close_exclusive_fp_after_loading = False 

131 

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. 

135 

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) 

141 

142 n = 0 

143 

144 # Default values 

145 self.info[MODE] = "L" 

146 self.info[SIZE] = (512, 512) 

147 self.info[FRAMES] = 1 

148 

149 self.rawmode = "L" 

150 

151 while True: 

152 s = self.fp.read(1) 

153 

154 # Some versions of IFUNC uses \n\r instead of \r\n... 

155 if s == b"\r": 

156 continue 

157 

158 if not s or s == b"\0" or s == b"\x1a": 

159 break 

160 

161 # FIXME: this may read whole file if not a text file 

162 s = s + self.fp.readline() 

163 

164 if len(s) > 100: 

165 msg = "not an IM file" 

166 raise SyntaxError(msg) 

167 

168 if s.endswith(b"\r\n"): 

169 s = s[:-2] 

170 elif s.endswith(b"\n"): 

171 s = s[:-1] 

172 

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 

178 

179 if m: 

180 k, v = m.group(1, 2) 

181 

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") 

186 

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] 

195 

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 

205 

206 if k in TAGS: 

207 n += 1 

208 

209 else: 

210 msg = f"Syntax error in IM header: {s.decode('ascii', 'replace')}" 

211 raise SyntaxError(msg) 

212 

213 if not n: 

214 msg = "Not an IM file" 

215 raise SyntaxError(msg) 

216 

217 deprecate("IM image format", 15) 

218 

219 # Basic attributes 

220 self._size = self.info[SIZE] 

221 self._mode = self.info[MODE] 

222 

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) 

229 

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) 

255 

256 self.frame = 0 

257 

258 self.__offset = offs = self.fp.tell() 

259 

260 self._fp = self.fp # FIXME: hack 

261 

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 

274 

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))] 

287 

288 @property 

289 def n_frames(self) -> int: 

290 return self.info[FRAMES] 

291 

292 @property 

293 def is_animated(self) -> bool: 

294 return self.info[FRAMES] > 1 

295 

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 

301 

302 self.frame = frame 

303 

304 if self.mode == "1": 

305 bits = 1 

306 else: 

307 bits = 8 * len(self.mode) 

308 

309 size = ((self.size[0] * bits + 7) // 8) * self.size[1] 

310 offs = self.__offset + frame * size 

311 

312 self.fp = self._fp 

313 

314 self.tile = [ImageFile._Tile("raw", None, offs, (self.rawmode, 0, -1))] 

315 

316 def tell(self) -> int: 

317 return self.frame 

318 

319 

320# 

321# -------------------------------------------------------------------- 

322# Save IM files 

323 

324 

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} 

343 

344 

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 

352 

353 frames = im.encoderinfo.get("frames", 1) 

354 

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]) 

365 

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))]) 

381 

382 

383# 

384# -------------------------------------------------------------------- 

385# Registry 

386 

387 

388Image.register_open(ImImageFile.format, ImImageFile) 

389Image.register_save(ImImageFile.format, _save) 

390 

391Image.register_extension(ImImageFile.format, ".im")