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")