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