Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/PIL/PpmImagePlugin.py: 12%

209 statements  

« 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# PPM support for PIL 

6# 

7# History: 

8# 96-03-24 fl Created 

9# 98-03-06 fl Write RGBA images (as RGB, that is) 

10# 

11# Copyright (c) Secret Labs AB 1997-98. 

12# Copyright (c) Fredrik Lundh 1996. 

13# 

14# See the README file for information on usage and redistribution. 

15# 

16 

17 

18from . import Image, ImageFile 

19from ._binary import i16be as i16 

20from ._binary import o8 

21from ._binary import o32le as o32 

22 

23# 

24# -------------------------------------------------------------------- 

25 

26b_whitespace = b"\x20\x09\x0a\x0b\x0c\x0d" 

27 

28MODES = { 

29 # standard 

30 b"P1": "1", 

31 b"P2": "L", 

32 b"P3": "RGB", 

33 b"P4": "1", 

34 b"P5": "L", 

35 b"P6": "RGB", 

36 # extensions 

37 b"P0CMYK": "CMYK", 

38 # PIL extensions (for test purposes only) 

39 b"PyP": "P", 

40 b"PyRGBA": "RGBA", 

41 b"PyCMYK": "CMYK", 

42} 

43 

44 

45def _accept(prefix): 

46 return prefix[0:1] == b"P" and prefix[1] in b"0123456y" 

47 

48 

49## 

50# Image plugin for PBM, PGM, and PPM images. 

51 

52 

53class PpmImageFile(ImageFile.ImageFile): 

54 

55 format = "PPM" 

56 format_description = "Pbmplus image" 

57 

58 def _read_magic(self): 

59 magic = b"" 

60 # read until whitespace or longest available magic number 

61 for _ in range(6): 

62 c = self.fp.read(1) 

63 if not c or c in b_whitespace: 

64 break 

65 magic += c 

66 return magic 

67 

68 def _read_token(self): 

69 token = b"" 

70 while len(token) <= 10: # read until next whitespace or limit of 10 characters 

71 c = self.fp.read(1) 

72 if not c: 

73 break 

74 elif c in b_whitespace: # token ended 

75 if not token: 

76 # skip whitespace at start 

77 continue 

78 break 

79 elif c == b"#": 

80 # ignores rest of the line; stops at CR, LF or EOF 

81 while self.fp.read(1) not in b"\r\n": 

82 pass 

83 continue 

84 token += c 

85 if not token: 

86 # Token was not even 1 byte 

87 raise ValueError("Reached EOF while reading header") 

88 elif len(token) > 10: 

89 raise ValueError(f"Token too long in file header: {token.decode()}") 

90 return token 

91 

92 def _open(self): 

93 magic_number = self._read_magic() 

94 try: 

95 mode = MODES[magic_number] 

96 except KeyError: 

97 raise SyntaxError("not a PPM file") 

98 

99 if magic_number in (b"P1", b"P4"): 

100 self.custom_mimetype = "image/x-portable-bitmap" 

101 elif magic_number in (b"P2", b"P5"): 

102 self.custom_mimetype = "image/x-portable-graymap" 

103 elif magic_number in (b"P3", b"P6"): 

104 self.custom_mimetype = "image/x-portable-pixmap" 

105 

106 maxval = None 

107 decoder_name = "raw" 

108 if magic_number in (b"P1", b"P2", b"P3"): 

109 decoder_name = "ppm_plain" 

110 for ix in range(3): 

111 token = int(self._read_token()) 

112 if ix == 0: # token is the x size 

113 xsize = token 

114 elif ix == 1: # token is the y size 

115 ysize = token 

116 if mode == "1": 

117 self.mode = "1" 

118 rawmode = "1;I" 

119 break 

120 else: 

121 self.mode = rawmode = mode 

122 elif ix == 2: # token is maxval 

123 maxval = token 

124 if not 0 < maxval < 65536: 

125 raise ValueError( 

126 "maxval must be greater than 0 and less than 65536" 

127 ) 

128 if maxval > 255 and mode == "L": 

129 self.mode = "I" 

130 

131 if decoder_name != "ppm_plain": 

132 # If maxval matches a bit depth, use the raw decoder directly 

133 if maxval == 65535 and mode == "L": 

134 rawmode = "I;16B" 

135 elif maxval != 255: 

136 decoder_name = "ppm" 

137 

138 args = (rawmode, 0, 1) if decoder_name == "raw" else (rawmode, maxval) 

139 self._size = xsize, ysize 

140 self.tile = [(decoder_name, (0, 0, xsize, ysize), self.fp.tell(), args)] 

141 

142 

143# 

144# -------------------------------------------------------------------- 

145 

146 

147class PpmPlainDecoder(ImageFile.PyDecoder): 

148 _pulls_fd = True 

149 

150 def _read_block(self): 

151 return self.fd.read(ImageFile.SAFEBLOCK) 

152 

153 def _find_comment_end(self, block, start=0): 

154 a = block.find(b"\n", start) 

155 b = block.find(b"\r", start) 

156 return min(a, b) if a * b > 0 else max(a, b) # lowest nonnegative index (or -1) 

157 

158 def _ignore_comments(self, block): 

159 if self._comment_spans: 

160 # Finish current comment 

161 while block: 

162 comment_end = self._find_comment_end(block) 

163 if comment_end != -1: 

164 # Comment ends in this block 

165 # Delete tail of comment 

166 block = block[comment_end + 1 :] 

167 break 

168 else: 

169 # Comment spans whole block 

170 # So read the next block, looking for the end 

171 block = self._read_block() 

172 

173 # Search for any further comments 

174 self._comment_spans = False 

175 while True: 

176 comment_start = block.find(b"#") 

177 if comment_start == -1: 

178 # No comment found 

179 break 

180 comment_end = self._find_comment_end(block, comment_start) 

181 if comment_end != -1: 

182 # Comment ends in this block 

183 # Delete comment 

184 block = block[:comment_start] + block[comment_end + 1 :] 

185 else: 

186 # Comment continues to next block(s) 

187 block = block[:comment_start] 

188 self._comment_spans = True 

189 break 

190 return block 

191 

192 def _decode_bitonal(self): 

193 """ 

194 This is a separate method because in the plain PBM format, all data tokens are 

195 exactly one byte, so the inter-token whitespace is optional. 

196 """ 

197 data = bytearray() 

198 total_bytes = self.state.xsize * self.state.ysize 

199 

200 while len(data) != total_bytes: 

201 block = self._read_block() # read next block 

202 if not block: 

203 # eof 

204 break 

205 

206 block = self._ignore_comments(block) 

207 

208 tokens = b"".join(block.split()) 

209 for token in tokens: 

210 if token not in (48, 49): 

211 raise ValueError(f"Invalid token for this mode: {bytes([token])}") 

212 data = (data + tokens)[:total_bytes] 

213 invert = bytes.maketrans(b"01", b"\xFF\x00") 

214 return data.translate(invert) 

215 

216 def _decode_blocks(self, maxval): 

217 data = bytearray() 

218 max_len = 10 

219 out_byte_count = 4 if self.mode == "I" else 1 

220 out_max = 65535 if self.mode == "I" else 255 

221 bands = Image.getmodebands(self.mode) 

222 total_bytes = self.state.xsize * self.state.ysize * bands * out_byte_count 

223 

224 half_token = False 

225 while len(data) != total_bytes: 

226 block = self._read_block() # read next block 

227 if not block: 

228 if half_token: 

229 block = bytearray(b" ") # flush half_token 

230 else: 

231 # eof 

232 break 

233 

234 block = self._ignore_comments(block) 

235 

236 if half_token: 

237 block = half_token + block # stitch half_token to new block 

238 

239 tokens = block.split() 

240 

241 if block and not block[-1:].isspace(): # block might split token 

242 half_token = tokens.pop() # save half token for later 

243 if len(half_token) > max_len: # prevent buildup of half_token 

244 raise ValueError( 

245 f"Token too long found in data: {half_token[:max_len + 1]}" 

246 ) 

247 

248 for token in tokens: 

249 if len(token) > max_len: 

250 raise ValueError( 

251 f"Token too long found in data: {token[:max_len + 1]}" 

252 ) 

253 value = int(token) 

254 if value > maxval: 

255 raise ValueError(f"Channel value too large for this mode: {value}") 

256 value = round(value / maxval * out_max) 

257 data += o32(value) if self.mode == "I" else o8(value) 

258 if len(data) == total_bytes: # finished! 

259 break 

260 return data 

261 

262 def decode(self, buffer): 

263 self._comment_spans = False 

264 if self.mode == "1": 

265 data = self._decode_bitonal() 

266 rawmode = "1;8" 

267 else: 

268 maxval = self.args[-1] 

269 data = self._decode_blocks(maxval) 

270 rawmode = "I;32" if self.mode == "I" else self.mode 

271 self.set_as_raw(bytes(data), rawmode) 

272 return -1, 0 

273 

274 

275class PpmDecoder(ImageFile.PyDecoder): 

276 _pulls_fd = True 

277 

278 def decode(self, buffer): 

279 data = bytearray() 

280 maxval = self.args[-1] 

281 in_byte_count = 1 if maxval < 256 else 2 

282 out_byte_count = 4 if self.mode == "I" else 1 

283 out_max = 65535 if self.mode == "I" else 255 

284 bands = Image.getmodebands(self.mode) 

285 while len(data) < self.state.xsize * self.state.ysize * bands * out_byte_count: 

286 pixels = self.fd.read(in_byte_count * bands) 

287 if len(pixels) < in_byte_count * bands: 

288 # eof 

289 break 

290 for b in range(bands): 

291 value = ( 

292 pixels[b] if in_byte_count == 1 else i16(pixels, b * in_byte_count) 

293 ) 

294 value = min(out_max, round(value / maxval * out_max)) 

295 data += o32(value) if self.mode == "I" else o8(value) 

296 rawmode = "I;32" if self.mode == "I" else self.mode 

297 self.set_as_raw(bytes(data), rawmode) 

298 return -1, 0 

299 

300 

301# 

302# -------------------------------------------------------------------- 

303 

304 

305def _save(im, fp, filename): 

306 if im.mode == "1": 

307 rawmode, head = "1;I", b"P4" 

308 elif im.mode == "L": 

309 rawmode, head = "L", b"P5" 

310 elif im.mode == "I": 

311 rawmode, head = "I;16B", b"P5" 

312 elif im.mode in ("RGB", "RGBA"): 

313 rawmode, head = "RGB", b"P6" 

314 else: 

315 raise OSError(f"cannot write mode {im.mode} as PPM") 

316 fp.write(head + b"\n%d %d\n" % im.size) 

317 if head == b"P6": 

318 fp.write(b"255\n") 

319 elif head == b"P5": 

320 if rawmode == "L": 

321 fp.write(b"255\n") 

322 else: 

323 fp.write(b"65535\n") 

324 ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, 1))]) 

325 

326 # ALTERNATIVE: save via builtin debug function 

327 # im._dump(filename) 

328 

329 

330# 

331# -------------------------------------------------------------------- 

332 

333 

334Image.register_open(PpmImageFile.format, PpmImageFile, _accept) 

335Image.register_save(PpmImageFile.format, _save) 

336 

337Image.register_decoder("ppm", PpmDecoder) 

338Image.register_decoder("ppm_plain", PpmPlainDecoder) 

339 

340Image.register_extensions(PpmImageFile.format, [".pbm", ".pgm", ".ppm", ".pnm"]) 

341 

342Image.register_mime(PpmImageFile.format, "image/x-portable-anymap")