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