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

413 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# JPEG (JFIF) file handling 

6# 

7# See "Digital Compression and Coding of Continuous-Tone Still Images, 

8# Part 1, Requirements and Guidelines" (CCITT T.81 / ISO 10918-1) 

9# 

10# History: 

11# 1995-09-09 fl Created 

12# 1995-09-13 fl Added full parser 

13# 1996-03-25 fl Added hack to use the IJG command line utilities 

14# 1996-05-05 fl Workaround Photoshop 2.5 CMYK polarity bug 

15# 1996-05-28 fl Added draft support, JFIF version (0.1) 

16# 1996-12-30 fl Added encoder options, added progression property (0.2) 

17# 1997-08-27 fl Save mode 1 images as BW (0.3) 

18# 1998-07-12 fl Added YCbCr to draft and save methods (0.4) 

19# 1998-10-19 fl Don't hang on files using 16-bit DQT's (0.4.1) 

20# 2001-04-16 fl Extract DPI settings from JFIF files (0.4.2) 

21# 2002-07-01 fl Skip pad bytes before markers; identify Exif files (0.4.3) 

22# 2003-04-25 fl Added experimental EXIF decoder (0.5) 

23# 2003-06-06 fl Added experimental EXIF GPSinfo decoder 

24# 2003-09-13 fl Extract COM markers 

25# 2009-09-06 fl Added icc_profile support (from Florian Hoech) 

26# 2009-03-06 fl Changed CMYK handling; always use Adobe polarity (0.6) 

27# 2009-03-08 fl Added subsampling support (from Justin Huff). 

28# 

29# Copyright (c) 1997-2003 by Secret Labs AB. 

30# Copyright (c) 1995-1996 by Fredrik Lundh. 

31# 

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

33# 

34import array 

35import io 

36import math 

37import os 

38import struct 

39import subprocess 

40import sys 

41import tempfile 

42import warnings 

43 

44from . import Image, ImageFile, TiffImagePlugin 

45from ._binary import i16be as i16 

46from ._binary import i32be as i32 

47from ._binary import o8 

48from ._deprecate import deprecate 

49from .JpegPresets import presets 

50 

51# 

52# Parser 

53 

54 

55def Skip(self, marker): 

56 n = i16(self.fp.read(2)) - 2 

57 ImageFile._safe_read(self.fp, n) 

58 

59 

60def APP(self, marker): 

61 # 

62 # Application marker. Store these in the APP dictionary. 

63 # Also look for well-known application markers. 

64 

65 n = i16(self.fp.read(2)) - 2 

66 s = ImageFile._safe_read(self.fp, n) 

67 

68 app = "APP%d" % (marker & 15) 

69 

70 self.app[app] = s # compatibility 

71 self.applist.append((app, s)) 

72 

73 if marker == 0xFFE0 and s[:4] == b"JFIF": 

74 # extract JFIF information 

75 self.info["jfif"] = version = i16(s, 5) # version 

76 self.info["jfif_version"] = divmod(version, 256) 

77 # extract JFIF properties 

78 try: 

79 jfif_unit = s[7] 

80 jfif_density = i16(s, 8), i16(s, 10) 

81 except Exception: 

82 pass 

83 else: 

84 if jfif_unit == 1: 

85 self.info["dpi"] = jfif_density 

86 self.info["jfif_unit"] = jfif_unit 

87 self.info["jfif_density"] = jfif_density 

88 elif marker == 0xFFE1 and s[:5] == b"Exif\0": 

89 if "exif" not in self.info: 

90 # extract EXIF information (incomplete) 

91 self.info["exif"] = s # FIXME: value will change 

92 elif marker == 0xFFE2 and s[:5] == b"FPXR\0": 

93 # extract FlashPix information (incomplete) 

94 self.info["flashpix"] = s # FIXME: value will change 

95 elif marker == 0xFFE2 and s[:12] == b"ICC_PROFILE\0": 

96 # Since an ICC profile can be larger than the maximum size of 

97 # a JPEG marker (64K), we need provisions to split it into 

98 # multiple markers. The format defined by the ICC specifies 

99 # one or more APP2 markers containing the following data: 

100 # Identifying string ASCII "ICC_PROFILE\0" (12 bytes) 

101 # Marker sequence number 1, 2, etc (1 byte) 

102 # Number of markers Total of APP2's used (1 byte) 

103 # Profile data (remainder of APP2 data) 

104 # Decoders should use the marker sequence numbers to 

105 # reassemble the profile, rather than assuming that the APP2 

106 # markers appear in the correct sequence. 

107 self.icclist.append(s) 

108 elif marker == 0xFFED and s[:14] == b"Photoshop 3.0\x00": 

109 # parse the image resource block 

110 offset = 14 

111 photoshop = self.info.setdefault("photoshop", {}) 

112 while s[offset : offset + 4] == b"8BIM": 

113 try: 

114 offset += 4 

115 # resource code 

116 code = i16(s, offset) 

117 offset += 2 

118 # resource name (usually empty) 

119 name_len = s[offset] 

120 # name = s[offset+1:offset+1+name_len] 

121 offset += 1 + name_len 

122 offset += offset & 1 # align 

123 # resource data block 

124 size = i32(s, offset) 

125 offset += 4 

126 data = s[offset : offset + size] 

127 if code == 0x03ED: # ResolutionInfo 

128 data = { 

129 "XResolution": i32(data, 0) / 65536, 

130 "DisplayedUnitsX": i16(data, 4), 

131 "YResolution": i32(data, 8) / 65536, 

132 "DisplayedUnitsY": i16(data, 12), 

133 } 

134 photoshop[code] = data 

135 offset += size 

136 offset += offset & 1 # align 

137 except struct.error: 

138 break # insufficient data 

139 

140 elif marker == 0xFFEE and s[:5] == b"Adobe": 

141 self.info["adobe"] = i16(s, 5) 

142 # extract Adobe custom properties 

143 try: 

144 adobe_transform = s[11] 

145 except IndexError: 

146 pass 

147 else: 

148 self.info["adobe_transform"] = adobe_transform 

149 elif marker == 0xFFE2 and s[:4] == b"MPF\0": 

150 # extract MPO information 

151 self.info["mp"] = s[4:] 

152 # offset is current location minus buffer size 

153 # plus constant header size 

154 self.info["mpoffset"] = self.fp.tell() - n + 4 

155 

156 # If DPI isn't in JPEG header, fetch from EXIF 

157 if "dpi" not in self.info and "exif" in self.info: 

158 try: 

159 exif = self.getexif() 

160 resolution_unit = exif[0x0128] 

161 x_resolution = exif[0x011A] 

162 try: 

163 dpi = float(x_resolution[0]) / x_resolution[1] 

164 except TypeError: 

165 dpi = x_resolution 

166 if math.isnan(dpi): 

167 raise ValueError 

168 if resolution_unit == 3: # cm 

169 # 1 dpcm = 2.54 dpi 

170 dpi *= 2.54 

171 self.info["dpi"] = dpi, dpi 

172 except (TypeError, KeyError, SyntaxError, ValueError, ZeroDivisionError): 

173 # SyntaxError for invalid/unreadable EXIF 

174 # KeyError for dpi not included 

175 # ZeroDivisionError for invalid dpi rational value 

176 # ValueError or TypeError for dpi being an invalid float 

177 self.info["dpi"] = 72, 72 

178 

179 

180def COM(self, marker): 

181 # 

182 # Comment marker. Store these in the APP dictionary. 

183 n = i16(self.fp.read(2)) - 2 

184 s = ImageFile._safe_read(self.fp, n) 

185 

186 self.info["comment"] = s 

187 self.app["COM"] = s # compatibility 

188 self.applist.append(("COM", s)) 

189 

190 

191def SOF(self, marker): 

192 # 

193 # Start of frame marker. Defines the size and mode of the 

194 # image. JPEG is colour blind, so we use some simple 

195 # heuristics to map the number of layers to an appropriate 

196 # mode. Note that this could be made a bit brighter, by 

197 # looking for JFIF and Adobe APP markers. 

198 

199 n = i16(self.fp.read(2)) - 2 

200 s = ImageFile._safe_read(self.fp, n) 

201 self._size = i16(s, 3), i16(s, 1) 

202 

203 self.bits = s[0] 

204 if self.bits != 8: 

205 raise SyntaxError(f"cannot handle {self.bits}-bit layers") 

206 

207 self.layers = s[5] 

208 if self.layers == 1: 

209 self.mode = "L" 

210 elif self.layers == 3: 

211 self.mode = "RGB" 

212 elif self.layers == 4: 

213 self.mode = "CMYK" 

214 else: 

215 raise SyntaxError(f"cannot handle {self.layers}-layer images") 

216 

217 if marker in [0xFFC2, 0xFFC6, 0xFFCA, 0xFFCE]: 

218 self.info["progressive"] = self.info["progression"] = 1 

219 

220 if self.icclist: 

221 # fixup icc profile 

222 self.icclist.sort() # sort by sequence number 

223 if self.icclist[0][13] == len(self.icclist): 

224 profile = [] 

225 for p in self.icclist: 

226 profile.append(p[14:]) 

227 icc_profile = b"".join(profile) 

228 else: 

229 icc_profile = None # wrong number of fragments 

230 self.info["icc_profile"] = icc_profile 

231 self.icclist = [] 

232 

233 for i in range(6, len(s), 3): 

234 t = s[i : i + 3] 

235 # 4-tuples: id, vsamp, hsamp, qtable 

236 self.layer.append((t[0], t[1] // 16, t[1] & 15, t[2])) 

237 

238 

239def DQT(self, marker): 

240 # 

241 # Define quantization table. Note that there might be more 

242 # than one table in each marker. 

243 

244 # FIXME: The quantization tables can be used to estimate the 

245 # compression quality. 

246 

247 n = i16(self.fp.read(2)) - 2 

248 s = ImageFile._safe_read(self.fp, n) 

249 while len(s): 

250 v = s[0] 

251 precision = 1 if (v // 16 == 0) else 2 # in bytes 

252 qt_length = 1 + precision * 64 

253 if len(s) < qt_length: 

254 raise SyntaxError("bad quantization table marker") 

255 data = array.array("B" if precision == 1 else "H", s[1:qt_length]) 

256 if sys.byteorder == "little" and precision > 1: 

257 data.byteswap() # the values are always big-endian 

258 self.quantization[v & 15] = [data[i] for i in zigzag_index] 

259 s = s[qt_length:] 

260 

261 

262# 

263# JPEG marker table 

264 

265MARKER = { 

266 0xFFC0: ("SOF0", "Baseline DCT", SOF), 

267 0xFFC1: ("SOF1", "Extended Sequential DCT", SOF), 

268 0xFFC2: ("SOF2", "Progressive DCT", SOF), 

269 0xFFC3: ("SOF3", "Spatial lossless", SOF), 

270 0xFFC4: ("DHT", "Define Huffman table", Skip), 

271 0xFFC5: ("SOF5", "Differential sequential DCT", SOF), 

272 0xFFC6: ("SOF6", "Differential progressive DCT", SOF), 

273 0xFFC7: ("SOF7", "Differential spatial", SOF), 

274 0xFFC8: ("JPG", "Extension", None), 

275 0xFFC9: ("SOF9", "Extended sequential DCT (AC)", SOF), 

276 0xFFCA: ("SOF10", "Progressive DCT (AC)", SOF), 

277 0xFFCB: ("SOF11", "Spatial lossless DCT (AC)", SOF), 

278 0xFFCC: ("DAC", "Define arithmetic coding conditioning", Skip), 

279 0xFFCD: ("SOF13", "Differential sequential DCT (AC)", SOF), 

280 0xFFCE: ("SOF14", "Differential progressive DCT (AC)", SOF), 

281 0xFFCF: ("SOF15", "Differential spatial (AC)", SOF), 

282 0xFFD0: ("RST0", "Restart 0", None), 

283 0xFFD1: ("RST1", "Restart 1", None), 

284 0xFFD2: ("RST2", "Restart 2", None), 

285 0xFFD3: ("RST3", "Restart 3", None), 

286 0xFFD4: ("RST4", "Restart 4", None), 

287 0xFFD5: ("RST5", "Restart 5", None), 

288 0xFFD6: ("RST6", "Restart 6", None), 

289 0xFFD7: ("RST7", "Restart 7", None), 

290 0xFFD8: ("SOI", "Start of image", None), 

291 0xFFD9: ("EOI", "End of image", None), 

292 0xFFDA: ("SOS", "Start of scan", Skip), 

293 0xFFDB: ("DQT", "Define quantization table", DQT), 

294 0xFFDC: ("DNL", "Define number of lines", Skip), 

295 0xFFDD: ("DRI", "Define restart interval", Skip), 

296 0xFFDE: ("DHP", "Define hierarchical progression", SOF), 

297 0xFFDF: ("EXP", "Expand reference component", Skip), 

298 0xFFE0: ("APP0", "Application segment 0", APP), 

299 0xFFE1: ("APP1", "Application segment 1", APP), 

300 0xFFE2: ("APP2", "Application segment 2", APP), 

301 0xFFE3: ("APP3", "Application segment 3", APP), 

302 0xFFE4: ("APP4", "Application segment 4", APP), 

303 0xFFE5: ("APP5", "Application segment 5", APP), 

304 0xFFE6: ("APP6", "Application segment 6", APP), 

305 0xFFE7: ("APP7", "Application segment 7", APP), 

306 0xFFE8: ("APP8", "Application segment 8", APP), 

307 0xFFE9: ("APP9", "Application segment 9", APP), 

308 0xFFEA: ("APP10", "Application segment 10", APP), 

309 0xFFEB: ("APP11", "Application segment 11", APP), 

310 0xFFEC: ("APP12", "Application segment 12", APP), 

311 0xFFED: ("APP13", "Application segment 13", APP), 

312 0xFFEE: ("APP14", "Application segment 14", APP), 

313 0xFFEF: ("APP15", "Application segment 15", APP), 

314 0xFFF0: ("JPG0", "Extension 0", None), 

315 0xFFF1: ("JPG1", "Extension 1", None), 

316 0xFFF2: ("JPG2", "Extension 2", None), 

317 0xFFF3: ("JPG3", "Extension 3", None), 

318 0xFFF4: ("JPG4", "Extension 4", None), 

319 0xFFF5: ("JPG5", "Extension 5", None), 

320 0xFFF6: ("JPG6", "Extension 6", None), 

321 0xFFF7: ("JPG7", "Extension 7", None), 

322 0xFFF8: ("JPG8", "Extension 8", None), 

323 0xFFF9: ("JPG9", "Extension 9", None), 

324 0xFFFA: ("JPG10", "Extension 10", None), 

325 0xFFFB: ("JPG11", "Extension 11", None), 

326 0xFFFC: ("JPG12", "Extension 12", None), 

327 0xFFFD: ("JPG13", "Extension 13", None), 

328 0xFFFE: ("COM", "Comment", COM), 

329} 

330 

331 

332def _accept(prefix): 

333 # Magic number was taken from https://en.wikipedia.org/wiki/JPEG 

334 return prefix[:3] == b"\xFF\xD8\xFF" 

335 

336 

337## 

338# Image plugin for JPEG and JFIF images. 

339 

340 

341class JpegImageFile(ImageFile.ImageFile): 

342 

343 format = "JPEG" 

344 format_description = "JPEG (ISO 10918)" 

345 

346 def _open(self): 

347 

348 s = self.fp.read(3) 

349 

350 if not _accept(s): 

351 raise SyntaxError("not a JPEG file") 

352 s = b"\xFF" 

353 

354 # Create attributes 

355 self.bits = self.layers = 0 

356 

357 # JPEG specifics (internal) 

358 self.layer = [] 

359 self.huffman_dc = {} 

360 self.huffman_ac = {} 

361 self.quantization = {} 

362 self.app = {} # compatibility 

363 self.applist = [] 

364 self.icclist = [] 

365 

366 while True: 

367 

368 i = s[0] 

369 if i == 0xFF: 

370 s = s + self.fp.read(1) 

371 i = i16(s) 

372 else: 

373 # Skip non-0xFF junk 

374 s = self.fp.read(1) 

375 continue 

376 

377 if i in MARKER: 

378 name, description, handler = MARKER[i] 

379 if handler is not None: 

380 handler(self, i) 

381 if i == 0xFFDA: # start of scan 

382 rawmode = self.mode 

383 if self.mode == "CMYK": 

384 rawmode = "CMYK;I" # assume adobe conventions 

385 self.tile = [("jpeg", (0, 0) + self.size, 0, (rawmode, ""))] 

386 # self.__offset = self.fp.tell() 

387 break 

388 s = self.fp.read(1) 

389 elif i == 0 or i == 0xFFFF: 

390 # padded marker or junk; move on 

391 s = b"\xff" 

392 elif i == 0xFF00: # Skip extraneous data (escaped 0xFF) 

393 s = self.fp.read(1) 

394 else: 

395 raise SyntaxError("no marker found") 

396 

397 def load_read(self, read_bytes): 

398 """ 

399 internal: read more image data 

400 For premature EOF and LOAD_TRUNCATED_IMAGES adds EOI marker 

401 so libjpeg can finish decoding 

402 """ 

403 s = self.fp.read(read_bytes) 

404 

405 if not s and ImageFile.LOAD_TRUNCATED_IMAGES and not hasattr(self, "_ended"): 

406 # Premature EOF. 

407 # Pretend file is finished adding EOI marker 

408 self._ended = True 

409 return b"\xFF\xD9" 

410 

411 return s 

412 

413 def draft(self, mode, size): 

414 

415 if len(self.tile) != 1: 

416 return 

417 

418 # Protect from second call 

419 if self.decoderconfig: 

420 return 

421 

422 d, e, o, a = self.tile[0] 

423 scale = 1 

424 original_size = self.size 

425 

426 if a[0] == "RGB" and mode in ["L", "YCbCr"]: 

427 self.mode = mode 

428 a = mode, "" 

429 

430 if size: 

431 scale = min(self.size[0] // size[0], self.size[1] // size[1]) 

432 for s in [8, 4, 2, 1]: 

433 if scale >= s: 

434 break 

435 e = ( 

436 e[0], 

437 e[1], 

438 (e[2] - e[0] + s - 1) // s + e[0], 

439 (e[3] - e[1] + s - 1) // s + e[1], 

440 ) 

441 self._size = ((self.size[0] + s - 1) // s, (self.size[1] + s - 1) // s) 

442 scale = s 

443 

444 self.tile = [(d, e, o, a)] 

445 self.decoderconfig = (scale, 0) 

446 

447 box = (0, 0, original_size[0] / scale, original_size[1] / scale) 

448 return self.mode, box 

449 

450 def load_djpeg(self): 

451 

452 # ALTERNATIVE: handle JPEGs via the IJG command line utilities 

453 

454 f, path = tempfile.mkstemp() 

455 os.close(f) 

456 if os.path.exists(self.filename): 

457 subprocess.check_call(["djpeg", "-outfile", path, self.filename]) 

458 else: 

459 raise ValueError("Invalid Filename") 

460 

461 try: 

462 with Image.open(path) as _im: 

463 _im.load() 

464 self.im = _im.im 

465 finally: 

466 try: 

467 os.unlink(path) 

468 except OSError: 

469 pass 

470 

471 self.mode = self.im.mode 

472 self._size = self.im.size 

473 

474 self.tile = [] 

475 

476 def _getexif(self): 

477 return _getexif(self) 

478 

479 def _getmp(self): 

480 return _getmp(self) 

481 

482 def getxmp(self): 

483 """ 

484 Returns a dictionary containing the XMP tags. 

485 Requires defusedxml to be installed. 

486 

487 :returns: XMP tags in a dictionary. 

488 """ 

489 

490 for segment, content in self.applist: 

491 if segment == "APP1": 

492 marker, xmp_tags = content.rsplit(b"\x00", 1) 

493 if marker == b"http://ns.adobe.com/xap/1.0/": 

494 return self._getxmp(xmp_tags) 

495 return {} 

496 

497 

498def _getexif(self): 

499 if "exif" not in self.info: 

500 return None 

501 return self.getexif()._get_merged_dict() 

502 

503 

504def _getmp(self): 

505 # Extract MP information. This method was inspired by the "highly 

506 # experimental" _getexif version that's been in use for years now, 

507 # itself based on the ImageFileDirectory class in the TIFF plugin. 

508 

509 # The MP record essentially consists of a TIFF file embedded in a JPEG 

510 # application marker. 

511 try: 

512 data = self.info["mp"] 

513 except KeyError: 

514 return None 

515 file_contents = io.BytesIO(data) 

516 head = file_contents.read(8) 

517 endianness = ">" if head[:4] == b"\x4d\x4d\x00\x2a" else "<" 

518 # process dictionary 

519 try: 

520 info = TiffImagePlugin.ImageFileDirectory_v2(head) 

521 file_contents.seek(info.next) 

522 info.load(file_contents) 

523 mp = dict(info) 

524 except Exception as e: 

525 raise SyntaxError("malformed MP Index (unreadable directory)") from e 

526 # it's an error not to have a number of images 

527 try: 

528 quant = mp[0xB001] 

529 except KeyError as e: 

530 raise SyntaxError("malformed MP Index (no number of images)") from e 

531 # get MP entries 

532 mpentries = [] 

533 try: 

534 rawmpentries = mp[0xB002] 

535 for entrynum in range(0, quant): 

536 unpackedentry = struct.unpack_from( 

537 f"{endianness}LLLHH", rawmpentries, entrynum * 16 

538 ) 

539 labels = ("Attribute", "Size", "DataOffset", "EntryNo1", "EntryNo2") 

540 mpentry = dict(zip(labels, unpackedentry)) 

541 mpentryattr = { 

542 "DependentParentImageFlag": bool(mpentry["Attribute"] & (1 << 31)), 

543 "DependentChildImageFlag": bool(mpentry["Attribute"] & (1 << 30)), 

544 "RepresentativeImageFlag": bool(mpentry["Attribute"] & (1 << 29)), 

545 "Reserved": (mpentry["Attribute"] & (3 << 27)) >> 27, 

546 "ImageDataFormat": (mpentry["Attribute"] & (7 << 24)) >> 24, 

547 "MPType": mpentry["Attribute"] & 0x00FFFFFF, 

548 } 

549 if mpentryattr["ImageDataFormat"] == 0: 

550 mpentryattr["ImageDataFormat"] = "JPEG" 

551 else: 

552 raise SyntaxError("unsupported picture format in MPO") 

553 mptypemap = { 

554 0x000000: "Undefined", 

555 0x010001: "Large Thumbnail (VGA Equivalent)", 

556 0x010002: "Large Thumbnail (Full HD Equivalent)", 

557 0x020001: "Multi-Frame Image (Panorama)", 

558 0x020002: "Multi-Frame Image: (Disparity)", 

559 0x020003: "Multi-Frame Image: (Multi-Angle)", 

560 0x030000: "Baseline MP Primary Image", 

561 } 

562 mpentryattr["MPType"] = mptypemap.get(mpentryattr["MPType"], "Unknown") 

563 mpentry["Attribute"] = mpentryattr 

564 mpentries.append(mpentry) 

565 mp[0xB002] = mpentries 

566 except KeyError as e: 

567 raise SyntaxError("malformed MP Index (bad MP Entry)") from e 

568 # Next we should try and parse the individual image unique ID list; 

569 # we don't because I've never seen this actually used in a real MPO 

570 # file and so can't test it. 

571 return mp 

572 

573 

574# -------------------------------------------------------------------- 

575# stuff to save JPEG files 

576 

577RAWMODE = { 

578 "1": "L", 

579 "L": "L", 

580 "RGB": "RGB", 

581 "RGBX": "RGB", 

582 "CMYK": "CMYK;I", # assume adobe conventions 

583 "YCbCr": "YCbCr", 

584} 

585 

586# fmt: off 

587zigzag_index = ( 

588 0, 1, 5, 6, 14, 15, 27, 28, 

589 2, 4, 7, 13, 16, 26, 29, 42, 

590 3, 8, 12, 17, 25, 30, 41, 43, 

591 9, 11, 18, 24, 31, 40, 44, 53, 

592 10, 19, 23, 32, 39, 45, 52, 54, 

593 20, 22, 33, 38, 46, 51, 55, 60, 

594 21, 34, 37, 47, 50, 56, 59, 61, 

595 35, 36, 48, 49, 57, 58, 62, 63, 

596) 

597 

598samplings = { 

599 (1, 1, 1, 1, 1, 1): 0, 

600 (2, 1, 1, 1, 1, 1): 1, 

601 (2, 2, 1, 1, 1, 1): 2, 

602} 

603# fmt: on 

604 

605 

606def convert_dict_qtables(qtables): 

607 deprecate("convert_dict_qtables", 10, action="Conversion is no longer needed") 

608 return qtables 

609 

610 

611def get_sampling(im): 

612 # There's no subsampling when images have only 1 layer 

613 # (grayscale images) or when they are CMYK (4 layers), 

614 # so set subsampling to the default value. 

615 # 

616 # NOTE: currently Pillow can't encode JPEG to YCCK format. 

617 # If YCCK support is added in the future, subsampling code will have 

618 # to be updated (here and in JpegEncode.c) to deal with 4 layers. 

619 if not hasattr(im, "layers") or im.layers in (1, 4): 

620 return -1 

621 sampling = im.layer[0][1:3] + im.layer[1][1:3] + im.layer[2][1:3] 

622 return samplings.get(sampling, -1) 

623 

624 

625def _save(im, fp, filename): 

626 if im.width == 0 or im.height == 0: 

627 raise ValueError("cannot write empty image as JPEG") 

628 

629 try: 

630 rawmode = RAWMODE[im.mode] 

631 except KeyError as e: 

632 raise OSError(f"cannot write mode {im.mode} as JPEG") from e 

633 

634 info = im.encoderinfo 

635 

636 dpi = [round(x) for x in info.get("dpi", (0, 0))] 

637 

638 quality = info.get("quality", -1) 

639 subsampling = info.get("subsampling", -1) 

640 qtables = info.get("qtables") 

641 

642 if quality == "keep": 

643 quality = -1 

644 subsampling = "keep" 

645 qtables = "keep" 

646 elif quality in presets: 

647 preset = presets[quality] 

648 quality = -1 

649 subsampling = preset.get("subsampling", -1) 

650 qtables = preset.get("quantization") 

651 elif not isinstance(quality, int): 

652 raise ValueError("Invalid quality setting") 

653 else: 

654 if subsampling in presets: 

655 subsampling = presets[subsampling].get("subsampling", -1) 

656 if isinstance(qtables, str) and qtables in presets: 

657 qtables = presets[qtables].get("quantization") 

658 

659 if subsampling == "4:4:4": 

660 subsampling = 0 

661 elif subsampling == "4:2:2": 

662 subsampling = 1 

663 elif subsampling == "4:2:0": 

664 subsampling = 2 

665 elif subsampling == "4:1:1": 

666 # For compatibility. Before Pillow 4.3, 4:1:1 actually meant 4:2:0. 

667 # Set 4:2:0 if someone is still using that value. 

668 subsampling = 2 

669 elif subsampling == "keep": 

670 if im.format != "JPEG": 

671 raise ValueError("Cannot use 'keep' when original image is not a JPEG") 

672 subsampling = get_sampling(im) 

673 

674 def validate_qtables(qtables): 

675 if qtables is None: 

676 return qtables 

677 if isinstance(qtables, str): 

678 try: 

679 lines = [ 

680 int(num) 

681 for line in qtables.splitlines() 

682 for num in line.split("#", 1)[0].split() 

683 ] 

684 except ValueError as e: 

685 raise ValueError("Invalid quantization table") from e 

686 else: 

687 qtables = [lines[s : s + 64] for s in range(0, len(lines), 64)] 

688 if isinstance(qtables, (tuple, list, dict)): 

689 if isinstance(qtables, dict): 

690 qtables = [ 

691 qtables[key] for key in range(len(qtables)) if key in qtables 

692 ] 

693 elif isinstance(qtables, tuple): 

694 qtables = list(qtables) 

695 if not (0 < len(qtables) < 5): 

696 raise ValueError("None or too many quantization tables") 

697 for idx, table in enumerate(qtables): 

698 try: 

699 if len(table) != 64: 

700 raise TypeError 

701 table = array.array("H", table) 

702 except TypeError as e: 

703 raise ValueError("Invalid quantization table") from e 

704 else: 

705 qtables[idx] = list(table) 

706 return qtables 

707 

708 if qtables == "keep": 

709 if im.format != "JPEG": 

710 raise ValueError("Cannot use 'keep' when original image is not a JPEG") 

711 qtables = getattr(im, "quantization", None) 

712 qtables = validate_qtables(qtables) 

713 

714 extra = b"" 

715 

716 icc_profile = info.get("icc_profile") 

717 if icc_profile: 

718 ICC_OVERHEAD_LEN = 14 

719 MAX_BYTES_IN_MARKER = 65533 

720 MAX_DATA_BYTES_IN_MARKER = MAX_BYTES_IN_MARKER - ICC_OVERHEAD_LEN 

721 markers = [] 

722 while icc_profile: 

723 markers.append(icc_profile[:MAX_DATA_BYTES_IN_MARKER]) 

724 icc_profile = icc_profile[MAX_DATA_BYTES_IN_MARKER:] 

725 i = 1 

726 for marker in markers: 

727 size = struct.pack(">H", 2 + ICC_OVERHEAD_LEN + len(marker)) 

728 extra += ( 

729 b"\xFF\xE2" 

730 + size 

731 + b"ICC_PROFILE\0" 

732 + o8(i) 

733 + o8(len(markers)) 

734 + marker 

735 ) 

736 i += 1 

737 

738 # "progressive" is the official name, but older documentation 

739 # says "progression" 

740 # FIXME: issue a warning if the wrong form is used (post-1.1.7) 

741 progressive = info.get("progressive", False) or info.get("progression", False) 

742 

743 optimize = info.get("optimize", False) 

744 

745 exif = info.get("exif", b"") 

746 if isinstance(exif, Image.Exif): 

747 exif = exif.tobytes() 

748 

749 # get keyword arguments 

750 im.encoderconfig = ( 

751 quality, 

752 progressive, 

753 info.get("smooth", 0), 

754 optimize, 

755 info.get("streamtype", 0), 

756 dpi[0], 

757 dpi[1], 

758 subsampling, 

759 qtables, 

760 extra, 

761 exif, 

762 ) 

763 

764 # if we optimize, libjpeg needs a buffer big enough to hold the whole image 

765 # in a shot. Guessing on the size, at im.size bytes. (raw pixel size is 

766 # channels*size, this is a value that's been used in a django patch. 

767 # https://github.com/matthewwithanm/django-imagekit/issues/50 

768 bufsize = 0 

769 if optimize or progressive: 

770 # CMYK can be bigger 

771 if im.mode == "CMYK": 

772 bufsize = 4 * im.size[0] * im.size[1] 

773 # keep sets quality to -1, but the actual value may be high. 

774 elif quality >= 95 or quality == -1: 

775 bufsize = 2 * im.size[0] * im.size[1] 

776 else: 

777 bufsize = im.size[0] * im.size[1] 

778 

779 # The EXIF info needs to be written as one block, + APP1, + one spare byte. 

780 # Ensure that our buffer is big enough. Same with the icc_profile block. 

781 bufsize = max(ImageFile.MAXBLOCK, bufsize, len(exif) + 5, len(extra) + 1) 

782 

783 ImageFile._save(im, fp, [("jpeg", (0, 0) + im.size, 0, rawmode)], bufsize) 

784 

785 

786def _save_cjpeg(im, fp, filename): 

787 # ALTERNATIVE: handle JPEGs via the IJG command line utilities. 

788 tempfile = im._dump() 

789 subprocess.check_call(["cjpeg", "-outfile", filename, tempfile]) 

790 try: 

791 os.unlink(tempfile) 

792 except OSError: 

793 pass 

794 

795 

796## 

797# Factory for making JPEG and MPO instances 

798def jpeg_factory(fp=None, filename=None): 

799 im = JpegImageFile(fp, filename) 

800 try: 

801 mpheader = im._getmp() 

802 if mpheader[45057] > 1: 

803 # It's actually an MPO 

804 from .MpoImagePlugin import MpoImageFile 

805 

806 # Don't reload everything, just convert it. 

807 im = MpoImageFile.adopt(im, mpheader) 

808 except (TypeError, IndexError): 

809 # It is really a JPEG 

810 pass 

811 except SyntaxError: 

812 warnings.warn( 

813 "Image appears to be a malformed MPO file, it will be " 

814 "interpreted as a base JPEG file" 

815 ) 

816 return im 

817 

818 

819# --------------------------------------------------------------------- 

820# Registry stuff 

821 

822Image.register_open(JpegImageFile.format, jpeg_factory, _accept) 

823Image.register_save(JpegImageFile.format, _save) 

824 

825Image.register_extensions(JpegImageFile.format, [".jfif", ".jpe", ".jpg", ".jpeg"]) 

826 

827Image.register_mime(JpegImageFile.format, "image/jpeg")