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

467 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 = "APP%d" % (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 self.info["jfif_unit"] = jfif_unit 

94 self.info["jfif_density"] = jfif_density 

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

96 # extract EXIF information 

97 if "exif" in self.info: 

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

99 else: 

100 self.info["exif"] = s 

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

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

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

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

105 # extract FlashPix information (incomplete) 

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

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

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

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

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

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

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

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

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

115 # Profile data (remainder of APP2 data) 

116 # Decoders should use the marker sequence numbers to 

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

118 # markers appear in the correct sequence. 

119 self.icclist.append(s) 

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

121 # parse the image resource block 

122 offset = 14 

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

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

125 try: 

126 offset += 4 

127 # resource code 

128 code = i16(s, offset) 

129 offset += 2 

130 # resource name (usually empty) 

131 name_len = s[offset] 

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

133 offset += 1 + name_len 

134 offset += offset & 1 # align 

135 # resource data block 

136 size = i32(s, offset) 

137 offset += 4 

138 data = s[offset : offset + size] 

139 if code == 0x03ED: # ResolutionInfo 

140 photoshop[code] = { 

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

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

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

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

145 } 

146 else: 

147 photoshop[code] = data 

148 offset += size 

149 offset += offset & 1 # align 

150 except struct.error: 

151 break # insufficient data 

152 

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

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

155 # extract Adobe custom properties 

156 try: 

157 adobe_transform = s[11] 

158 except IndexError: 

159 pass 

160 else: 

161 self.info["adobe_transform"] = adobe_transform 

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

163 # extract MPO information 

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

165 # offset is current location minus buffer size 

166 # plus constant header size 

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

168 

169 

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

171 # 

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

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

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

175 

176 self.info["comment"] = s 

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

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

179 

180 

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

182 # 

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

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

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

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

187 # looking for JFIF and Adobe APP markers. 

188 

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

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

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

192 

193 self.bits = s[0] 

194 if self.bits != 8: 

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

196 raise SyntaxError(msg) 

197 

198 self.layers = s[5] 

199 if self.layers == 1: 

200 self._mode = "L" 

201 elif self.layers == 3: 

202 self._mode = "RGB" 

203 elif self.layers == 4: 

204 self._mode = "CMYK" 

205 else: 

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

207 raise SyntaxError(msg) 

208 

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

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

211 

212 if self.icclist: 

213 # fixup icc profile 

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

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

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

217 icc_profile = b"".join(profile) 

218 else: 

219 icc_profile = None # wrong number of fragments 

220 self.info["icc_profile"] = icc_profile 

221 self.icclist = [] 

222 

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

224 t = s[i : i + 3] 

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

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

227 

228 

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

230 # 

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

232 # than one table in each marker. 

233 

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

235 # compression quality. 

236 

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

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

239 while len(s): 

240 v = s[0] 

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

242 qt_length = 1 + precision * 64 

243 if len(s) < qt_length: 

244 msg = "bad quantization table marker" 

245 raise SyntaxError(msg) 

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

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

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

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

250 s = s[qt_length:] 

251 

252 

253# 

254# JPEG marker table 

255 

256MARKER = { 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

320} 

321 

322 

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

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

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

326 

327 

328## 

329# Image plugin for JPEG and JFIF images. 

330 

331 

332class JpegImageFile(ImageFile.ImageFile): 

333 format = "JPEG" 

334 format_description = "JPEG (ISO 10918)" 

335 

336 def _open(self) -> None: 

337 s = self.fp.read(3) 

338 

339 if not _accept(s): 

340 msg = "not a JPEG file" 

341 raise SyntaxError(msg) 

342 s = b"\xFF" 

343 

344 # Create attributes 

345 self.bits = self.layers = 0 

346 self._exif_offset = 0 

347 

348 # JPEG specifics (internal) 

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

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

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

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

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

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

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

356 

357 while True: 

358 i = s[0] 

359 if i == 0xFF: 

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

361 i = i16(s) 

362 else: 

363 # Skip non-0xFF junk 

364 s = self.fp.read(1) 

365 continue 

366 

367 if i in MARKER: 

368 name, description, handler = MARKER[i] 

369 if handler is not None: 

370 handler(self, i) 

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

372 rawmode = self.mode 

373 if self.mode == "CMYK": 

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

375 self.tile = [ 

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

377 ] 

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

379 break 

380 s = self.fp.read(1) 

381 elif i in {0, 0xFFFF}: 

382 # padded marker or junk; move on 

383 s = b"\xff" 

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

385 s = self.fp.read(1) 

386 else: 

387 msg = "no marker found" 

388 raise SyntaxError(msg) 

389 

390 self._read_dpi_from_exif() 

391 

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

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

394 deprecate(name, 12) 

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

396 raise AttributeError(name) 

397 

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

399 """ 

400 internal: read more image data 

401 For premature EOF and LOAD_TRUNCATED_IMAGES adds EOI marker 

402 so libjpeg can finish decoding 

403 """ 

404 s = self.fp.read(read_bytes) 

405 

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

407 # Premature EOF. 

408 # Pretend file is finished adding EOI marker 

409 self._ended = True 

410 return b"\xFF\xD9" 

411 

412 return s 

413 

414 def draft( 

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

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

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

418 return None 

419 

420 # Protect from second call 

421 if self.decoderconfig: 

422 return None 

423 

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

425 scale = 1 

426 original_size = self.size 

427 

428 assert isinstance(a, tuple) 

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

430 self._mode = mode 

431 a = mode, "" 

432 

433 if size: 

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

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

436 if scale >= s: 

437 break 

438 assert e is not None 

439 e = ( 

440 e[0], 

441 e[1], 

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

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

444 ) 

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

446 scale = s 

447 

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

449 self.decoderconfig = (scale, 0) 

450 

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

452 return self.mode, box 

453 

454 def load_djpeg(self) -> None: 

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

456 

457 f, path = tempfile.mkstemp() 

458 os.close(f) 

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

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

461 else: 

462 try: 

463 os.unlink(path) 

464 except OSError: 

465 pass 

466 

467 msg = "Invalid Filename" 

468 raise ValueError(msg) 

469 

470 try: 

471 with Image.open(path) as _im: 

472 _im.load() 

473 self.im = _im.im 

474 finally: 

475 try: 

476 os.unlink(path) 

477 except OSError: 

478 pass 

479 

480 self._mode = self.im.mode 

481 self._size = self.im.size 

482 

483 self.tile = [] 

484 

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

486 return _getexif(self) 

487 

488 def _read_dpi_from_exif(self) -> None: 

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

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

491 return 

492 try: 

493 exif = self.getexif() 

494 resolution_unit = exif[0x0128] 

495 x_resolution = exif[0x011A] 

496 try: 

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

498 except TypeError: 

499 dpi = x_resolution 

500 if math.isnan(dpi): 

501 msg = "DPI is not a number" 

502 raise ValueError(msg) 

503 if resolution_unit == 3: # cm 

504 # 1 dpcm = 2.54 dpi 

505 dpi *= 2.54 

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

507 except ( 

508 struct.error, # truncated EXIF 

509 KeyError, # dpi not included 

510 SyntaxError, # invalid/unreadable EXIF 

511 TypeError, # dpi is an invalid float 

512 ValueError, # dpi is an invalid float 

513 ZeroDivisionError, # invalid dpi rational value 

514 ): 

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

516 

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

518 return _getmp(self) 

519 

520 

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

522 if "exif" not in self.info: 

523 return None 

524 return self.getexif()._get_merged_dict() 

525 

526 

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

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

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

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

531 

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

533 # application marker. 

534 try: 

535 data = self.info["mp"] 

536 except KeyError: 

537 return None 

538 file_contents = io.BytesIO(data) 

539 head = file_contents.read(8) 

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

541 # process dictionary 

542 from . import TiffImagePlugin 

543 

544 try: 

545 info = TiffImagePlugin.ImageFileDirectory_v2(head) 

546 file_contents.seek(info.next) 

547 info.load(file_contents) 

548 mp = dict(info) 

549 except Exception as e: 

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

551 raise SyntaxError(msg) from e 

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

553 try: 

554 quant = mp[0xB001] 

555 except KeyError as e: 

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

557 raise SyntaxError(msg) from e 

558 # get MP entries 

559 mpentries = [] 

560 try: 

561 rawmpentries = mp[0xB002] 

562 for entrynum in range(0, quant): 

563 unpackedentry = struct.unpack_from( 

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

565 ) 

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

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

568 mpentryattr = { 

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

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

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

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

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

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

575 } 

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

577 mpentryattr["ImageDataFormat"] = "JPEG" 

578 else: 

579 msg = "unsupported picture format in MPO" 

580 raise SyntaxError(msg) 

581 mptypemap = { 

582 0x000000: "Undefined", 

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

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

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

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

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

588 0x030000: "Baseline MP Primary Image", 

589 } 

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

591 mpentry["Attribute"] = mpentryattr 

592 mpentries.append(mpentry) 

593 mp[0xB002] = mpentries 

594 except KeyError as e: 

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

596 raise SyntaxError(msg) from e 

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

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

599 # file and so can't test it. 

600 return mp 

601 

602 

603# -------------------------------------------------------------------- 

604# stuff to save JPEG files 

605 

606RAWMODE = { 

607 "1": "L", 

608 "L": "L", 

609 "RGB": "RGB", 

610 "RGBX": "RGB", 

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

612 "YCbCr": "YCbCr", 

613} 

614 

615# fmt: off 

616zigzag_index = ( 

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

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

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

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

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

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

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

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

625) 

626 

627samplings = { 

628 (1, 1, 1, 1, 1, 1): 0, 

629 (2, 1, 1, 1, 1, 1): 1, 

630 (2, 2, 1, 1, 1, 1): 2, 

631} 

632# fmt: on 

633 

634 

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

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

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

638 # so set subsampling to the default value. 

639 # 

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

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

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

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

644 return -1 

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

646 return samplings.get(sampling, -1) 

647 

648 

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

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

651 msg = "cannot write empty image as JPEG" 

652 raise ValueError(msg) 

653 

654 try: 

655 rawmode = RAWMODE[im.mode] 

656 except KeyError as e: 

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

658 raise OSError(msg) from e 

659 

660 info = im.encoderinfo 

661 

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

663 

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

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

666 qtables = info.get("qtables") 

667 

668 if quality == "keep": 

669 quality = -1 

670 subsampling = "keep" 

671 qtables = "keep" 

672 elif quality in presets: 

673 preset = presets[quality] 

674 quality = -1 

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

676 qtables = preset.get("quantization") 

677 elif not isinstance(quality, int): 

678 msg = "Invalid quality setting" 

679 raise ValueError(msg) 

680 else: 

681 if subsampling in presets: 

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

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

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

685 

686 if subsampling == "4:4:4": 

687 subsampling = 0 

688 elif subsampling == "4:2:2": 

689 subsampling = 1 

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

691 subsampling = 2 

692 elif subsampling == "4:1:1": 

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

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

695 subsampling = 2 

696 elif subsampling == "keep": 

697 if im.format != "JPEG": 

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

699 raise ValueError(msg) 

700 subsampling = get_sampling(im) 

701 

702 def validate_qtables( 

703 qtables: ( 

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

705 ) 

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

707 if qtables is None: 

708 return qtables 

709 if isinstance(qtables, str): 

710 try: 

711 lines = [ 

712 int(num) 

713 for line in qtables.splitlines() 

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

715 ] 

716 except ValueError as e: 

717 msg = "Invalid quantization table" 

718 raise ValueError(msg) from e 

719 else: 

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

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

722 if isinstance(qtables, dict): 

723 qtables = [ 

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

725 ] 

726 elif isinstance(qtables, tuple): 

727 qtables = list(qtables) 

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

729 msg = "None or too many quantization tables" 

730 raise ValueError(msg) 

731 for idx, table in enumerate(qtables): 

732 try: 

733 if len(table) != 64: 

734 msg = "Invalid quantization table" 

735 raise TypeError(msg) 

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

737 except TypeError as e: 

738 msg = "Invalid quantization table" 

739 raise ValueError(msg) from e 

740 else: 

741 qtables[idx] = list(table_array) 

742 return qtables 

743 

744 if qtables == "keep": 

745 if im.format != "JPEG": 

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

747 raise ValueError(msg) 

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

749 qtables = validate_qtables(qtables) 

750 

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

752 

753 MAX_BYTES_IN_MARKER = 65533 

754 xmp = info.get("xmp", im.info.get("xmp")) 

755 if xmp: 

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

757 max_data_bytes_in_marker = MAX_BYTES_IN_MARKER - overhead_len 

758 if len(xmp) > max_data_bytes_in_marker: 

759 msg = "XMP data is too long" 

760 raise ValueError(msg) 

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

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

763 

764 icc_profile = info.get("icc_profile") 

765 if icc_profile: 

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

767 max_data_bytes_in_marker = MAX_BYTES_IN_MARKER - overhead_len 

768 markers = [] 

769 while icc_profile: 

770 markers.append(icc_profile[:max_data_bytes_in_marker]) 

771 icc_profile = icc_profile[max_data_bytes_in_marker:] 

772 i = 1 

773 for marker in markers: 

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

775 extra += ( 

776 b"\xFF\xE2" 

777 + size 

778 + b"ICC_PROFILE\0" 

779 + o8(i) 

780 + o8(len(markers)) 

781 + marker 

782 ) 

783 i += 1 

784 

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

786 

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

788 # says "progression" 

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

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

791 

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

793 

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

795 if isinstance(exif, Image.Exif): 

796 exif = exif.tobytes() 

797 if len(exif) > MAX_BYTES_IN_MARKER: 

798 msg = "EXIF data is too long" 

799 raise ValueError(msg) 

800 

801 # get keyword arguments 

802 im.encoderconfig = ( 

803 quality, 

804 progressive, 

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

806 optimize, 

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

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

809 dpi[0], 

810 dpi[1], 

811 subsampling, 

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

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

814 qtables, 

815 comment, 

816 extra, 

817 exif, 

818 ) 

819 

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

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

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

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

824 bufsize = 0 

825 if optimize or progressive: 

826 # CMYK can be bigger 

827 if im.mode == "CMYK": 

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

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

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

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

832 else: 

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

834 if exif: 

835 bufsize += len(exif) + 5 

836 if extra: 

837 bufsize += len(extra) + 1 

838 else: 

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

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

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

842 

843 ImageFile._save( 

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

845 ) 

846 

847 

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

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

850 tempfile = im._dump() 

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

852 try: 

853 os.unlink(tempfile) 

854 except OSError: 

855 pass 

856 

857 

858## 

859# Factory for making JPEG and MPO instances 

860def jpeg_factory( 

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

862) -> JpegImageFile | MpoImageFile: 

863 im = JpegImageFile(fp, filename) 

864 try: 

865 mpheader = im._getmp() 

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

867 for segment, content in im.applist: 

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

869 # Ultra HDR images are not yet supported 

870 return im 

871 # It's actually an MPO 

872 from .MpoImagePlugin import MpoImageFile 

873 

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

875 im = MpoImageFile.adopt(im, mpheader) 

876 except (TypeError, IndexError): 

877 # It is really a JPEG 

878 pass 

879 except SyntaxError: 

880 warnings.warn( 

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

882 "interpreted as a base JPEG file" 

883 ) 

884 return im 

885 

886 

887# --------------------------------------------------------------------- 

888# Registry stuff 

889 

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

891Image.register_save(JpegImageFile.format, _save) 

892 

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

894 

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