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 try:
131 while s[offset : offset + 4] == b"8BIM":
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 pass # 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, struct.error) 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}
629zigzag_index = (
630 0, 1, 5, 6, 14, 15, 27, 28,
631 2, 4, 7, 13, 16, 26, 29, 42,
632 3, 8, 12, 17, 25, 30, 41, 43,
633 9, 11, 18, 24, 31, 40, 44, 53,
634 10, 19, 23, 32, 39, 45, 52, 54,
635 20, 22, 33, 38, 46, 51, 55, 60,
636 21, 34, 37, 47, 50, 56, 59, 61,
637 35, 36, 48, 49, 57, 58, 62, 63,
638) # fmt: skip
640samplings = {
641 (1, 1, 1, 1, 1, 1): 0,
642 (2, 1, 1, 1, 1, 1): 1,
643 (2, 2, 1, 1, 1, 1): 2,
644}
647def get_sampling(im: Image.Image) -> int:
648 # There's no subsampling when images have only 1 layer
649 # (grayscale images) or when they are CMYK (4 layers),
650 # so set subsampling to the default value.
651 #
652 # NOTE: currently Pillow can't encode JPEG to YCCK format.
653 # If YCCK support is added in the future, subsampling code will have
654 # to be updated (here and in JpegEncode.c) to deal with 4 layers.
655 if not isinstance(im, JpegImageFile) or im.layers in (1, 4):
656 return -1
657 sampling = im.layer[0][1:3] + im.layer[1][1:3] + im.layer[2][1:3]
658 return samplings.get(sampling, -1)
661def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
662 try:
663 rawmode = RAWMODE[im.mode]
664 except KeyError as e:
665 msg = f"cannot write mode {im.mode} as JPEG"
666 raise OSError(msg) from e
668 info = im.encoderinfo
670 dpi = [round(x) for x in info.get("dpi", (0, 0))]
672 quality = info.get("quality", -1)
673 subsampling = info.get("subsampling", -1)
674 qtables = info.get("qtables")
676 if quality == "keep":
677 quality = -1
678 subsampling = "keep"
679 qtables = "keep"
680 elif quality in presets:
681 preset = presets[quality]
682 quality = -1
683 subsampling = preset.get("subsampling", -1)
684 qtables = preset.get("quantization")
685 elif not isinstance(quality, int):
686 msg = "Invalid quality setting"
687 raise ValueError(msg)
688 else:
689 if subsampling in presets:
690 subsampling = presets[subsampling].get("subsampling", -1)
691 if isinstance(qtables, str) and qtables in presets:
692 qtables = presets[qtables].get("quantization")
694 if subsampling == "4:4:4":
695 subsampling = 0
696 elif subsampling == "4:2:2":
697 subsampling = 1
698 elif subsampling == "4:2:0":
699 subsampling = 2
700 elif subsampling == "4:1:1":
701 # For compatibility. Before Pillow 4.3, 4:1:1 actually meant 4:2:0.
702 # Set 4:2:0 if someone is still using that value.
703 subsampling = 2
704 elif subsampling == "keep":
705 if im.format != "JPEG":
706 msg = "Cannot use 'keep' when original image is not a JPEG"
707 raise ValueError(msg)
708 subsampling = get_sampling(im)
710 def validate_qtables(
711 qtables: (
712 str | tuple[list[int], ...] | list[list[int]] | dict[int, list[int]] | None
713 ),
714 ) -> list[list[int]] | None:
715 if qtables is None:
716 return qtables
717 if isinstance(qtables, str):
718 try:
719 lines = [
720 int(num)
721 for line in qtables.splitlines()
722 for num in line.split("#", 1)[0].split()
723 ]
724 except ValueError as e:
725 msg = "Invalid quantization table"
726 raise ValueError(msg) from e
727 else:
728 qtables = [lines[s : s + 64] for s in range(0, len(lines), 64)]
729 if isinstance(qtables, (tuple, list, dict)):
730 if isinstance(qtables, dict):
731 qtables = [
732 qtables[key] for key in range(len(qtables)) if key in qtables
733 ]
734 elif isinstance(qtables, tuple):
735 qtables = list(qtables)
736 if not (0 < len(qtables) < 5):
737 msg = "None or too many quantization tables"
738 raise ValueError(msg)
739 try:
740 for idx, table in enumerate(qtables):
741 if len(table) != 64:
742 msg = "Invalid quantization table"
743 raise TypeError(msg)
744 qtables[idx] = list(array.array("H", table))
745 except TypeError as e:
746 msg = "Invalid quantization table"
747 raise ValueError(msg) from e
748 return qtables
750 if qtables == "keep":
751 if im.format != "JPEG":
752 msg = "Cannot use 'keep' when original image is not a JPEG"
753 raise ValueError(msg)
754 qtables = getattr(im, "quantization", None)
755 qtables = validate_qtables(qtables)
757 extra = info.get("extra", b"")
759 MAX_BYTES_IN_MARKER = 65533
760 if xmp := info.get("xmp"):
761 overhead_len = 29 # b"http://ns.adobe.com/xap/1.0/\x00"
762 max_data_bytes_in_marker = MAX_BYTES_IN_MARKER - overhead_len
763 if len(xmp) > max_data_bytes_in_marker:
764 msg = "XMP data is too long"
765 raise ValueError(msg)
766 size = o16(2 + overhead_len + len(xmp))
767 extra += b"\xff\xe1" + size + b"http://ns.adobe.com/xap/1.0/\x00" + xmp
769 if icc_profile := info.get("icc_profile"):
770 overhead_len = 14 # b"ICC_PROFILE\0" + o8(i) + o8(len(markers))
771 max_data_bytes_in_marker = MAX_BYTES_IN_MARKER - overhead_len
772 markers = []
773 while icc_profile:
774 markers.append(icc_profile[:max_data_bytes_in_marker])
775 icc_profile = icc_profile[max_data_bytes_in_marker:]
776 i = 1
777 for marker in markers:
778 size = o16(2 + overhead_len + len(marker))
779 extra += (
780 b"\xff\xe2"
781 + size
782 + b"ICC_PROFILE\0"
783 + o8(i)
784 + o8(len(markers))
785 + marker
786 )
787 i += 1
789 comment = info.get("comment", im.info.get("comment"))
791 # "progressive" is the official name, but older documentation
792 # says "progression"
793 # FIXME: issue a warning if the wrong form is used (post-1.1.7)
794 progressive = info.get("progressive", False) or info.get("progression", False)
796 optimize = info.get("optimize", False)
798 exif = info.get("exif", b"")
799 if isinstance(exif, Image.Exif):
800 exif = exif.tobytes()
801 if len(exif) > MAX_BYTES_IN_MARKER:
802 msg = "EXIF data is too long"
803 raise ValueError(msg)
805 # get keyword arguments
806 im.encoderconfig = (
807 quality,
808 progressive,
809 info.get("smooth", 0),
810 optimize,
811 info.get("keep_rgb", False),
812 info.get("streamtype", 0),
813 dpi,
814 subsampling,
815 info.get("restart_marker_blocks", 0),
816 info.get("restart_marker_rows", 0),
817 qtables,
818 comment,
819 extra,
820 exif,
821 )
823 # if we optimize, libjpeg needs a buffer big enough to hold the whole image
824 # in a shot. Guessing on the size, at im.size bytes. (raw pixel size is
825 # channels*size, this is a value that's been used in a django patch.
826 # https://github.com/matthewwithanm/django-imagekit/issues/50
827 if optimize or progressive:
828 # CMYK can be bigger
829 if im.mode == "CMYK":
830 bufsize = 4 * im.size[0] * im.size[1]
831 # keep sets quality to -1, but the actual value may be high.
832 elif quality >= 95 or quality == -1:
833 bufsize = 2 * im.size[0] * im.size[1]
834 else:
835 bufsize = im.size[0] * im.size[1]
836 if exif:
837 bufsize += len(exif) + 5
838 if extra:
839 bufsize += len(extra) + 1
840 else:
841 # The EXIF info needs to be written as one block, + APP1, + one spare byte.
842 # Ensure that our buffer is big enough. Same with the icc_profile block.
843 bufsize = max(len(exif) + 5, len(extra) + 1)
845 ImageFile._save(
846 im, fp, [ImageFile._Tile("jpeg", (0, 0) + im.size, 0, rawmode)], bufsize
847 )
850##
851# Factory for making JPEG and MPO instances
852def jpeg_factory(
853 fp: IO[bytes], filename: str | bytes | None = None
854) -> JpegImageFile | MpoImageFile:
855 im = JpegImageFile(fp, filename)
856 try:
857 mpheader = im._getmp()
858 if mpheader is not None and mpheader[45057] > 1:
859 for segment, content in im.applist:
860 if segment == "APP1" and b' hdrgm:Version="' in content:
861 # Ultra HDR images are not yet supported
862 return im
863 # It's actually an MPO
864 from .MpoImagePlugin import MpoImageFile
866 # Don't reload everything, just convert it.
867 im = MpoImageFile.adopt(im, mpheader)
868 except (TypeError, IndexError):
869 # It is really a JPEG
870 pass
871 except SyntaxError:
872 warnings.warn(
873 "Image appears to be a malformed MPO file, it will be "
874 "interpreted as a base JPEG file"
875 )
876 return im
879# ---------------------------------------------------------------------
880# Registry stuff
882Image.register_open(JpegImageFile.format, jpeg_factory, _accept)
883Image.register_save(JpegImageFile.format, _save)
885Image.register_extensions(JpegImageFile.format, [".jfif", ".jpe", ".jpg", ".jpeg"])
887Image.register_mime(JpegImageFile.format, "image/jpeg")