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