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