JpegImagePlugin.py 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905
  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. #
  34. from __future__ import annotations
  35. import array
  36. import io
  37. import math
  38. import os
  39. import struct
  40. import subprocess
  41. import sys
  42. import tempfile
  43. import warnings
  44. from typing import IO, TYPE_CHECKING, Any
  45. from . import Image, ImageFile
  46. from ._binary import i16be as i16
  47. from ._binary import i32be as i32
  48. from ._binary import o8
  49. from ._binary import o16be as o16
  50. from ._deprecate import deprecate
  51. from .JpegPresets import presets
  52. if TYPE_CHECKING:
  53. from .MpoImagePlugin import MpoImageFile
  54. #
  55. # Parser
  56. def Skip(self: JpegImageFile, marker: int) -> None:
  57. n = i16(self.fp.read(2)) - 2
  58. ImageFile._safe_read(self.fp, n)
  59. def APP(self: JpegImageFile, marker: int) -> None:
  60. #
  61. # Application marker. Store these in the APP dictionary.
  62. # Also look for well-known application markers.
  63. n = i16(self.fp.read(2)) - 2
  64. s = ImageFile._safe_read(self.fp, n)
  65. app = f"APP{marker & 15}"
  66. self.app[app] = s # compatibility
  67. self.applist.append((app, s))
  68. if marker == 0xFFE0 and s[:4] == b"JFIF":
  69. # extract JFIF information
  70. self.info["jfif"] = version = i16(s, 5) # version
  71. self.info["jfif_version"] = divmod(version, 256)
  72. # extract JFIF properties
  73. try:
  74. jfif_unit = s[7]
  75. jfif_density = i16(s, 8), i16(s, 10)
  76. except Exception:
  77. pass
  78. else:
  79. if jfif_unit == 1:
  80. self.info["dpi"] = jfif_density
  81. elif jfif_unit == 2: # cm
  82. # 1 dpcm = 2.54 dpi
  83. self.info["dpi"] = tuple(d * 2.54 for d in jfif_density)
  84. self.info["jfif_unit"] = jfif_unit
  85. self.info["jfif_density"] = jfif_density
  86. elif marker == 0xFFE1 and s[:6] == b"Exif\0\0":
  87. # extract EXIF information
  88. if "exif" in self.info:
  89. self.info["exif"] += s[6:]
  90. else:
  91. self.info["exif"] = s
  92. self._exif_offset = self.fp.tell() - n + 6
  93. elif marker == 0xFFE1 and s[:29] == b"http://ns.adobe.com/xap/1.0/\x00":
  94. self.info["xmp"] = s.split(b"\x00", 1)[1]
  95. elif marker == 0xFFE2 and s[:5] == b"FPXR\0":
  96. # extract FlashPix information (incomplete)
  97. self.info["flashpix"] = s # FIXME: value will change
  98. elif marker == 0xFFE2 and s[:12] == b"ICC_PROFILE\0":
  99. # Since an ICC profile can be larger than the maximum size of
  100. # a JPEG marker (64K), we need provisions to split it into
  101. # multiple markers. The format defined by the ICC specifies
  102. # one or more APP2 markers containing the following data:
  103. # Identifying string ASCII "ICC_PROFILE\0" (12 bytes)
  104. # Marker sequence number 1, 2, etc (1 byte)
  105. # Number of markers Total of APP2's used (1 byte)
  106. # Profile data (remainder of APP2 data)
  107. # Decoders should use the marker sequence numbers to
  108. # reassemble the profile, rather than assuming that the APP2
  109. # markers appear in the correct sequence.
  110. self.icclist.append(s)
  111. elif marker == 0xFFED and s[:14] == b"Photoshop 3.0\x00":
  112. # parse the image resource block
  113. offset = 14
  114. photoshop = self.info.setdefault("photoshop", {})
  115. while s[offset : offset + 4] == b"8BIM":
  116. try:
  117. offset += 4
  118. # resource code
  119. code = i16(s, offset)
  120. offset += 2
  121. # resource name (usually empty)
  122. name_len = s[offset]
  123. # name = s[offset+1:offset+1+name_len]
  124. offset += 1 + name_len
  125. offset += offset & 1 # align
  126. # resource data block
  127. size = i32(s, offset)
  128. offset += 4
  129. data = s[offset : offset + size]
  130. if code == 0x03ED: # ResolutionInfo
  131. photoshop[code] = {
  132. "XResolution": i32(data, 0) / 65536,
  133. "DisplayedUnitsX": i16(data, 4),
  134. "YResolution": i32(data, 8) / 65536,
  135. "DisplayedUnitsY": i16(data, 12),
  136. }
  137. else:
  138. photoshop[code] = data
  139. offset += size
  140. offset += offset & 1 # align
  141. except struct.error:
  142. break # insufficient data
  143. elif marker == 0xFFEE and s[:5] == b"Adobe":
  144. self.info["adobe"] = i16(s, 5)
  145. # extract Adobe custom properties
  146. try:
  147. adobe_transform = s[11]
  148. except IndexError:
  149. pass
  150. else:
  151. self.info["adobe_transform"] = adobe_transform
  152. elif marker == 0xFFE2 and s[:4] == b"MPF\0":
  153. # extract MPO information
  154. self.info["mp"] = s[4:]
  155. # offset is current location minus buffer size
  156. # plus constant header size
  157. self.info["mpoffset"] = self.fp.tell() - n + 4
  158. def COM(self: JpegImageFile, marker: int) -> None:
  159. #
  160. # Comment marker. Store these in the APP dictionary.
  161. n = i16(self.fp.read(2)) - 2
  162. s = ImageFile._safe_read(self.fp, n)
  163. self.info["comment"] = s
  164. self.app["COM"] = s # compatibility
  165. self.applist.append(("COM", s))
  166. def SOF(self: JpegImageFile, marker: int) -> None:
  167. #
  168. # Start of frame marker. Defines the size and mode of the
  169. # image. JPEG is colour blind, so we use some simple
  170. # heuristics to map the number of layers to an appropriate
  171. # mode. Note that this could be made a bit brighter, by
  172. # looking for JFIF and Adobe APP markers.
  173. n = i16(self.fp.read(2)) - 2
  174. s = ImageFile._safe_read(self.fp, n)
  175. self._size = i16(s, 3), i16(s, 1)
  176. self.bits = s[0]
  177. if self.bits != 8:
  178. msg = f"cannot handle {self.bits}-bit layers"
  179. raise SyntaxError(msg)
  180. self.layers = s[5]
  181. if self.layers == 1:
  182. self._mode = "L"
  183. elif self.layers == 3:
  184. self._mode = "RGB"
  185. elif self.layers == 4:
  186. self._mode = "CMYK"
  187. else:
  188. msg = f"cannot handle {self.layers}-layer images"
  189. raise SyntaxError(msg)
  190. if marker in [0xFFC2, 0xFFC6, 0xFFCA, 0xFFCE]:
  191. self.info["progressive"] = self.info["progression"] = 1
  192. if self.icclist:
  193. # fixup icc profile
  194. self.icclist.sort() # sort by sequence number
  195. if self.icclist[0][13] == len(self.icclist):
  196. profile = [p[14:] for p in self.icclist]
  197. icc_profile = b"".join(profile)
  198. else:
  199. icc_profile = None # wrong number of fragments
  200. self.info["icc_profile"] = icc_profile
  201. self.icclist = []
  202. for i in range(6, len(s), 3):
  203. t = s[i : i + 3]
  204. # 4-tuples: id, vsamp, hsamp, qtable
  205. self.layer.append((t[0], t[1] // 16, t[1] & 15, t[2]))
  206. def DQT(self: JpegImageFile, marker: int) -> None:
  207. #
  208. # Define quantization table. Note that there might be more
  209. # than one table in each marker.
  210. # FIXME: The quantization tables can be used to estimate the
  211. # compression quality.
  212. n = i16(self.fp.read(2)) - 2
  213. s = ImageFile._safe_read(self.fp, n)
  214. while len(s):
  215. v = s[0]
  216. precision = 1 if (v // 16 == 0) else 2 # in bytes
  217. qt_length = 1 + precision * 64
  218. if len(s) < qt_length:
  219. msg = "bad quantization table marker"
  220. raise SyntaxError(msg)
  221. data = array.array("B" if precision == 1 else "H", s[1:qt_length])
  222. if sys.byteorder == "little" and precision > 1:
  223. data.byteswap() # the values are always big-endian
  224. self.quantization[v & 15] = [data[i] for i in zigzag_index]
  225. s = s[qt_length:]
  226. #
  227. # JPEG marker table
  228. MARKER = {
  229. 0xFFC0: ("SOF0", "Baseline DCT", SOF),
  230. 0xFFC1: ("SOF1", "Extended Sequential DCT", SOF),
  231. 0xFFC2: ("SOF2", "Progressive DCT", SOF),
  232. 0xFFC3: ("SOF3", "Spatial lossless", SOF),
  233. 0xFFC4: ("DHT", "Define Huffman table", Skip),
  234. 0xFFC5: ("SOF5", "Differential sequential DCT", SOF),
  235. 0xFFC6: ("SOF6", "Differential progressive DCT", SOF),
  236. 0xFFC7: ("SOF7", "Differential spatial", SOF),
  237. 0xFFC8: ("JPG", "Extension", None),
  238. 0xFFC9: ("SOF9", "Extended sequential DCT (AC)", SOF),
  239. 0xFFCA: ("SOF10", "Progressive DCT (AC)", SOF),
  240. 0xFFCB: ("SOF11", "Spatial lossless DCT (AC)", SOF),
  241. 0xFFCC: ("DAC", "Define arithmetic coding conditioning", Skip),
  242. 0xFFCD: ("SOF13", "Differential sequential DCT (AC)", SOF),
  243. 0xFFCE: ("SOF14", "Differential progressive DCT (AC)", SOF),
  244. 0xFFCF: ("SOF15", "Differential spatial (AC)", SOF),
  245. 0xFFD0: ("RST0", "Restart 0", None),
  246. 0xFFD1: ("RST1", "Restart 1", None),
  247. 0xFFD2: ("RST2", "Restart 2", None),
  248. 0xFFD3: ("RST3", "Restart 3", None),
  249. 0xFFD4: ("RST4", "Restart 4", None),
  250. 0xFFD5: ("RST5", "Restart 5", None),
  251. 0xFFD6: ("RST6", "Restart 6", None),
  252. 0xFFD7: ("RST7", "Restart 7", None),
  253. 0xFFD8: ("SOI", "Start of image", None),
  254. 0xFFD9: ("EOI", "End of image", None),
  255. 0xFFDA: ("SOS", "Start of scan", Skip),
  256. 0xFFDB: ("DQT", "Define quantization table", DQT),
  257. 0xFFDC: ("DNL", "Define number of lines", Skip),
  258. 0xFFDD: ("DRI", "Define restart interval", Skip),
  259. 0xFFDE: ("DHP", "Define hierarchical progression", SOF),
  260. 0xFFDF: ("EXP", "Expand reference component", Skip),
  261. 0xFFE0: ("APP0", "Application segment 0", APP),
  262. 0xFFE1: ("APP1", "Application segment 1", APP),
  263. 0xFFE2: ("APP2", "Application segment 2", APP),
  264. 0xFFE3: ("APP3", "Application segment 3", APP),
  265. 0xFFE4: ("APP4", "Application segment 4", APP),
  266. 0xFFE5: ("APP5", "Application segment 5", APP),
  267. 0xFFE6: ("APP6", "Application segment 6", APP),
  268. 0xFFE7: ("APP7", "Application segment 7", APP),
  269. 0xFFE8: ("APP8", "Application segment 8", APP),
  270. 0xFFE9: ("APP9", "Application segment 9", APP),
  271. 0xFFEA: ("APP10", "Application segment 10", APP),
  272. 0xFFEB: ("APP11", "Application segment 11", APP),
  273. 0xFFEC: ("APP12", "Application segment 12", APP),
  274. 0xFFED: ("APP13", "Application segment 13", APP),
  275. 0xFFEE: ("APP14", "Application segment 14", APP),
  276. 0xFFEF: ("APP15", "Application segment 15", APP),
  277. 0xFFF0: ("JPG0", "Extension 0", None),
  278. 0xFFF1: ("JPG1", "Extension 1", None),
  279. 0xFFF2: ("JPG2", "Extension 2", None),
  280. 0xFFF3: ("JPG3", "Extension 3", None),
  281. 0xFFF4: ("JPG4", "Extension 4", None),
  282. 0xFFF5: ("JPG5", "Extension 5", None),
  283. 0xFFF6: ("JPG6", "Extension 6", None),
  284. 0xFFF7: ("JPG7", "Extension 7", None),
  285. 0xFFF8: ("JPG8", "Extension 8", None),
  286. 0xFFF9: ("JPG9", "Extension 9", None),
  287. 0xFFFA: ("JPG10", "Extension 10", None),
  288. 0xFFFB: ("JPG11", "Extension 11", None),
  289. 0xFFFC: ("JPG12", "Extension 12", None),
  290. 0xFFFD: ("JPG13", "Extension 13", None),
  291. 0xFFFE: ("COM", "Comment", COM),
  292. }
  293. def _accept(prefix: bytes) -> bool:
  294. # Magic number was taken from https://en.wikipedia.org/wiki/JPEG
  295. return prefix[:3] == b"\xFF\xD8\xFF"
  296. ##
  297. # Image plugin for JPEG and JFIF images.
  298. class JpegImageFile(ImageFile.ImageFile):
  299. format = "JPEG"
  300. format_description = "JPEG (ISO 10918)"
  301. def _open(self) -> None:
  302. s = self.fp.read(3)
  303. if not _accept(s):
  304. msg = "not a JPEG file"
  305. raise SyntaxError(msg)
  306. s = b"\xFF"
  307. # Create attributes
  308. self.bits = self.layers = 0
  309. self._exif_offset = 0
  310. # JPEG specifics (internal)
  311. self.layer: list[tuple[int, int, int, int]] = []
  312. self._huffman_dc: dict[Any, Any] = {}
  313. self._huffman_ac: dict[Any, Any] = {}
  314. self.quantization: dict[int, list[int]] = {}
  315. self.app: dict[str, bytes] = {} # compatibility
  316. self.applist: list[tuple[str, bytes]] = []
  317. self.icclist: list[bytes] = []
  318. while True:
  319. i = s[0]
  320. if i == 0xFF:
  321. s = s + self.fp.read(1)
  322. i = i16(s)
  323. else:
  324. # Skip non-0xFF junk
  325. s = self.fp.read(1)
  326. continue
  327. if i in MARKER:
  328. name, description, handler = MARKER[i]
  329. if handler is not None:
  330. handler(self, i)
  331. if i == 0xFFDA: # start of scan
  332. rawmode = self.mode
  333. if self.mode == "CMYK":
  334. rawmode = "CMYK;I" # assume adobe conventions
  335. self.tile = [
  336. ImageFile._Tile("jpeg", (0, 0) + self.size, 0, (rawmode, ""))
  337. ]
  338. # self.__offset = self.fp.tell()
  339. break
  340. s = self.fp.read(1)
  341. elif i in {0, 0xFFFF}:
  342. # padded marker or junk; move on
  343. s = b"\xff"
  344. elif i == 0xFF00: # Skip extraneous data (escaped 0xFF)
  345. s = self.fp.read(1)
  346. else:
  347. msg = "no marker found"
  348. raise SyntaxError(msg)
  349. self._read_dpi_from_exif()
  350. def __getattr__(self, name: str) -> Any:
  351. if name in ("huffman_ac", "huffman_dc"):
  352. deprecate(name, 12)
  353. return getattr(self, "_" + name)
  354. raise AttributeError(name)
  355. def __getstate__(self) -> list[Any]:
  356. return super().__getstate__() + [self.layers, self.layer]
  357. def __setstate__(self, state: list[Any]) -> None:
  358. super().__setstate__(state)
  359. self.layers, self.layer = state[5:]
  360. def load_read(self, read_bytes: int) -> bytes:
  361. """
  362. internal: read more image data
  363. For premature EOF and LOAD_TRUNCATED_IMAGES adds EOI marker
  364. so libjpeg can finish decoding
  365. """
  366. s = self.fp.read(read_bytes)
  367. if not s and ImageFile.LOAD_TRUNCATED_IMAGES and not hasattr(self, "_ended"):
  368. # Premature EOF.
  369. # Pretend file is finished adding EOI marker
  370. self._ended = True
  371. return b"\xFF\xD9"
  372. return s
  373. def draft(
  374. self, mode: str | None, size: tuple[int, int] | None
  375. ) -> tuple[str, tuple[int, int, float, float]] | None:
  376. if len(self.tile) != 1:
  377. return None
  378. # Protect from second call
  379. if self.decoderconfig:
  380. return None
  381. d, e, o, a = self.tile[0]
  382. scale = 1
  383. original_size = self.size
  384. assert isinstance(a, tuple)
  385. if a[0] == "RGB" and mode in ["L", "YCbCr"]:
  386. self._mode = mode
  387. a = mode, ""
  388. if size:
  389. scale = min(self.size[0] // size[0], self.size[1] // size[1])
  390. for s in [8, 4, 2, 1]:
  391. if scale >= s:
  392. break
  393. assert e is not None
  394. e = (
  395. e[0],
  396. e[1],
  397. (e[2] - e[0] + s - 1) // s + e[0],
  398. (e[3] - e[1] + s - 1) // s + e[1],
  399. )
  400. self._size = ((self.size[0] + s - 1) // s, (self.size[1] + s - 1) // s)
  401. scale = s
  402. self.tile = [ImageFile._Tile(d, e, o, a)]
  403. self.decoderconfig = (scale, 0)
  404. box = (0, 0, original_size[0] / scale, original_size[1] / scale)
  405. return self.mode, box
  406. def load_djpeg(self) -> None:
  407. # ALTERNATIVE: handle JPEGs via the IJG command line utilities
  408. f, path = tempfile.mkstemp()
  409. os.close(f)
  410. if os.path.exists(self.filename):
  411. subprocess.check_call(["djpeg", "-outfile", path, self.filename])
  412. else:
  413. try:
  414. os.unlink(path)
  415. except OSError:
  416. pass
  417. msg = "Invalid Filename"
  418. raise ValueError(msg)
  419. try:
  420. with Image.open(path) as _im:
  421. _im.load()
  422. self.im = _im.im
  423. finally:
  424. try:
  425. os.unlink(path)
  426. except OSError:
  427. pass
  428. self._mode = self.im.mode
  429. self._size = self.im.size
  430. self.tile = []
  431. def _getexif(self) -> dict[int, Any] | None:
  432. return _getexif(self)
  433. def _read_dpi_from_exif(self) -> None:
  434. # If DPI isn't in JPEG header, fetch from EXIF
  435. if "dpi" in self.info or "exif" not in self.info:
  436. return
  437. try:
  438. exif = self.getexif()
  439. resolution_unit = exif[0x0128]
  440. x_resolution = exif[0x011A]
  441. try:
  442. dpi = float(x_resolution[0]) / x_resolution[1]
  443. except TypeError:
  444. dpi = x_resolution
  445. if math.isnan(dpi):
  446. msg = "DPI is not a number"
  447. raise ValueError(msg)
  448. if resolution_unit == 3: # cm
  449. # 1 dpcm = 2.54 dpi
  450. dpi *= 2.54
  451. self.info["dpi"] = dpi, dpi
  452. except (
  453. struct.error, # truncated EXIF
  454. KeyError, # dpi not included
  455. SyntaxError, # invalid/unreadable EXIF
  456. TypeError, # dpi is an invalid float
  457. ValueError, # dpi is an invalid float
  458. ZeroDivisionError, # invalid dpi rational value
  459. ):
  460. self.info["dpi"] = 72, 72
  461. def _getmp(self) -> dict[int, Any] | None:
  462. return _getmp(self)
  463. def _getexif(self: JpegImageFile) -> dict[int, Any] | None:
  464. if "exif" not in self.info:
  465. return None
  466. return self.getexif()._get_merged_dict()
  467. def _getmp(self: JpegImageFile) -> dict[int, Any] | None:
  468. # Extract MP information. This method was inspired by the "highly
  469. # experimental" _getexif version that's been in use for years now,
  470. # itself based on the ImageFileDirectory class in the TIFF plugin.
  471. # The MP record essentially consists of a TIFF file embedded in a JPEG
  472. # application marker.
  473. try:
  474. data = self.info["mp"]
  475. except KeyError:
  476. return None
  477. file_contents = io.BytesIO(data)
  478. head = file_contents.read(8)
  479. endianness = ">" if head[:4] == b"\x4d\x4d\x00\x2a" else "<"
  480. # process dictionary
  481. from . import TiffImagePlugin
  482. try:
  483. info = TiffImagePlugin.ImageFileDirectory_v2(head)
  484. file_contents.seek(info.next)
  485. info.load(file_contents)
  486. mp = dict(info)
  487. except Exception as e:
  488. msg = "malformed MP Index (unreadable directory)"
  489. raise SyntaxError(msg) from e
  490. # it's an error not to have a number of images
  491. try:
  492. quant = mp[0xB001]
  493. except KeyError as e:
  494. msg = "malformed MP Index (no number of images)"
  495. raise SyntaxError(msg) from e
  496. # get MP entries
  497. mpentries = []
  498. try:
  499. rawmpentries = mp[0xB002]
  500. for entrynum in range(0, quant):
  501. unpackedentry = struct.unpack_from(
  502. f"{endianness}LLLHH", rawmpentries, entrynum * 16
  503. )
  504. labels = ("Attribute", "Size", "DataOffset", "EntryNo1", "EntryNo2")
  505. mpentry = dict(zip(labels, unpackedentry))
  506. mpentryattr = {
  507. "DependentParentImageFlag": bool(mpentry["Attribute"] & (1 << 31)),
  508. "DependentChildImageFlag": bool(mpentry["Attribute"] & (1 << 30)),
  509. "RepresentativeImageFlag": bool(mpentry["Attribute"] & (1 << 29)),
  510. "Reserved": (mpentry["Attribute"] & (3 << 27)) >> 27,
  511. "ImageDataFormat": (mpentry["Attribute"] & (7 << 24)) >> 24,
  512. "MPType": mpentry["Attribute"] & 0x00FFFFFF,
  513. }
  514. if mpentryattr["ImageDataFormat"] == 0:
  515. mpentryattr["ImageDataFormat"] = "JPEG"
  516. else:
  517. msg = "unsupported picture format in MPO"
  518. raise SyntaxError(msg)
  519. mptypemap = {
  520. 0x000000: "Undefined",
  521. 0x010001: "Large Thumbnail (VGA Equivalent)",
  522. 0x010002: "Large Thumbnail (Full HD Equivalent)",
  523. 0x020001: "Multi-Frame Image (Panorama)",
  524. 0x020002: "Multi-Frame Image: (Disparity)",
  525. 0x020003: "Multi-Frame Image: (Multi-Angle)",
  526. 0x030000: "Baseline MP Primary Image",
  527. }
  528. mpentryattr["MPType"] = mptypemap.get(mpentryattr["MPType"], "Unknown")
  529. mpentry["Attribute"] = mpentryattr
  530. mpentries.append(mpentry)
  531. mp[0xB002] = mpentries
  532. except KeyError as e:
  533. msg = "malformed MP Index (bad MP Entry)"
  534. raise SyntaxError(msg) from e
  535. # Next we should try and parse the individual image unique ID list;
  536. # we don't because I've never seen this actually used in a real MPO
  537. # file and so can't test it.
  538. return mp
  539. # --------------------------------------------------------------------
  540. # stuff to save JPEG files
  541. RAWMODE = {
  542. "1": "L",
  543. "L": "L",
  544. "RGB": "RGB",
  545. "RGBX": "RGB",
  546. "CMYK": "CMYK;I", # assume adobe conventions
  547. "YCbCr": "YCbCr",
  548. }
  549. # fmt: off
  550. zigzag_index = (
  551. 0, 1, 5, 6, 14, 15, 27, 28,
  552. 2, 4, 7, 13, 16, 26, 29, 42,
  553. 3, 8, 12, 17, 25, 30, 41, 43,
  554. 9, 11, 18, 24, 31, 40, 44, 53,
  555. 10, 19, 23, 32, 39, 45, 52, 54,
  556. 20, 22, 33, 38, 46, 51, 55, 60,
  557. 21, 34, 37, 47, 50, 56, 59, 61,
  558. 35, 36, 48, 49, 57, 58, 62, 63,
  559. )
  560. samplings = {
  561. (1, 1, 1, 1, 1, 1): 0,
  562. (2, 1, 1, 1, 1, 1): 1,
  563. (2, 2, 1, 1, 1, 1): 2,
  564. }
  565. # fmt: on
  566. def get_sampling(im: Image.Image) -> int:
  567. # There's no subsampling when images have only 1 layer
  568. # (grayscale images) or when they are CMYK (4 layers),
  569. # so set subsampling to the default value.
  570. #
  571. # NOTE: currently Pillow can't encode JPEG to YCCK format.
  572. # If YCCK support is added in the future, subsampling code will have
  573. # to be updated (here and in JpegEncode.c) to deal with 4 layers.
  574. if not isinstance(im, JpegImageFile) or im.layers in (1, 4):
  575. return -1
  576. sampling = im.layer[0][1:3] + im.layer[1][1:3] + im.layer[2][1:3]
  577. return samplings.get(sampling, -1)
  578. def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
  579. if im.width == 0 or im.height == 0:
  580. msg = "cannot write empty image as JPEG"
  581. raise ValueError(msg)
  582. try:
  583. rawmode = RAWMODE[im.mode]
  584. except KeyError as e:
  585. msg = f"cannot write mode {im.mode} as JPEG"
  586. raise OSError(msg) from e
  587. info = im.encoderinfo
  588. dpi = [round(x) for x in info.get("dpi", (0, 0))]
  589. quality = info.get("quality", -1)
  590. subsampling = info.get("subsampling", -1)
  591. qtables = info.get("qtables")
  592. if quality == "keep":
  593. quality = -1
  594. subsampling = "keep"
  595. qtables = "keep"
  596. elif quality in presets:
  597. preset = presets[quality]
  598. quality = -1
  599. subsampling = preset.get("subsampling", -1)
  600. qtables = preset.get("quantization")
  601. elif not isinstance(quality, int):
  602. msg = "Invalid quality setting"
  603. raise ValueError(msg)
  604. else:
  605. if subsampling in presets:
  606. subsampling = presets[subsampling].get("subsampling", -1)
  607. if isinstance(qtables, str) and qtables in presets:
  608. qtables = presets[qtables].get("quantization")
  609. if subsampling == "4:4:4":
  610. subsampling = 0
  611. elif subsampling == "4:2:2":
  612. subsampling = 1
  613. elif subsampling == "4:2:0":
  614. subsampling = 2
  615. elif subsampling == "4:1:1":
  616. # For compatibility. Before Pillow 4.3, 4:1:1 actually meant 4:2:0.
  617. # Set 4:2:0 if someone is still using that value.
  618. subsampling = 2
  619. elif subsampling == "keep":
  620. if im.format != "JPEG":
  621. msg = "Cannot use 'keep' when original image is not a JPEG"
  622. raise ValueError(msg)
  623. subsampling = get_sampling(im)
  624. def validate_qtables(
  625. qtables: (
  626. str | tuple[list[int], ...] | list[list[int]] | dict[int, list[int]] | None
  627. )
  628. ) -> list[list[int]] | None:
  629. if qtables is None:
  630. return qtables
  631. if isinstance(qtables, str):
  632. try:
  633. lines = [
  634. int(num)
  635. for line in qtables.splitlines()
  636. for num in line.split("#", 1)[0].split()
  637. ]
  638. except ValueError as e:
  639. msg = "Invalid quantization table"
  640. raise ValueError(msg) from e
  641. else:
  642. qtables = [lines[s : s + 64] for s in range(0, len(lines), 64)]
  643. if isinstance(qtables, (tuple, list, dict)):
  644. if isinstance(qtables, dict):
  645. qtables = [
  646. qtables[key] for key in range(len(qtables)) if key in qtables
  647. ]
  648. elif isinstance(qtables, tuple):
  649. qtables = list(qtables)
  650. if not (0 < len(qtables) < 5):
  651. msg = "None or too many quantization tables"
  652. raise ValueError(msg)
  653. for idx, table in enumerate(qtables):
  654. try:
  655. if len(table) != 64:
  656. msg = "Invalid quantization table"
  657. raise TypeError(msg)
  658. table_array = array.array("H", table)
  659. except TypeError as e:
  660. msg = "Invalid quantization table"
  661. raise ValueError(msg) from e
  662. else:
  663. qtables[idx] = list(table_array)
  664. return qtables
  665. if qtables == "keep":
  666. if im.format != "JPEG":
  667. msg = "Cannot use 'keep' when original image is not a JPEG"
  668. raise ValueError(msg)
  669. qtables = getattr(im, "quantization", None)
  670. qtables = validate_qtables(qtables)
  671. extra = info.get("extra", b"")
  672. MAX_BYTES_IN_MARKER = 65533
  673. xmp = info.get("xmp")
  674. if xmp:
  675. overhead_len = 29 # b"http://ns.adobe.com/xap/1.0/\x00"
  676. max_data_bytes_in_marker = MAX_BYTES_IN_MARKER - overhead_len
  677. if len(xmp) > max_data_bytes_in_marker:
  678. msg = "XMP data is too long"
  679. raise ValueError(msg)
  680. size = o16(2 + overhead_len + len(xmp))
  681. extra += b"\xFF\xE1" + size + b"http://ns.adobe.com/xap/1.0/\x00" + xmp
  682. icc_profile = info.get("icc_profile")
  683. if icc_profile:
  684. overhead_len = 14 # b"ICC_PROFILE\0" + o8(i) + o8(len(markers))
  685. max_data_bytes_in_marker = MAX_BYTES_IN_MARKER - overhead_len
  686. markers = []
  687. while icc_profile:
  688. markers.append(icc_profile[:max_data_bytes_in_marker])
  689. icc_profile = icc_profile[max_data_bytes_in_marker:]
  690. i = 1
  691. for marker in markers:
  692. size = o16(2 + overhead_len + len(marker))
  693. extra += (
  694. b"\xFF\xE2"
  695. + size
  696. + b"ICC_PROFILE\0"
  697. + o8(i)
  698. + o8(len(markers))
  699. + marker
  700. )
  701. i += 1
  702. comment = info.get("comment", im.info.get("comment"))
  703. # "progressive" is the official name, but older documentation
  704. # says "progression"
  705. # FIXME: issue a warning if the wrong form is used (post-1.1.7)
  706. progressive = info.get("progressive", False) or info.get("progression", False)
  707. optimize = info.get("optimize", False)
  708. exif = info.get("exif", b"")
  709. if isinstance(exif, Image.Exif):
  710. exif = exif.tobytes()
  711. if len(exif) > MAX_BYTES_IN_MARKER:
  712. msg = "EXIF data is too long"
  713. raise ValueError(msg)
  714. # get keyword arguments
  715. im.encoderconfig = (
  716. quality,
  717. progressive,
  718. info.get("smooth", 0),
  719. optimize,
  720. info.get("keep_rgb", False),
  721. info.get("streamtype", 0),
  722. dpi[0],
  723. dpi[1],
  724. subsampling,
  725. info.get("restart_marker_blocks", 0),
  726. info.get("restart_marker_rows", 0),
  727. qtables,
  728. comment,
  729. extra,
  730. exif,
  731. )
  732. # if we optimize, libjpeg needs a buffer big enough to hold the whole image
  733. # in a shot. Guessing on the size, at im.size bytes. (raw pixel size is
  734. # channels*size, this is a value that's been used in a django patch.
  735. # https://github.com/matthewwithanm/django-imagekit/issues/50
  736. bufsize = 0
  737. if optimize or progressive:
  738. # CMYK can be bigger
  739. if im.mode == "CMYK":
  740. bufsize = 4 * im.size[0] * im.size[1]
  741. # keep sets quality to -1, but the actual value may be high.
  742. elif quality >= 95 or quality == -1:
  743. bufsize = 2 * im.size[0] * im.size[1]
  744. else:
  745. bufsize = im.size[0] * im.size[1]
  746. if exif:
  747. bufsize += len(exif) + 5
  748. if extra:
  749. bufsize += len(extra) + 1
  750. else:
  751. # The EXIF info needs to be written as one block, + APP1, + one spare byte.
  752. # Ensure that our buffer is big enough. Same with the icc_profile block.
  753. bufsize = max(bufsize, len(exif) + 5, len(extra) + 1)
  754. ImageFile._save(
  755. im, fp, [ImageFile._Tile("jpeg", (0, 0) + im.size, 0, rawmode)], bufsize
  756. )
  757. def _save_cjpeg(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
  758. # ALTERNATIVE: handle JPEGs via the IJG command line utilities.
  759. tempfile = im._dump()
  760. subprocess.check_call(["cjpeg", "-outfile", filename, tempfile])
  761. try:
  762. os.unlink(tempfile)
  763. except OSError:
  764. pass
  765. ##
  766. # Factory for making JPEG and MPO instances
  767. def jpeg_factory(
  768. fp: IO[bytes], filename: str | bytes | None = None
  769. ) -> JpegImageFile | MpoImageFile:
  770. im = JpegImageFile(fp, filename)
  771. try:
  772. mpheader = im._getmp()
  773. if mpheader is not None and mpheader[45057] > 1:
  774. for segment, content in im.applist:
  775. if segment == "APP1" and b' hdrgm:Version="' in content:
  776. # Ultra HDR images are not yet supported
  777. return im
  778. # It's actually an MPO
  779. from .MpoImagePlugin import MpoImageFile
  780. # Don't reload everything, just convert it.
  781. im = MpoImageFile.adopt(im, mpheader)
  782. except (TypeError, IndexError):
  783. # It is really a JPEG
  784. pass
  785. except SyntaxError:
  786. warnings.warn(
  787. "Image appears to be a malformed MPO file, it will be "
  788. "interpreted as a base JPEG file"
  789. )
  790. return im
  791. # ---------------------------------------------------------------------
  792. # Registry stuff
  793. Image.register_open(JpegImageFile.format, jpeg_factory, _accept)
  794. Image.register_save(JpegImageFile.format, _save)
  795. Image.register_extensions(JpegImageFile.format, [".jfif", ".jpe", ".jpg", ".jpeg"])
  796. Image.register_mime(JpegImageFile.format, "image/jpeg")