Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.10/site-packages/pillow-11.1.0-py3.10-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

474 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, TYPE_CHECKING, 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 ._deprecate import deprecate 

53from .JpegPresets import presets 

54 

55if TYPE_CHECKING: 

56 from .MpoImagePlugin import MpoImageFile 

57 

58# 

59# Parser 

60 

61 

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

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

64 ImageFile._safe_read(self.fp, n) 

65 

66 

67def APP(self: JpegImageFile, marker: int) -> None: 

68 # 

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

70 # Also look for well-known application markers. 

71 

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

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

74 

75 app = f"APP{marker & 15}" 

76 

77 self.app[app] = s # compatibility 

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

79 

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

81 # extract JFIF information 

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

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

84 # extract JFIF properties 

85 try: 

86 jfif_unit = s[7] 

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

88 except Exception: 

89 pass 

90 else: 

91 if jfif_unit == 1: 

92 self.info["dpi"] = jfif_density 

93 elif jfif_unit == 2: # cm 

94 # 1 dpcm = 2.54 dpi 

95 self.info["dpi"] = tuple(d * 2.54 for d in jfif_density) 

96 self.info["jfif_unit"] = jfif_unit 

97 self.info["jfif_density"] = jfif_density 

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

99 # extract EXIF information 

100 if "exif" in self.info: 

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

102 else: 

103 self.info["exif"] = s 

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

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

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

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

108 # extract FlashPix information (incomplete) 

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

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

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

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

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

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

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

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

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

118 # Profile data (remainder of APP2 data) 

119 # Decoders should use the marker sequence numbers to 

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

121 # markers appear in the correct sequence. 

122 self.icclist.append(s) 

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

124 # parse the image resource block 

125 offset = 14 

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

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

128 try: 

129 offset += 4 

130 # resource code 

131 code = i16(s, offset) 

132 offset += 2 

133 # resource name (usually empty) 

134 name_len = s[offset] 

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

136 offset += 1 + name_len 

137 offset += offset & 1 # align 

138 # resource data block 

139 size = i32(s, offset) 

140 offset += 4 

141 data = s[offset : offset + size] 

142 if code == 0x03ED: # ResolutionInfo 

143 photoshop[code] = { 

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

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

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

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

148 } 

149 else: 

150 photoshop[code] = data 

151 offset += size 

152 offset += offset & 1 # align 

153 except struct.error: 

154 break # insufficient data 

155 

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

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

158 # extract Adobe custom properties 

159 try: 

160 adobe_transform = s[11] 

161 except IndexError: 

162 pass 

163 else: 

164 self.info["adobe_transform"] = adobe_transform 

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

166 # extract MPO information 

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

168 # offset is current location minus buffer size 

169 # plus constant header size 

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

171 

172 

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

174 # 

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

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

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

178 

179 self.info["comment"] = s 

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

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

182 

183 

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

185 # 

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

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

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

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

190 # looking for JFIF and Adobe APP markers. 

191 

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

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

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

195 

196 self.bits = s[0] 

197 if self.bits != 8: 

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

199 raise SyntaxError(msg) 

200 

201 self.layers = s[5] 

202 if self.layers == 1: 

203 self._mode = "L" 

204 elif self.layers == 3: 

205 self._mode = "RGB" 

206 elif self.layers == 4: 

207 self._mode = "CMYK" 

208 else: 

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

210 raise SyntaxError(msg) 

211 

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

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

214 

215 if self.icclist: 

216 # fixup icc profile 

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

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

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

220 icc_profile = b"".join(profile) 

221 else: 

222 icc_profile = None # wrong number of fragments 

223 self.info["icc_profile"] = icc_profile 

224 self.icclist = [] 

225 

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

227 t = s[i : i + 3] 

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

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

230 

231 

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

233 # 

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

235 # than one table in each marker. 

236 

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

238 # compression quality. 

239 

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

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

242 while len(s): 

243 v = s[0] 

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

245 qt_length = 1 + precision * 64 

246 if len(s) < qt_length: 

247 msg = "bad quantization table marker" 

248 raise SyntaxError(msg) 

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

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

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

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

253 s = s[qt_length:] 

254 

255 

256# 

257# JPEG marker table 

258 

259MARKER = { 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

323} 

324 

325 

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

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

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

329 

330 

331## 

332# Image plugin for JPEG and JFIF images. 

333 

334 

335class JpegImageFile(ImageFile.ImageFile): 

336 format = "JPEG" 

337 format_description = "JPEG (ISO 10918)" 

338 

339 def _open(self) -> None: 

340 s = self.fp.read(3) 

341 

342 if not _accept(s): 

343 msg = "not a JPEG file" 

344 raise SyntaxError(msg) 

345 s = b"\xFF" 

346 

347 # Create attributes 

348 self.bits = self.layers = 0 

349 self._exif_offset = 0 

350 

351 # JPEG specifics (internal) 

352 self.layer: list[tuple[int, int, int, int]] = [] 

353 self._huffman_dc: dict[Any, Any] = {} 

354 self._huffman_ac: dict[Any, Any] = {} 

355 self.quantization: dict[int, list[int]] = {} 

356 self.app: dict[str, bytes] = {} # compatibility 

357 self.applist: list[tuple[str, bytes]] = [] 

358 self.icclist: list[bytes] = [] 

359 

360 while True: 

361 i = s[0] 

362 if i == 0xFF: 

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

364 i = i16(s) 

365 else: 

366 # Skip non-0xFF junk 

367 s = self.fp.read(1) 

368 continue 

369 

370 if i in MARKER: 

371 name, description, handler = MARKER[i] 

372 if handler is not None: 

373 handler(self, i) 

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

375 rawmode = self.mode 

376 if self.mode == "CMYK": 

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

378 self.tile = [ 

379 ImageFile._Tile("jpeg", (0, 0) + self.size, 0, (rawmode, "")) 

380 ] 

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

382 break 

383 s = self.fp.read(1) 

384 elif i in {0, 0xFFFF}: 

385 # padded marker or junk; move on 

386 s = b"\xff" 

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

388 s = self.fp.read(1) 

389 else: 

390 msg = "no marker found" 

391 raise SyntaxError(msg) 

392 

393 self._read_dpi_from_exif() 

394 

395 def __getattr__(self, name: str) -> Any: 

396 if name in ("huffman_ac", "huffman_dc"): 

397 deprecate(name, 12) 

398 return getattr(self, "_" + name) 

399 raise AttributeError(name) 

400 

401 def __getstate__(self) -> list[Any]: 

402 return super().__getstate__() + [self.layers, self.layer] 

403 

404 def __setstate__(self, state: list[Any]) -> None: 

405 super().__setstate__(state) 

406 self.layers, self.layer = state[5:] 

407 

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

409 """ 

410 internal: read more image data 

411 For premature EOF and LOAD_TRUNCATED_IMAGES adds EOI marker 

412 so libjpeg can finish decoding 

413 """ 

414 s = self.fp.read(read_bytes) 

415 

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

417 # Premature EOF. 

418 # Pretend file is finished adding EOI marker 

419 self._ended = True 

420 return b"\xFF\xD9" 

421 

422 return s 

423 

424 def draft( 

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

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

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

428 return None 

429 

430 # Protect from second call 

431 if self.decoderconfig: 

432 return None 

433 

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

435 scale = 1 

436 original_size = self.size 

437 

438 assert isinstance(a, tuple) 

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

440 self._mode = mode 

441 a = mode, "" 

442 

443 if size: 

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

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

446 if scale >= s: 

447 break 

448 assert e is not None 

449 e = ( 

450 e[0], 

451 e[1], 

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

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

454 ) 

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

456 scale = s 

457 

458 self.tile = [ImageFile._Tile(d, e, o, a)] 

459 self.decoderconfig = (scale, 0) 

460 

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

462 return self.mode, box 

463 

464 def load_djpeg(self) -> None: 

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

466 

467 f, path = tempfile.mkstemp() 

468 os.close(f) 

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

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

471 else: 

472 try: 

473 os.unlink(path) 

474 except OSError: 

475 pass 

476 

477 msg = "Invalid Filename" 

478 raise ValueError(msg) 

479 

480 try: 

481 with Image.open(path) as _im: 

482 _im.load() 

483 self.im = _im.im 

484 finally: 

485 try: 

486 os.unlink(path) 

487 except OSError: 

488 pass 

489 

490 self._mode = self.im.mode 

491 self._size = self.im.size 

492 

493 self.tile = [] 

494 

495 def _getexif(self) -> dict[int, Any] | None: 

496 return _getexif(self) 

497 

498 def _read_dpi_from_exif(self) -> None: 

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

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

501 return 

502 try: 

503 exif = self.getexif() 

504 resolution_unit = exif[0x0128] 

505 x_resolution = exif[0x011A] 

506 try: 

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

508 except TypeError: 

509 dpi = x_resolution 

510 if math.isnan(dpi): 

511 msg = "DPI is not a number" 

512 raise ValueError(msg) 

513 if resolution_unit == 3: # cm 

514 # 1 dpcm = 2.54 dpi 

515 dpi *= 2.54 

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

517 except ( 

518 struct.error, # truncated EXIF 

519 KeyError, # dpi not included 

520 SyntaxError, # invalid/unreadable EXIF 

521 TypeError, # dpi is an invalid float 

522 ValueError, # dpi is an invalid float 

523 ZeroDivisionError, # invalid dpi rational value 

524 ): 

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

526 

527 def _getmp(self) -> dict[int, Any] | None: 

528 return _getmp(self) 

529 

530 

531def _getexif(self: JpegImageFile) -> dict[int, Any] | None: 

532 if "exif" not in self.info: 

533 return None 

534 return self.getexif()._get_merged_dict() 

535 

536 

537def _getmp(self: JpegImageFile) -> dict[int, Any] | None: 

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

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

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

541 

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

543 # application marker. 

544 try: 

545 data = self.info["mp"] 

546 except KeyError: 

547 return None 

548 file_contents = io.BytesIO(data) 

549 head = file_contents.read(8) 

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

551 # process dictionary 

552 from . import TiffImagePlugin 

553 

554 try: 

555 info = TiffImagePlugin.ImageFileDirectory_v2(head) 

556 file_contents.seek(info.next) 

557 info.load(file_contents) 

558 mp = dict(info) 

559 except Exception as e: 

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

561 raise SyntaxError(msg) from e 

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

563 try: 

564 quant = mp[0xB001] 

565 except KeyError as e: 

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

567 raise SyntaxError(msg) from e 

568 # get MP entries 

569 mpentries = [] 

570 try: 

571 rawmpentries = mp[0xB002] 

572 for entrynum in range(0, quant): 

573 unpackedentry = struct.unpack_from( 

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

575 ) 

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

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

578 mpentryattr = { 

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

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

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

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

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

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

585 } 

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

587 mpentryattr["ImageDataFormat"] = "JPEG" 

588 else: 

589 msg = "unsupported picture format in MPO" 

590 raise SyntaxError(msg) 

591 mptypemap = { 

592 0x000000: "Undefined", 

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

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

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

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

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

598 0x030000: "Baseline MP Primary Image", 

599 } 

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

601 mpentry["Attribute"] = mpentryattr 

602 mpentries.append(mpentry) 

603 mp[0xB002] = mpentries 

604 except KeyError as e: 

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

606 raise SyntaxError(msg) from e 

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

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

609 # file and so can't test it. 

610 return mp 

611 

612 

613# -------------------------------------------------------------------- 

614# stuff to save JPEG files 

615 

616RAWMODE = { 

617 "1": "L", 

618 "L": "L", 

619 "RGB": "RGB", 

620 "RGBX": "RGB", 

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

622 "YCbCr": "YCbCr", 

623} 

624 

625# fmt: off 

626zigzag_index = ( 

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

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

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

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

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

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

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

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

635) 

636 

637samplings = { 

638 (1, 1, 1, 1, 1, 1): 0, 

639 (2, 1, 1, 1, 1, 1): 1, 

640 (2, 2, 1, 1, 1, 1): 2, 

641} 

642# fmt: on 

643 

644 

645def get_sampling(im: Image.Image) -> int: 

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

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

648 # so set subsampling to the default value. 

649 # 

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

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

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

653 if not isinstance(im, JpegImageFile) or im.layers in (1, 4): 

654 return -1 

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

656 return samplings.get(sampling, -1) 

657 

658 

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

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

661 msg = "cannot write empty image as JPEG" 

662 raise ValueError(msg) 

663 

664 try: 

665 rawmode = RAWMODE[im.mode] 

666 except KeyError as e: 

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

668 raise OSError(msg) from e 

669 

670 info = im.encoderinfo 

671 

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

673 

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

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

676 qtables = info.get("qtables") 

677 

678 if quality == "keep": 

679 quality = -1 

680 subsampling = "keep" 

681 qtables = "keep" 

682 elif quality in presets: 

683 preset = presets[quality] 

684 quality = -1 

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

686 qtables = preset.get("quantization") 

687 elif not isinstance(quality, int): 

688 msg = "Invalid quality setting" 

689 raise ValueError(msg) 

690 else: 

691 if subsampling in presets: 

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

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

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

695 

696 if subsampling == "4:4:4": 

697 subsampling = 0 

698 elif subsampling == "4:2:2": 

699 subsampling = 1 

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

701 subsampling = 2 

702 elif subsampling == "4:1:1": 

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

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

705 subsampling = 2 

706 elif subsampling == "keep": 

707 if im.format != "JPEG": 

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

709 raise ValueError(msg) 

710 subsampling = get_sampling(im) 

711 

712 def validate_qtables( 

713 qtables: ( 

714 str | tuple[list[int], ...] | list[list[int]] | dict[int, list[int]] | None 

715 ) 

716 ) -> list[list[int]] | None: 

717 if qtables is None: 

718 return qtables 

719 if isinstance(qtables, str): 

720 try: 

721 lines = [ 

722 int(num) 

723 for line in qtables.splitlines() 

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

725 ] 

726 except ValueError as e: 

727 msg = "Invalid quantization table" 

728 raise ValueError(msg) from e 

729 else: 

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

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

732 if isinstance(qtables, dict): 

733 qtables = [ 

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

735 ] 

736 elif isinstance(qtables, tuple): 

737 qtables = list(qtables) 

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

739 msg = "None or too many quantization tables" 

740 raise ValueError(msg) 

741 for idx, table in enumerate(qtables): 

742 try: 

743 if len(table) != 64: 

744 msg = "Invalid quantization table" 

745 raise TypeError(msg) 

746 table_array = array.array("H", table) 

747 except TypeError as e: 

748 msg = "Invalid quantization table" 

749 raise ValueError(msg) from e 

750 else: 

751 qtables[idx] = list(table_array) 

752 return qtables 

753 

754 if qtables == "keep": 

755 if im.format != "JPEG": 

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

757 raise ValueError(msg) 

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

759 qtables = validate_qtables(qtables) 

760 

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

762 

763 MAX_BYTES_IN_MARKER = 65533 

764 xmp = info.get("xmp") 

765 if xmp: 

766 overhead_len = 29 # b"http://ns.adobe.com/xap/1.0/\x00" 

767 max_data_bytes_in_marker = MAX_BYTES_IN_MARKER - overhead_len 

768 if len(xmp) > max_data_bytes_in_marker: 

769 msg = "XMP data is too long" 

770 raise ValueError(msg) 

771 size = o16(2 + overhead_len + len(xmp)) 

772 extra += b"\xFF\xE1" + size + b"http://ns.adobe.com/xap/1.0/\x00" + xmp 

773 

774 icc_profile = info.get("icc_profile") 

775 if icc_profile: 

776 overhead_len = 14 # b"ICC_PROFILE\0" + o8(i) + o8(len(markers)) 

777 max_data_bytes_in_marker = MAX_BYTES_IN_MARKER - overhead_len 

778 markers = [] 

779 while icc_profile: 

780 markers.append(icc_profile[:max_data_bytes_in_marker]) 

781 icc_profile = icc_profile[max_data_bytes_in_marker:] 

782 i = 1 

783 for marker in markers: 

784 size = o16(2 + overhead_len + len(marker)) 

785 extra += ( 

786 b"\xFF\xE2" 

787 + size 

788 + b"ICC_PROFILE\0" 

789 + o8(i) 

790 + o8(len(markers)) 

791 + marker 

792 ) 

793 i += 1 

794 

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

796 

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

798 # says "progression" 

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

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

801 

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

803 

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

805 if isinstance(exif, Image.Exif): 

806 exif = exif.tobytes() 

807 if len(exif) > MAX_BYTES_IN_MARKER: 

808 msg = "EXIF data is too long" 

809 raise ValueError(msg) 

810 

811 # get keyword arguments 

812 im.encoderconfig = ( 

813 quality, 

814 progressive, 

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

816 optimize, 

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

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

819 dpi[0], 

820 dpi[1], 

821 subsampling, 

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

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

824 qtables, 

825 comment, 

826 extra, 

827 exif, 

828 ) 

829 

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

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

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

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

834 bufsize = 0 

835 if optimize or progressive: 

836 # CMYK can be bigger 

837 if im.mode == "CMYK": 

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

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

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

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

842 else: 

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

844 if exif: 

845 bufsize += len(exif) + 5 

846 if extra: 

847 bufsize += len(extra) + 1 

848 else: 

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

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

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

852 

853 ImageFile._save( 

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

855 ) 

856 

857 

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

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

860 tempfile = im._dump() 

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

862 try: 

863 os.unlink(tempfile) 

864 except OSError: 

865 pass 

866 

867 

868## 

869# Factory for making JPEG and MPO instances 

870def jpeg_factory( 

871 fp: IO[bytes], filename: str | bytes | None = None 

872) -> JpegImageFile | MpoImageFile: 

873 im = JpegImageFile(fp, filename) 

874 try: 

875 mpheader = im._getmp() 

876 if mpheader is not None and mpheader[45057] > 1: 

877 for segment, content in im.applist: 

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

879 # Ultra HDR images are not yet supported 

880 return im 

881 # It's actually an MPO 

882 from .MpoImagePlugin import MpoImageFile 

883 

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

885 im = MpoImageFile.adopt(im, mpheader) 

886 except (TypeError, IndexError): 

887 # It is really a JPEG 

888 pass 

889 except SyntaxError: 

890 warnings.warn( 

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

892 "interpreted as a base JPEG file" 

893 ) 

894 return im 

895 

896 

897# --------------------------------------------------------------------- 

898# Registry stuff 

899 

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

901Image.register_save(JpegImageFile.format, _save) 

902 

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

904 

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