Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/pillow-10.4.0-py3.8-linux-x86_64.egg/PIL/JpegImagePlugin.py: 11%

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

446 statements  

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# 

34from __future__ import annotations 

35 

36import array 

37import io 

38import math 

39import os 

40import struct 

41import subprocess 

42import sys 

43import tempfile 

44import warnings 

45from typing import IO, Any 

46 

47from . import Image, ImageFile 

48from ._binary import i16be as i16 

49from ._binary import i32be as i32 

50from ._binary import o8 

51from ._binary import o16be as o16 

52from .JpegPresets import presets 

53 

54# 

55# Parser 

56 

57 

58def Skip(self: JpegImageFile, marker: int) -> None: 

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

60 ImageFile._safe_read(self.fp, n) 

61 

62 

63def APP(self, marker): 

64 # 

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

66 # Also look for well-known application markers. 

67 

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

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

70 

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

72 

73 self.app[app] = s # compatibility 

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

75 

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

77 # extract JFIF information 

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

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

80 # extract JFIF properties 

81 try: 

82 jfif_unit = s[7] 

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

84 except Exception: 

85 pass 

86 else: 

87 if jfif_unit == 1: 

88 self.info["dpi"] = jfif_density 

89 self.info["jfif_unit"] = jfif_unit 

90 self.info["jfif_density"] = jfif_density 

91 elif marker == 0xFFE1 and s[:6] == b"Exif\0\0": 

92 # extract EXIF information 

93 if "exif" in self.info: 

94 self.info["exif"] += s[6:] 

95 else: 

96 self.info["exif"] = s 

97 self._exif_offset = self.fp.tell() - n + 6 

98 elif marker == 0xFFE1 and s[:29] == b"http://ns.adobe.com/xap/1.0/\x00": 

99 self.info["xmp"] = s.split(b"\x00", 1)[1] 

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

101 # extract FlashPix information (incomplete) 

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

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

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

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

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

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

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

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

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

111 # Profile data (remainder of APP2 data) 

112 # Decoders should use the marker sequence numbers to 

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

114 # markers appear in the correct sequence. 

115 self.icclist.append(s) 

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

117 # parse the image resource block 

118 offset = 14 

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

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

121 try: 

122 offset += 4 

123 # resource code 

124 code = i16(s, offset) 

125 offset += 2 

126 # resource name (usually empty) 

127 name_len = s[offset] 

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

129 offset += 1 + name_len 

130 offset += offset & 1 # align 

131 # resource data block 

132 size = i32(s, offset) 

133 offset += 4 

134 data = s[offset : offset + size] 

135 if code == 0x03ED: # ResolutionInfo 

136 data = { 

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

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

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

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

141 } 

142 photoshop[code] = data 

143 offset += size 

144 offset += offset & 1 # align 

145 except struct.error: 

146 break # insufficient data 

147 

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

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

150 # extract Adobe custom properties 

151 try: 

152 adobe_transform = s[11] 

153 except IndexError: 

154 pass 

155 else: 

156 self.info["adobe_transform"] = adobe_transform 

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

158 # extract MPO information 

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

160 # offset is current location minus buffer size 

161 # plus constant header size 

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

163 

164 

165def COM(self: JpegImageFile, marker: int) -> None: 

166 # 

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

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

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

170 

171 self.info["comment"] = s 

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

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

174 

175 

176def SOF(self: JpegImageFile, marker: int) -> None: 

177 # 

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

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

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

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

182 # looking for JFIF and Adobe APP markers. 

183 

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

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

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

187 

188 self.bits = s[0] 

189 if self.bits != 8: 

190 msg = f"cannot handle {self.bits}-bit layers" 

191 raise SyntaxError(msg) 

192 

193 self.layers = s[5] 

194 if self.layers == 1: 

195 self._mode = "L" 

196 elif self.layers == 3: 

197 self._mode = "RGB" 

198 elif self.layers == 4: 

199 self._mode = "CMYK" 

200 else: 

201 msg = f"cannot handle {self.layers}-layer images" 

202 raise SyntaxError(msg) 

203 

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

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

206 

207 if self.icclist: 

208 # fixup icc profile 

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

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

211 profile = [p[14:] for p in self.icclist] 

212 icc_profile = b"".join(profile) 

213 else: 

214 icc_profile = None # wrong number of fragments 

215 self.info["icc_profile"] = icc_profile 

216 self.icclist = [] 

217 

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

219 t = s[i : i + 3] 

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

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

222 

223 

224def DQT(self: JpegImageFile, marker: int) -> None: 

225 # 

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

227 # than one table in each marker. 

228 

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

230 # compression quality. 

231 

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

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

234 while len(s): 

235 v = s[0] 

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

237 qt_length = 1 + precision * 64 

238 if len(s) < qt_length: 

239 msg = "bad quantization table marker" 

240 raise SyntaxError(msg) 

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

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

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

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

245 s = s[qt_length:] 

246 

247 

248# 

249# JPEG marker table 

250 

251MARKER = { 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

315} 

316 

317 

318def _accept(prefix: bytes) -> bool: 

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

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

321 

322 

323## 

324# Image plugin for JPEG and JFIF images. 

325 

326 

327class JpegImageFile(ImageFile.ImageFile): 

328 format = "JPEG" 

329 format_description = "JPEG (ISO 10918)" 

330 

331 def _open(self): 

332 s = self.fp.read(3) 

333 

334 if not _accept(s): 

335 msg = "not a JPEG file" 

336 raise SyntaxError(msg) 

337 s = b"\xFF" 

338 

339 # Create attributes 

340 self.bits = self.layers = 0 

341 

342 # JPEG specifics (internal) 

343 self.layer = [] 

344 self.huffman_dc = {} 

345 self.huffman_ac = {} 

346 self.quantization = {} 

347 self.app = {} # compatibility 

348 self.applist = [] 

349 self.icclist = [] 

350 

351 while True: 

352 i = s[0] 

353 if i == 0xFF: 

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

355 i = i16(s) 

356 else: 

357 # Skip non-0xFF junk 

358 s = self.fp.read(1) 

359 continue 

360 

361 if i in MARKER: 

362 name, description, handler = MARKER[i] 

363 if handler is not None: 

364 handler(self, i) 

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

366 rawmode = self.mode 

367 if self.mode == "CMYK": 

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

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

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

371 break 

372 s = self.fp.read(1) 

373 elif i in {0, 0xFFFF}: 

374 # padded marker or junk; move on 

375 s = b"\xff" 

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

377 s = self.fp.read(1) 

378 else: 

379 msg = "no marker found" 

380 raise SyntaxError(msg) 

381 

382 self._read_dpi_from_exif() 

383 

384 def load_read(self, read_bytes: int) -> bytes: 

385 """ 

386 internal: read more image data 

387 For premature EOF and LOAD_TRUNCATED_IMAGES adds EOI marker 

388 so libjpeg can finish decoding 

389 """ 

390 s = self.fp.read(read_bytes) 

391 

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

393 # Premature EOF. 

394 # Pretend file is finished adding EOI marker 

395 self._ended = True 

396 return b"\xFF\xD9" 

397 

398 return s 

399 

400 def draft( 

401 self, mode: str | None, size: tuple[int, int] | None 

402 ) -> tuple[str, tuple[int, int, float, float]] | None: 

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

404 return None 

405 

406 # Protect from second call 

407 if self.decoderconfig: 

408 return None 

409 

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

411 scale = 1 

412 original_size = self.size 

413 

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

415 self._mode = mode 

416 a = mode, "" 

417 

418 if size: 

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

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

421 if scale >= s: 

422 break 

423 e = ( 

424 e[0], 

425 e[1], 

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

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

428 ) 

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

430 scale = s 

431 

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

433 self.decoderconfig = (scale, 0) 

434 

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

436 return self.mode, box 

437 

438 def load_djpeg(self) -> None: 

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

440 

441 f, path = tempfile.mkstemp() 

442 os.close(f) 

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

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

445 else: 

446 try: 

447 os.unlink(path) 

448 except OSError: 

449 pass 

450 

451 msg = "Invalid Filename" 

452 raise ValueError(msg) 

453 

454 try: 

455 with Image.open(path) as _im: 

456 _im.load() 

457 self.im = _im.im 

458 finally: 

459 try: 

460 os.unlink(path) 

461 except OSError: 

462 pass 

463 

464 self._mode = self.im.mode 

465 self._size = self.im.size 

466 

467 self.tile = [] 

468 

469 def _getexif(self) -> dict[str, Any] | None: 

470 return _getexif(self) 

471 

472 def _read_dpi_from_exif(self) -> None: 

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

474 if "dpi" in self.info or "exif" not in self.info: 

475 return 

476 try: 

477 exif = self.getexif() 

478 resolution_unit = exif[0x0128] 

479 x_resolution = exif[0x011A] 

480 try: 

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

482 except TypeError: 

483 dpi = x_resolution 

484 if math.isnan(dpi): 

485 msg = "DPI is not a number" 

486 raise ValueError(msg) 

487 if resolution_unit == 3: # cm 

488 # 1 dpcm = 2.54 dpi 

489 dpi *= 2.54 

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

491 except ( 

492 struct.error, # truncated EXIF 

493 KeyError, # dpi not included 

494 SyntaxError, # invalid/unreadable EXIF 

495 TypeError, # dpi is an invalid float 

496 ValueError, # dpi is an invalid float 

497 ZeroDivisionError, # invalid dpi rational value 

498 ): 

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

500 

501 def _getmp(self): 

502 return _getmp(self) 

503 

504 

505def _getexif(self) -> dict[str, Any] | None: 

506 if "exif" not in self.info: 

507 return None 

508 return self.getexif()._get_merged_dict() 

509 

510 

511def _getmp(self): 

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

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

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

515 

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

517 # application marker. 

518 try: 

519 data = self.info["mp"] 

520 except KeyError: 

521 return None 

522 file_contents = io.BytesIO(data) 

523 head = file_contents.read(8) 

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

525 # process dictionary 

526 from . import TiffImagePlugin 

527 

528 try: 

529 info = TiffImagePlugin.ImageFileDirectory_v2(head) 

530 file_contents.seek(info.next) 

531 info.load(file_contents) 

532 mp = dict(info) 

533 except Exception as e: 

534 msg = "malformed MP Index (unreadable directory)" 

535 raise SyntaxError(msg) from e 

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

537 try: 

538 quant = mp[0xB001] 

539 except KeyError as e: 

540 msg = "malformed MP Index (no number of images)" 

541 raise SyntaxError(msg) from e 

542 # get MP entries 

543 mpentries = [] 

544 try: 

545 rawmpentries = mp[0xB002] 

546 for entrynum in range(0, quant): 

547 unpackedentry = struct.unpack_from( 

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

549 ) 

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

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

552 mpentryattr = { 

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

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

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

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

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

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

559 } 

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

561 mpentryattr["ImageDataFormat"] = "JPEG" 

562 else: 

563 msg = "unsupported picture format in MPO" 

564 raise SyntaxError(msg) 

565 mptypemap = { 

566 0x000000: "Undefined", 

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

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

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

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

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

572 0x030000: "Baseline MP Primary Image", 

573 } 

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

575 mpentry["Attribute"] = mpentryattr 

576 mpentries.append(mpentry) 

577 mp[0xB002] = mpentries 

578 except KeyError as e: 

579 msg = "malformed MP Index (bad MP Entry)" 

580 raise SyntaxError(msg) from e 

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

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

583 # file and so can't test it. 

584 return mp 

585 

586 

587# -------------------------------------------------------------------- 

588# stuff to save JPEG files 

589 

590RAWMODE = { 

591 "1": "L", 

592 "L": "L", 

593 "RGB": "RGB", 

594 "RGBX": "RGB", 

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

596 "YCbCr": "YCbCr", 

597} 

598 

599# fmt: off 

600zigzag_index = ( 

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

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

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

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

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

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

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

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

609) 

610 

611samplings = { 

612 (1, 1, 1, 1, 1, 1): 0, 

613 (2, 1, 1, 1, 1, 1): 1, 

614 (2, 2, 1, 1, 1, 1): 2, 

615} 

616# fmt: on 

617 

618 

619def get_sampling(im): 

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

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

622 # so set subsampling to the default value. 

623 # 

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

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

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

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

628 return -1 

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

630 return samplings.get(sampling, -1) 

631 

632 

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

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

635 msg = "cannot write empty image as JPEG" 

636 raise ValueError(msg) 

637 

638 try: 

639 rawmode = RAWMODE[im.mode] 

640 except KeyError as e: 

641 msg = f"cannot write mode {im.mode} as JPEG" 

642 raise OSError(msg) from e 

643 

644 info = im.encoderinfo 

645 

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

647 

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

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

650 qtables = info.get("qtables") 

651 

652 if quality == "keep": 

653 quality = -1 

654 subsampling = "keep" 

655 qtables = "keep" 

656 elif quality in presets: 

657 preset = presets[quality] 

658 quality = -1 

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

660 qtables = preset.get("quantization") 

661 elif not isinstance(quality, int): 

662 msg = "Invalid quality setting" 

663 raise ValueError(msg) 

664 else: 

665 if subsampling in presets: 

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

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

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

669 

670 if subsampling == "4:4:4": 

671 subsampling = 0 

672 elif subsampling == "4:2:2": 

673 subsampling = 1 

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

675 subsampling = 2 

676 elif subsampling == "4:1:1": 

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

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

679 subsampling = 2 

680 elif subsampling == "keep": 

681 if im.format != "JPEG": 

682 msg = "Cannot use 'keep' when original image is not a JPEG" 

683 raise ValueError(msg) 

684 subsampling = get_sampling(im) 

685 

686 def validate_qtables(qtables): 

687 if qtables is None: 

688 return qtables 

689 if isinstance(qtables, str): 

690 try: 

691 lines = [ 

692 int(num) 

693 for line in qtables.splitlines() 

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

695 ] 

696 except ValueError as e: 

697 msg = "Invalid quantization table" 

698 raise ValueError(msg) from e 

699 else: 

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

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

702 if isinstance(qtables, dict): 

703 qtables = [ 

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

705 ] 

706 elif isinstance(qtables, tuple): 

707 qtables = list(qtables) 

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

709 msg = "None or too many quantization tables" 

710 raise ValueError(msg) 

711 for idx, table in enumerate(qtables): 

712 try: 

713 if len(table) != 64: 

714 msg = "Invalid quantization table" 

715 raise TypeError(msg) 

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

717 except TypeError as e: 

718 msg = "Invalid quantization table" 

719 raise ValueError(msg) from e 

720 else: 

721 qtables[idx] = list(table) 

722 return qtables 

723 

724 if qtables == "keep": 

725 if im.format != "JPEG": 

726 msg = "Cannot use 'keep' when original image is not a JPEG" 

727 raise ValueError(msg) 

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

729 qtables = validate_qtables(qtables) 

730 

731 extra = info.get("extra", b"") 

732 

733 MAX_BYTES_IN_MARKER = 65533 

734 icc_profile = info.get("icc_profile") 

735 if icc_profile: 

736 ICC_OVERHEAD_LEN = 14 

737 MAX_DATA_BYTES_IN_MARKER = MAX_BYTES_IN_MARKER - ICC_OVERHEAD_LEN 

738 markers = [] 

739 while icc_profile: 

740 markers.append(icc_profile[:MAX_DATA_BYTES_IN_MARKER]) 

741 icc_profile = icc_profile[MAX_DATA_BYTES_IN_MARKER:] 

742 i = 1 

743 for marker in markers: 

744 size = o16(2 + ICC_OVERHEAD_LEN + len(marker)) 

745 extra += ( 

746 b"\xFF\xE2" 

747 + size 

748 + b"ICC_PROFILE\0" 

749 + o8(i) 

750 + o8(len(markers)) 

751 + marker 

752 ) 

753 i += 1 

754 

755 comment = info.get("comment", im.info.get("comment")) 

756 

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

758 # says "progression" 

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

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

761 

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

763 

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

765 if isinstance(exif, Image.Exif): 

766 exif = exif.tobytes() 

767 if len(exif) > MAX_BYTES_IN_MARKER: 

768 msg = "EXIF data is too long" 

769 raise ValueError(msg) 

770 

771 # get keyword arguments 

772 im.encoderconfig = ( 

773 quality, 

774 progressive, 

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

776 optimize, 

777 info.get("keep_rgb", False), 

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

779 dpi[0], 

780 dpi[1], 

781 subsampling, 

782 info.get("restart_marker_blocks", 0), 

783 info.get("restart_marker_rows", 0), 

784 qtables, 

785 comment, 

786 extra, 

787 exif, 

788 ) 

789 

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

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

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

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

794 bufsize = 0 

795 if optimize or progressive: 

796 # CMYK can be bigger 

797 if im.mode == "CMYK": 

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

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

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

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

802 else: 

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

804 if exif: 

805 bufsize += len(exif) + 5 

806 if extra: 

807 bufsize += len(extra) + 1 

808 else: 

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

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

811 bufsize = max(bufsize, len(exif) + 5, len(extra) + 1) 

812 

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

814 

815 

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

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

818 tempfile = im._dump() 

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

820 try: 

821 os.unlink(tempfile) 

822 except OSError: 

823 pass 

824 

825 

826## 

827# Factory for making JPEG and MPO instances 

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

829 im = JpegImageFile(fp, filename) 

830 try: 

831 mpheader = im._getmp() 

832 if mpheader[45057] > 1: 

833 for segment, content in im.applist: 

834 if segment == "APP1" and b' hdrgm:Version="' in content: 

835 # Ultra HDR images are not yet supported 

836 return im 

837 # It's actually an MPO 

838 from .MpoImagePlugin import MpoImageFile 

839 

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

841 im = MpoImageFile.adopt(im, mpheader) 

842 except (TypeError, IndexError): 

843 # It is really a JPEG 

844 pass 

845 except SyntaxError: 

846 warnings.warn( 

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

848 "interpreted as a base JPEG file" 

849 ) 

850 return im 

851 

852 

853# --------------------------------------------------------------------- 

854# Registry stuff 

855 

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

857Image.register_save(JpegImageFile.format, _save) 

858 

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

860 

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