Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/PIL/ImImagePlugin.py: 56%

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

189 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 

29import os 

30import re 

31from typing import IO, Any 

32 

33from . import Image, ImageFile, ImagePalette 

34from ._util import DeferredError 

35 

36# -------------------------------------------------------------------- 

37# Standard tags 

38 

39COMMENT = "Comment" 

40DATE = "Date" 

41EQUIPMENT = "Digitalization equipment" 

42FRAMES = "File size (no of images)" 

43LUT = "Lut" 

44NAME = "Name" 

45SCALE = "Scale (x,y)" 

46SIZE = "Image size (x*y)" 

47MODE = "Image type" 

48 

49TAGS = { 

50 COMMENT: 0, 

51 DATE: 0, 

52 EQUIPMENT: 0, 

53 FRAMES: 0, 

54 LUT: 0, 

55 NAME: 0, 

56 SCALE: 0, 

57 SIZE: 0, 

58 MODE: 0, 

59} 

60 

61OPEN = { 

62 # ifunc93/p3cfunc formats 

63 "0 1 image": ("1", "1"), 

64 "L 1 image": ("1", "1"), 

65 "Greyscale image": ("L", "L"), 

66 "Grayscale image": ("L", "L"), 

67 "RGB image": ("RGB", "RGB;L"), 

68 "RLB image": ("RGB", "RLB"), 

69 "RYB image": ("RGB", "RLB"), 

70 "B1 image": ("1", "1"), 

71 "B2 image": ("P", "P;2"), 

72 "B4 image": ("P", "P;4"), 

73 "X 24 image": ("RGB", "RGB"), 

74 "L 32 S image": ("I", "I;32"), 

75 "L 32 F image": ("F", "F;32"), 

76 # old p3cfunc formats 

77 "RGB3 image": ("RGB", "RGB;T"), 

78 "RYB3 image": ("RGB", "RYB;T"), 

79 # extensions 

80 "LA image": ("LA", "LA;L"), 

81 "PA image": ("LA", "PA;L"), 

82 "RGBA image": ("RGBA", "RGBA;L"), 

83 "RGBX image": ("RGB", "RGBX;L"), 

84 "CMYK image": ("CMYK", "CMYK;L"), 

85 "YCC image": ("YCbCr", "YCbCr;L"), 

86} 

87 

88# ifunc95 extensions 

89for i in ["8", "8S", "16", "16S", "32", "32F"]: 

90 OPEN[f"L {i} image"] = ("F", f"F;{i}") 

91 OPEN[f"L*{i} image"] = ("F", f"F;{i}") 

92for i in ["16", "16L", "16B"]: 

93 OPEN[f"L {i} image"] = (f"I;{i}", f"I;{i}") 

94 OPEN[f"L*{i} image"] = (f"I;{i}", f"I;{i}") 

95for i in ["32S"]: 

96 OPEN[f"L {i} image"] = ("I", f"I;{i}") 

97 OPEN[f"L*{i} image"] = ("I", f"I;{i}") 

98for j in range(2, 33): 

99 OPEN[f"L*{j} image"] = ("F", f"F;{j}") 

100 

101 

102# -------------------------------------------------------------------- 

103# Read IM directory 

104 

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

106 

107 

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

109 try: 

110 return int(s) 

111 except ValueError: 

112 return float(s) 

113 

114 

115## 

116# Image plugin for the IFUNC IM file format. 

117 

118 

119class ImImageFile(ImageFile.ImageFile): 

120 format = "IM" 

121 format_description = "IFUNC Image Memory" 

122 _close_exclusive_fp_after_loading = False 

123 

124 def _open(self) -> None: 

125 # Quick rejection: if there's not an LF among the first 

126 # 100 bytes, this is (probably) not a text header. 

127 

128 assert self.fp is not None 

129 if b"\n" not in self.fp.read(100): 

130 msg = "not an IM file" 

131 raise SyntaxError(msg) 

132 self.fp.seek(0) 

133 

134 n = 0 

135 

136 # Default values 

137 self.info[MODE] = "L" 

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

139 self.info[FRAMES] = 1 

140 

141 self.rawmode = "L" 

142 

143 while True: 

144 s = self.fp.read(1) 

145 

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

147 if s == b"\r": 

148 continue 

149 

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

151 break 

152 

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

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

155 

156 if len(s) > 100: 

157 msg = "not an IM file" 

158 raise SyntaxError(msg) 

159 

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

161 s = s[:-2] 

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

163 s = s[:-1] 

164 

165 try: 

166 m = split.match(s) 

167 except re.error as e: 

168 msg = "not an IM file" 

169 raise SyntaxError(msg) from e 

170 

171 if m: 

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

173 

174 # Don't know if this is the correct encoding, 

175 # but a decent guess (I guess) 

176 k = k.decode("latin-1", "replace") 

177 v = v.decode("latin-1", "replace") 

178 

179 # Convert value as appropriate 

180 if k in [FRAMES, SCALE, SIZE]: 

181 v = v.replace("*", ",") 

182 v = tuple(map(number, v.split(","))) 

183 if len(v) == 1: 

184 v = v[0] 

185 elif k == MODE and v in OPEN: 

186 v, self.rawmode = OPEN[v] 

187 

188 # Add to dictionary. Note that COMMENT tags are 

189 # combined into a list of strings. 

190 if k == COMMENT: 

191 if k in self.info: 

192 self.info[k].append(v) 

193 else: 

194 self.info[k] = [v] 

195 else: 

196 self.info[k] = v 

197 

198 if k in TAGS: 

199 n += 1 

200 

201 else: 

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

203 raise SyntaxError(msg) 

204 

205 if not n: 

206 msg = "Not an IM file" 

207 raise SyntaxError(msg) 

208 

209 # Basic attributes 

210 self._size = self.info[SIZE] 

211 self._mode = self.info[MODE] 

212 

213 # Skip forward to start of image data 

214 while s and not s.startswith(b"\x1a"): 

215 s = self.fp.read(1) 

216 if not s: 

217 msg = "File truncated" 

218 raise SyntaxError(msg) 

219 

220 if LUT in self.info: 

221 # convert lookup table to palette or lut attribute 

222 palette = self.fp.read(768) 

223 greyscale = 1 # greyscale palette 

224 linear = 1 # linear greyscale palette 

225 for i in range(256): 

226 if palette[i] == palette[i + 256] == palette[i + 512]: 

227 if palette[i] != i: 

228 linear = 0 

229 else: 

230 greyscale = 0 

231 if self.mode in ["L", "LA", "P", "PA"]: 

232 if greyscale: 

233 if not linear: 

234 self.lut = list(palette[:256]) 

235 else: 

236 if self.mode in ["L", "P"]: 

237 self._mode = self.rawmode = "P" 

238 elif self.mode in ["LA", "PA"]: 

239 self._mode = "PA" 

240 self.rawmode = "PA;L" 

241 self.palette = ImagePalette.raw("RGB;L", palette) 

242 elif self.mode == "RGB": 

243 if not greyscale or not linear: 

244 self.lut = list(palette) 

245 

246 self.frame = 0 

247 

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

249 

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

251 

252 if self.rawmode.startswith("F;"): 

253 # ifunc95 formats 

254 try: 

255 # use bit decoder (if necessary) 

256 bits = int(self.rawmode[2:]) 

257 if bits not in [8, 16, 32]: 

258 self.tile = [ 

259 ImageFile._Tile( 

260 "bit", (0, 0) + self.size, offs, (bits, 8, 3, 0, -1) 

261 ) 

262 ] 

263 return 

264 except ValueError: 

265 pass 

266 

267 if self.rawmode in ["RGB;T", "RYB;T"]: 

268 # Old LabEye/3PC files. Would be very surprised if anyone 

269 # ever stumbled upon such a file ;-) 

270 size = self.size[0] * self.size[1] 

271 self.tile = [ 

272 ImageFile._Tile("raw", (0, 0) + self.size, offs, ("G", 0, -1)), 

273 ImageFile._Tile("raw", (0, 0) + self.size, offs + size, ("R", 0, -1)), 

274 ImageFile._Tile( 

275 "raw", (0, 0) + self.size, offs + 2 * size, ("B", 0, -1) 

276 ), 

277 ] 

278 else: 

279 # LabEye/IFUNC files 

280 self.tile = [ 

281 ImageFile._Tile("raw", (0, 0) + self.size, offs, (self.rawmode, 0, -1)) 

282 ] 

283 

284 @property 

285 def n_frames(self) -> int: 

286 return self.info[FRAMES] 

287 

288 @property 

289 def is_animated(self) -> bool: 

290 return self.info[FRAMES] > 1 

291 

292 def seek(self, frame: int) -> None: 

293 if not self._seek_check(frame): 

294 return 

295 if isinstance(self._fp, DeferredError): 

296 raise self._fp.ex 

297 

298 self.frame = frame 

299 

300 if self.mode == "1": 

301 bits = 1 

302 else: 

303 bits = 8 * len(self.mode) 

304 

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

306 offs = self.__offset + frame * size 

307 

308 self.fp = self._fp 

309 

310 self.tile = [ 

311 ImageFile._Tile("raw", (0, 0) + self.size, offs, (self.rawmode, 0, -1)) 

312 ] 

313 

314 def tell(self) -> int: 

315 return self.frame 

316 

317 

318# 

319# -------------------------------------------------------------------- 

320# Save IM files 

321 

322 

323SAVE = { 

324 # mode: (im type, raw mode) 

325 "1": ("0 1", "1"), 

326 "L": ("Greyscale", "L"), 

327 "LA": ("LA", "LA;L"), 

328 "P": ("Greyscale", "P"), 

329 "PA": ("LA", "PA;L"), 

330 "I": ("L 32S", "I;32S"), 

331 "I;16": ("L 16", "I;16"), 

332 "I;16L": ("L 16L", "I;16L"), 

333 "I;16B": ("L 16B", "I;16B"), 

334 "F": ("L 32F", "F;32F"), 

335 "RGB": ("RGB", "RGB;L"), 

336 "RGBA": ("RGBA", "RGBA;L"), 

337 "RGBX": ("RGBX", "RGBX;L"), 

338 "CMYK": ("CMYK", "CMYK;L"), 

339 "YCbCr": ("YCC", "YCbCr;L"), 

340} 

341 

342 

343def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: 

344 try: 

345 image_type, rawmode = SAVE[im.mode] 

346 except KeyError as e: 

347 msg = f"Cannot save {im.mode} images as IM" 

348 raise ValueError(msg) from e 

349 

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

351 

352 fp.write(f"Image type: {image_type} image\r\n".encode("ascii")) 

353 if filename: 

354 # Each line must be 100 characters or less, 

355 # or: SyntaxError("not an IM file") 

356 # 8 characters are used for "Name: " and "\r\n" 

357 # Keep just the filename, ditch the potentially overlong path 

358 if isinstance(filename, bytes): 

359 filename = filename.decode("ascii") 

360 name, ext = os.path.splitext(os.path.basename(filename)) 

361 name = "".join([name[: 92 - len(ext)], ext]) 

362 

363 fp.write(f"Name: {name}\r\n".encode("ascii")) 

364 fp.write(f"Image size (x*y): {im.size[0]}*{im.size[1]}\r\n".encode("ascii")) 

365 fp.write(f"File size (no of images): {frames}\r\n".encode("ascii")) 

366 if im.mode in ["P", "PA"]: 

367 fp.write(b"Lut: 1\r\n") 

368 fp.write(b"\000" * (511 - fp.tell()) + b"\032") 

369 if im.mode in ["P", "PA"]: 

370 im_palette = im.im.getpalette("RGB", "RGB;L") 

371 colors = len(im_palette) // 3 

372 palette = b"" 

373 for i in range(3): 

374 palette += im_palette[colors * i : colors * (i + 1)] 

375 palette += b"\x00" * (256 - colors) 

376 fp.write(palette) # 768 bytes 

377 ImageFile._save( 

378 im, fp, [ImageFile._Tile("raw", (0, 0) + im.size, 0, (rawmode, 0, -1))] 

379 ) 

380 

381 

382# 

383# -------------------------------------------------------------------- 

384# Registry 

385 

386 

387Image.register_open(ImImageFile.format, ImImageFile) 

388Image.register_save(ImImageFile.format, _save) 

389 

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