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