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