Pillow/src/PIL/JpegImagePlugin.py

826 lines
28 KiB
Python
Raw Normal View History

2010-07-31 06:52:47 +04:00
#
# The Python Imaging Library.
# $Id$
#
# JPEG (JFIF) file handling
#
2015-05-29 07:59:54 +03:00
# See "Digital Compression and Coding of Continuous-Tone Still Images,
2010-07-31 06:52:47 +04:00
# Part 1, Requirements and Guidelines" (CCITT T.81 / ISO 10918-1)
#
# History:
# 1995-09-09 fl Created
# 1995-09-13 fl Added full parser
# 1996-03-25 fl Added hack to use the IJG command line utilities
# 1996-05-05 fl Workaround Photoshop 2.5 CMYK polarity bug
# 1996-05-28 fl Added draft support, JFIF version (0.1)
# 1996-12-30 fl Added encoder options, added progression property (0.2)
# 1997-08-27 fl Save mode 1 images as BW (0.3)
# 1998-07-12 fl Added YCbCr to draft and save methods (0.4)
# 1998-10-19 fl Don't hang on files using 16-bit DQT's (0.4.1)
# 2001-04-16 fl Extract DPI settings from JFIF files (0.4.2)
# 2002-07-01 fl Skip pad bytes before markers; identify Exif files (0.4.3)
# 2003-04-25 fl Added experimental EXIF decoder (0.5)
# 2003-06-06 fl Added experimental EXIF GPSinfo decoder
# 2003-09-13 fl Extract COM markers
# 2009-09-06 fl Added icc_profile support (from Florian Hoech)
# 2009-03-06 fl Changed CMYK handling; always use Adobe polarity (0.6)
# 2009-03-08 fl Added subsampling support (from Justin Huff).
#
# Copyright (c) 1997-2003 by Secret Labs AB.
# Copyright (c) 1995-1996 by Fredrik Lundh.
#
# See the README file for information on usage and redistribution.
#
2014-05-05 23:41:09 +04:00
import array
import io
2021-05-07 14:23:29 +03:00
import math
import os
import struct
import subprocess
import sys
import tempfile
import warnings
from . import Image, ImageFile, TiffImagePlugin
2020-09-01 20:16:46 +03:00
from ._binary import i16be as i16
from ._binary import i32be as i32
from ._binary import o8
from .JpegPresets import presets
2010-07-31 06:52:47 +04:00
#
# Parser
2019-03-21 16:28:20 +03:00
2010-07-31 06:52:47 +04:00
def Skip(self, marker):
2019-03-21 16:28:20 +03:00
n = i16(self.fp.read(2)) - 2
2010-07-31 06:52:47 +04:00
ImageFile._safe_read(self.fp, n)
2014-05-05 23:41:09 +04:00
2010-07-31 06:52:47 +04:00
def APP(self, marker):
#
# Application marker. Store these in the APP dictionary.
# Also look for well-known application markers.
2019-03-21 16:28:20 +03:00
n = i16(self.fp.read(2)) - 2
2010-07-31 06:52:47 +04:00
s = ImageFile._safe_read(self.fp, n)
2014-05-05 23:41:09 +04:00
app = "APP%d" % (marker & 15)
2010-07-31 06:52:47 +04:00
2014-05-05 23:41:09 +04:00
self.app[app] = s # compatibility
2010-07-31 06:52:47 +04:00
self.applist.append((app, s))
if marker == 0xFFE0 and s[:4] == b"JFIF":
2010-07-31 06:52:47 +04:00
# extract JFIF information
2014-05-05 23:41:09 +04:00
self.info["jfif"] = version = i16(s, 5) # version
2010-07-31 06:52:47 +04:00
self.info["jfif_version"] = divmod(version, 256)
# extract JFIF properties
try:
jfif_unit = s[7]
2010-07-31 06:52:47 +04:00
jfif_density = i16(s, 8), i16(s, 10)
except Exception:
2010-07-31 06:52:47 +04:00
pass
else:
if jfif_unit == 1:
self.info["dpi"] = jfif_density
self.info["jfif_unit"] = jfif_unit
self.info["jfif_density"] = jfif_density
elif marker == 0xFFE1 and s[:5] == b"Exif\0":
2018-01-09 18:24:57 +03:00
if "exif" not in self.info:
2019-03-05 10:11:46 +03:00
# extract EXIF information (incomplete)
2018-01-09 18:24:57 +03:00
self.info["exif"] = s # FIXME: value will change
elif marker == 0xFFE2 and s[:5] == b"FPXR\0":
2010-07-31 06:52:47 +04:00
# extract FlashPix information (incomplete)
2014-05-05 23:41:09 +04:00
self.info["flashpix"] = s # FIXME: value will change
elif marker == 0xFFE2 and s[:12] == b"ICC_PROFILE\0":
2010-07-31 06:52:47 +04:00
# Since an ICC profile can be larger than the maximum size of
# a JPEG marker (64K), we need provisions to split it into
# multiple markers. The format defined by the ICC specifies
# one or more APP2 markers containing the following data:
# Identifying string ASCII "ICC_PROFILE\0" (12 bytes)
# Marker sequence number 1, 2, etc (1 byte)
# Number of markers Total of APP2's used (1 byte)
# Profile data (remainder of APP2 data)
# Decoders should use the marker sequence numbers to
# reassemble the profile, rather than assuming that the APP2
# markers appear in the correct sequence.
self.icclist.append(s)
2020-01-19 03:15:37 +03:00
elif marker == 0xFFED and s[:14] == b"Photoshop 3.0\x00":
2020-01-19 14:48:01 +03:00
# parse the image resource block
offset = 14
photoshop = self.info.setdefault("photoshop", {})
while s[offset : offset + 4] == b"8BIM":
try:
2019-03-06 02:28:45 +03:00
offset += 4
# resource code
2020-01-19 14:48:01 +03:00
code = i16(s, offset)
2019-03-06 02:28:45 +03:00
offset += 2
# resource name (usually empty)
name_len = s[offset]
2020-01-19 14:48:01 +03:00
# name = s[offset+1:offset+1+name_len]
offset += 1 + name_len
offset += offset & 1 # align
2019-03-06 02:28:45 +03:00
# resource data block
2020-01-19 14:48:01 +03:00
size = i32(s, offset)
2019-03-06 02:28:45 +03:00
offset += 4
2020-01-19 14:48:01 +03:00
data = s[offset : offset + size]
2019-03-06 02:28:45 +03:00
if code == 0x03ED: # ResolutionInfo
data = {
"XResolution": i32(data, 0) / 65536,
"DisplayedUnitsX": i16(data, 4),
"YResolution": i32(data, 8) / 65536,
"DisplayedUnitsY": i16(data, 12),
2019-03-06 02:28:45 +03:00
}
photoshop[code] = data
2020-01-19 14:48:01 +03:00
offset += size
offset += offset & 1 # align
except struct.error:
break # insufficient data
2020-01-19 03:15:37 +03:00
elif marker == 0xFFEE and s[:5] == b"Adobe":
2010-07-31 06:52:47 +04:00
self.info["adobe"] = i16(s, 5)
# extract Adobe custom properties
try:
2021-04-15 14:49:51 +03:00
adobe_transform = s[11]
except IndexError:
2010-07-31 06:52:47 +04:00
pass
else:
self.info["adobe_transform"] = adobe_transform
elif marker == 0xFFE2 and s[:4] == b"MPF\0":
# extract MPO information
self.info["mp"] = s[4:]
2014-08-28 15:44:19 +04:00
# offset is current location minus buffer size
# plus constant header size
2014-07-23 02:23:45 +04:00
self.info["mpoffset"] = self.fp.tell() - n + 4
2010-07-31 06:52:47 +04:00
# If DPI isn't in JPEG header, fetch from EXIF
if "dpi" not in self.info and "exif" in self.info:
try:
exif = self.getexif()
resolution_unit = exif[0x0128]
x_resolution = exif[0x011A]
try:
2019-03-30 07:03:57 +03:00
dpi = float(x_resolution[0]) / x_resolution[1]
except TypeError:
dpi = x_resolution
2021-05-07 14:23:29 +03:00
if math.isnan(dpi):
raise ValueError
2017-05-27 23:55:14 +03:00
if resolution_unit == 3: # cm
# 1 dpcm = 2.54 dpi
dpi *= 2.54
2021-05-07 14:23:29 +03:00
self.info["dpi"] = dpi, dpi
except (KeyError, SyntaxError, ValueError, ZeroDivisionError):
2019-03-05 10:11:46 +03:00
# SyntaxError for invalid/unreadable EXIF
# KeyError for dpi not included
# ZeroDivisionError for invalid dpi rational value
2021-05-07 14:23:29 +03:00
# ValueError for dpi being an invalid float
self.info["dpi"] = 72, 72
2014-05-05 23:41:09 +04:00
2010-07-31 06:52:47 +04:00
def COM(self, marker):
#
# Comment marker. Store these in the APP dictionary.
2019-03-21 16:28:20 +03:00
n = i16(self.fp.read(2)) - 2
2010-07-31 06:52:47 +04:00
s = ImageFile._safe_read(self.fp, n)
2020-03-06 22:57:29 +03:00
self.info["comment"] = s
2014-05-05 23:41:09 +04:00
self.app["COM"] = s # compatibility
2010-07-31 06:52:47 +04:00
self.applist.append(("COM", s))
2014-05-05 23:41:09 +04:00
2010-07-31 06:52:47 +04:00
def SOF(self, marker):
#
# Start of frame marker. Defines the size and mode of the
# image. JPEG is colour blind, so we use some simple
# heuristics to map the number of layers to an appropriate
# mode. Note that this could be made a bit brighter, by
# looking for JFIF and Adobe APP markers.
2019-03-21 16:28:20 +03:00
n = i16(self.fp.read(2)) - 2
2010-07-31 06:52:47 +04:00
s = ImageFile._safe_read(self.fp, n)
self._size = i16(s, 3), i16(s, 1)
2010-07-31 06:52:47 +04:00
self.bits = s[0]
2010-07-31 06:52:47 +04:00
if self.bits != 8:
raise SyntaxError(f"cannot handle {self.bits}-bit layers")
2010-07-31 06:52:47 +04:00
self.layers = s[5]
2010-07-31 06:52:47 +04:00
if self.layers == 1:
self.mode = "L"
elif self.layers == 3:
self.mode = "RGB"
elif self.layers == 4:
self.mode = "CMYK"
else:
raise SyntaxError(f"cannot handle {self.layers}-layer images")
2010-07-31 06:52:47 +04:00
if marker in [0xFFC2, 0xFFC6, 0xFFCA, 0xFFCE]:
self.info["progressive"] = self.info["progression"] = 1
if self.icclist:
# fixup icc profile
2014-05-05 23:41:09 +04:00
self.icclist.sort() # sort by sequence number
if self.icclist[0][13] == len(self.icclist):
2010-07-31 06:52:47 +04:00
profile = []
for p in self.icclist:
profile.append(p[14:])
icc_profile = b"".join(profile)
2010-07-31 06:52:47 +04:00
else:
2014-05-05 23:41:09 +04:00
icc_profile = None # wrong number of fragments
2010-07-31 06:52:47 +04:00
self.info["icc_profile"] = icc_profile
self.icclist = []
2010-07-31 06:52:47 +04:00
for i in range(6, len(s), 3):
2019-03-21 16:28:20 +03:00
t = s[i : i + 3]
2010-07-31 06:52:47 +04:00
# 4-tuples: id, vsamp, hsamp, qtable
self.layer.append((t[0], t[1] // 16, t[1] & 15, t[2]))
2014-05-05 23:41:09 +04:00
2010-07-31 06:52:47 +04:00
def DQT(self, marker):
#
# Define quantization table. Note that there might be more
# than one table in each marker.
2010-07-31 06:52:47 +04:00
# FIXME: The quantization tables can be used to estimate the
# compression quality.
2019-03-21 16:28:20 +03:00
n = i16(self.fp.read(2)) - 2
2010-07-31 06:52:47 +04:00
s = ImageFile._safe_read(self.fp, n)
while len(s):
v = s[0]
precision = 1 if (v // 16 == 0) else 2 # in bytes
qt_length = 1 + precision * 64
if len(s) < qt_length:
raise SyntaxError("bad quantization table marker")
data = array.array("B" if precision == 1 else "H", s[1:qt_length])
if sys.byteorder == "little" and precision > 1:
data.byteswap() # the values are always big-endian
self.quantization[v & 15] = data
s = s[qt_length:]
2010-07-31 06:52:47 +04:00
#
# JPEG marker table
MARKER = {
0xFFC0: ("SOF0", "Baseline DCT", SOF),
0xFFC1: ("SOF1", "Extended Sequential DCT", SOF),
0xFFC2: ("SOF2", "Progressive DCT", SOF),
0xFFC3: ("SOF3", "Spatial lossless", SOF),
0xFFC4: ("DHT", "Define Huffman table", Skip),
0xFFC5: ("SOF5", "Differential sequential DCT", SOF),
0xFFC6: ("SOF6", "Differential progressive DCT", SOF),
0xFFC7: ("SOF7", "Differential spatial", SOF),
0xFFC8: ("JPG", "Extension", None),
0xFFC9: ("SOF9", "Extended sequential DCT (AC)", SOF),
0xFFCA: ("SOF10", "Progressive DCT (AC)", SOF),
0xFFCB: ("SOF11", "Spatial lossless DCT (AC)", SOF),
0xFFCC: ("DAC", "Define arithmetic coding conditioning", Skip),
0xFFCD: ("SOF13", "Differential sequential DCT (AC)", SOF),
0xFFCE: ("SOF14", "Differential progressive DCT (AC)", SOF),
0xFFCF: ("SOF15", "Differential spatial (AC)", SOF),
0xFFD0: ("RST0", "Restart 0", None),
0xFFD1: ("RST1", "Restart 1", None),
0xFFD2: ("RST2", "Restart 2", None),
0xFFD3: ("RST3", "Restart 3", None),
0xFFD4: ("RST4", "Restart 4", None),
0xFFD5: ("RST5", "Restart 5", None),
0xFFD6: ("RST6", "Restart 6", None),
0xFFD7: ("RST7", "Restart 7", None),
0xFFD8: ("SOI", "Start of image", None),
0xFFD9: ("EOI", "End of image", None),
0xFFDA: ("SOS", "Start of scan", Skip),
0xFFDB: ("DQT", "Define quantization table", DQT),
0xFFDC: ("DNL", "Define number of lines", Skip),
0xFFDD: ("DRI", "Define restart interval", Skip),
0xFFDE: ("DHP", "Define hierarchical progression", SOF),
0xFFDF: ("EXP", "Expand reference component", Skip),
0xFFE0: ("APP0", "Application segment 0", APP),
0xFFE1: ("APP1", "Application segment 1", APP),
0xFFE2: ("APP2", "Application segment 2", APP),
0xFFE3: ("APP3", "Application segment 3", APP),
0xFFE4: ("APP4", "Application segment 4", APP),
0xFFE5: ("APP5", "Application segment 5", APP),
0xFFE6: ("APP6", "Application segment 6", APP),
0xFFE7: ("APP7", "Application segment 7", APP),
0xFFE8: ("APP8", "Application segment 8", APP),
0xFFE9: ("APP9", "Application segment 9", APP),
0xFFEA: ("APP10", "Application segment 10", APP),
0xFFEB: ("APP11", "Application segment 11", APP),
0xFFEC: ("APP12", "Application segment 12", APP),
0xFFED: ("APP13", "Application segment 13", APP),
0xFFEE: ("APP14", "Application segment 14", APP),
0xFFEF: ("APP15", "Application segment 15", APP),
0xFFF0: ("JPG0", "Extension 0", None),
0xFFF1: ("JPG1", "Extension 1", None),
0xFFF2: ("JPG2", "Extension 2", None),
0xFFF3: ("JPG3", "Extension 3", None),
0xFFF4: ("JPG4", "Extension 4", None),
0xFFF5: ("JPG5", "Extension 5", None),
0xFFF6: ("JPG6", "Extension 6", None),
0xFFF7: ("JPG7", "Extension 7", None),
0xFFF8: ("JPG8", "Extension 8", None),
0xFFF9: ("JPG9", "Extension 9", None),
0xFFFA: ("JPG10", "Extension 10", None),
0xFFFB: ("JPG11", "Extension 11", None),
0xFFFC: ("JPG12", "Extension 12", None),
0xFFFD: ("JPG13", "Extension 13", None),
2019-03-21 16:28:20 +03:00
0xFFFE: ("COM", "Comment", COM),
2010-07-31 06:52:47 +04:00
}
def _accept(prefix):
# Magic number was taken from https://en.wikipedia.org/wiki/JPEG
return prefix[0:3] == b"\xFF\xD8\xFF"
2010-07-31 06:52:47 +04:00
2014-05-05 23:41:09 +04:00
2010-07-31 06:52:47 +04:00
##
# Image plugin for JPEG and JFIF images.
2019-03-21 16:28:20 +03:00
2010-07-31 06:52:47 +04:00
class JpegImageFile(ImageFile.ImageFile):
format = "JPEG"
format_description = "JPEG (ISO 10918)"
def _open(self):
2020-06-22 17:25:59 +03:00
s = self.fp.read(3)
2010-07-31 06:52:47 +04:00
if not _accept(s):
2010-07-31 06:52:47 +04:00
raise SyntaxError("not a JPEG file")
2020-06-22 17:25:59 +03:00
s = b"\xFF"
2010-07-31 06:52:47 +04:00
# Create attributes
self.bits = self.layers = 0
# JPEG specifics (internal)
self.layer = []
self.huffman_dc = {}
self.huffman_ac = {}
self.quantization = {}
2014-05-05 23:41:09 +04:00
self.app = {} # compatibility
2010-07-31 06:52:47 +04:00
self.applist = []
self.icclist = []
2021-04-01 14:28:37 +03:00
self._xmp = None
2010-07-31 06:52:47 +04:00
while True:
2010-07-31 06:52:47 +04:00
i = s[0]
if i == 0xFF:
s = s + self.fp.read(1)
i = i16(s)
else:
# Skip non-0xFF junk
s = self.fp.read(1)
continue
2010-07-31 06:52:47 +04:00
if i in MARKER:
2010-07-31 06:52:47 +04:00
name, description, handler = MARKER[i]
if handler is not None:
handler(self, i)
if i == 0xFFDA: # start of scan
2010-07-31 06:52:47 +04:00
rawmode = self.mode
if self.mode == "CMYK":
rawmode = "CMYK;I" # assume adobe conventions
2019-03-21 16:28:20 +03:00
self.tile = [("jpeg", (0, 0) + self.size, 0, (rawmode, ""))]
2010-07-31 06:52:47 +04:00
# self.__offset = self.fp.tell()
break
s = self.fp.read(1)
elif i == 0 or i == 0xFFFF:
2010-07-31 06:52:47 +04:00
# padded marker or junk; move on
s = b"\xff"
2016-06-22 23:36:23 +03:00
elif i == 0xFF00: # Skip extraneous data (escaped 0xFF)
s = self.fp.read(1)
2010-07-31 06:52:47 +04:00
else:
raise SyntaxError("no marker found")
def load_read(self, read_bytes):
"""
internal: read more image data
For premature EOF and LOAD_TRUNCATED_IMAGES adds EOI marker
so libjpeg can finish decoding
"""
s = self.fp.read(read_bytes)
if not s and ImageFile.LOAD_TRUNCATED_IMAGES:
# Premature EOF.
# Pretend file is finished adding EOI marker
return b"\xFF\xD9"
return s
2010-07-31 06:52:47 +04:00
def draft(self, mode, size):
if len(self.tile) != 1:
return
2016-11-22 04:28:04 +03:00
# Protect from second call
if self.decoderconfig:
return
2010-07-31 06:52:47 +04:00
d, e, o, a = self.tile[0]
2019-11-24 04:33:34 +03:00
scale = 1
original_size = self.size
2010-07-31 06:52:47 +04:00
if a[0] == "RGB" and mode in ["L", "YCbCr"]:
self.mode = mode
a = mode, ""
if size:
2016-11-22 04:00:49 +03:00
scale = min(self.size[0] // size[0], self.size[1] // size[1])
2010-07-31 06:52:47 +04:00
for s in [8, 4, 2, 1]:
if scale >= s:
break
2019-03-21 16:28:20 +03:00
e = (
e[0],
e[1],
(e[2] - e[0] + s - 1) // s + e[0],
(e[3] - e[1] + s - 1) // s + e[1],
)
self._size = ((self.size[0] + s - 1) // s, (self.size[1] + s - 1) // s)
2010-07-31 06:52:47 +04:00
scale = s
self.tile = [(d, e, o, a)]
self.decoderconfig = (scale, 0)
2010-07-31 06:52:47 +04:00
box = (0, 0, original_size[0] / scale, original_size[1] / scale)
return (self.mode, box)
2010-07-31 06:52:47 +04:00
def load_djpeg(self):
# ALTERNATIVE: handle JPEGs via the IJG command line utilities
f, path = tempfile.mkstemp()
os.close(f)
if os.path.exists(self.filename):
subprocess.check_call(["djpeg", "-outfile", path, self.filename])
else:
raise ValueError("Invalid Filename")
2010-07-31 06:52:47 +04:00
try:
2020-02-18 12:49:05 +03:00
with Image.open(path) as _im:
_im.load()
self.im = _im.im
2010-07-31 06:52:47 +04:00
finally:
2014-05-05 23:41:09 +04:00
try:
os.unlink(path)
2015-12-02 08:23:49 +03:00
except OSError:
2014-05-05 23:41:09 +04:00
pass
2010-07-31 06:52:47 +04:00
self.mode = self.im.mode
self._size = self.im.size
2010-07-31 06:52:47 +04:00
self.tile = []
def _getexif(self):
2013-09-29 20:17:37 +04:00
return _getexif(self)
def _getmp(self):
return _getmp(self)
2021-04-01 14:28:37 +03:00
def getxmp(self):
"""
2021-04-01 14:20:55 +03:00
Returns a dictionary containing the XMP tags.
2021-04-01 14:28:37 +03:00
:returns: XMP tags in a dictionary.
"""
if self._xmp is None:
self._xmp = {}
for segment, content in self.applist:
if segment == "APP1":
marker, xmp_tags = content.rsplit(b"\x00", 1)
if marker == b"http://ns.adobe.com/xap/1.0/":
2021-06-12 05:17:38 +03:00
self._getxmp(xmp_tags)
2021-04-01 14:28:37 +03:00
return self._xmp
2013-09-29 20:17:37 +04:00
def _getexif(self):
2019-03-12 02:27:43 +03:00
if "exif" not in self.info:
2013-09-29 20:17:37 +04:00
return None
return self.getexif()._get_merged_dict()
2010-07-31 06:52:47 +04:00
def _getmp(self):
# Extract MP information. This method was inspired by the "highly
# experimental" _getexif version that's been in use for years now,
2020-07-03 09:38:30 +03:00
# itself based on the ImageFileDirectory class in the TIFF plugin.
# The MP record essentially consists of a TIFF file embedded in a JPEG
# application marker.
try:
data = self.info["mp"]
except KeyError:
return None
2015-04-24 11:24:52 +03:00
file_contents = io.BytesIO(data)
head = file_contents.read(8)
2019-03-21 16:28:20 +03:00
endianness = ">" if head[:4] == b"\x4d\x4d\x00\x2a" else "<"
# process dictionary
2015-12-31 00:34:33 +03:00
try:
info = TiffImagePlugin.ImageFileDirectory_v2(head)
2019-01-12 03:40:32 +03:00
file_contents.seek(info.next)
2015-12-31 00:34:33 +03:00
info.load(file_contents)
mp = dict(info)
except Exception as e:
raise SyntaxError("malformed MP Index (unreadable directory)") from e
# it's an error not to have a number of images
try:
quant = mp[0xB001]
except KeyError as e:
raise SyntaxError("malformed MP Index (no number of images)") from e
# get MP entries
mpentries = []
try:
rawmpentries = mp[0xB002]
for entrynum in range(0, quant):
2017-01-06 07:19:13 +03:00
unpackedentry = struct.unpack_from(
f"{endianness}LLLHH", rawmpentries, entrynum * 16
2019-03-21 16:28:20 +03:00
)
labels = ("Attribute", "Size", "DataOffset", "EntryNo1", "EntryNo2")
mpentry = dict(zip(labels, unpackedentry))
mpentryattr = {
2019-03-21 16:28:20 +03:00
"DependentParentImageFlag": bool(mpentry["Attribute"] & (1 << 31)),
"DependentChildImageFlag": bool(mpentry["Attribute"] & (1 << 30)),
"RepresentativeImageFlag": bool(mpentry["Attribute"] & (1 << 29)),
"Reserved": (mpentry["Attribute"] & (3 << 27)) >> 27,
"ImageDataFormat": (mpentry["Attribute"] & (7 << 24)) >> 24,
"MPType": mpentry["Attribute"] & 0x00FFFFFF,
}
2019-03-21 16:28:20 +03:00
if mpentryattr["ImageDataFormat"] == 0:
mpentryattr["ImageDataFormat"] = "JPEG"
else:
raise SyntaxError("unsupported picture format in MPO")
mptypemap = {
2019-03-21 16:28:20 +03:00
0x000000: "Undefined",
0x010001: "Large Thumbnail (VGA Equivalent)",
0x010002: "Large Thumbnail (Full HD Equivalent)",
0x020001: "Multi-Frame Image (Panorama)",
0x020002: "Multi-Frame Image: (Disparity)",
0x020003: "Multi-Frame Image: (Multi-Angle)",
0x030000: "Baseline MP Primary Image",
}
2019-03-21 16:28:20 +03:00
mpentryattr["MPType"] = mptypemap.get(mpentryattr["MPType"], "Unknown")
mpentry["Attribute"] = mpentryattr
mpentries.append(mpentry)
mp[0xB002] = mpentries
except KeyError as e:
raise SyntaxError("malformed MP Index (bad MP Entry)") from e
# Next we should try and parse the individual image unique ID list;
# we don't because I've never seen this actually used in a real MPO
# file and so can't test it.
return mp
2010-07-31 06:52:47 +04:00
# --------------------------------------------------------------------
# stuff to save JPEG files
RAWMODE = {
"1": "L",
"L": "L",
"RGB": "RGB",
"RGBX": "RGB",
2014-05-05 23:41:09 +04:00
"CMYK": "CMYK;I", # assume adobe conventions
2010-07-31 06:52:47 +04:00
"YCbCr": "YCbCr",
}
2019-03-21 16:28:20 +03:00
# fmt: off
zigzag_index = (
0, 1, 5, 6, 14, 15, 27, 28,
2, 4, 7, 13, 16, 26, 29, 42,
3, 8, 12, 17, 25, 30, 41, 43,
9, 11, 18, 24, 31, 40, 44, 53,
10, 19, 23, 32, 39, 45, 52, 54,
20, 22, 33, 38, 46, 51, 55, 60,
21, 34, 37, 47, 50, 56, 59, 61,
35, 36, 48, 49, 57, 58, 62, 63,
)
samplings = {
(1, 1, 1, 1, 1, 1): 0,
(2, 1, 1, 1, 1, 1): 1,
(2, 2, 1, 1, 1, 1): 2,
}
# fmt: on
2014-05-05 23:41:09 +04:00
def convert_dict_qtables(qtables):
2014-03-28 03:40:44 +04:00
qtables = [qtables[key] for key in range(len(qtables)) if key in qtables]
for idx, table in enumerate(qtables):
qtables[idx] = [table[i] for i in zigzag_index]
return qtables
2014-05-05 23:41:09 +04:00
def get_sampling(im):
2020-05-16 14:37:33 +03:00
# There's no subsampling when images have only 1 layer
# (grayscale images) or when they are CMYK (4 layers),
2020-05-16 14:37:33 +03:00
# so set subsampling to the default value.
#
# NOTE: currently Pillow can't encode JPEG to YCCK format.
# If YCCK support is added in the future, subsampling code will have
# to be updated (here and in JpegEncode.c) to deal with 4 layers.
2019-03-21 16:28:20 +03:00
if not hasattr(im, "layers") or im.layers in (1, 4):
return -1
sampling = im.layer[0][1:3] + im.layer[1][1:3] + im.layer[2][1:3]
return samplings.get(sampling, -1)
2014-05-05 23:41:09 +04:00
2010-07-31 06:52:47 +04:00
def _save(im, fp, filename):
try:
rawmode = RAWMODE[im.mode]
except KeyError as e:
raise OSError(f"cannot write mode {im.mode} as JPEG") from e
2010-07-31 06:52:47 +04:00
info = im.encoderinfo
2020-02-08 00:34:53 +03:00
dpi = [round(x) for x in info.get("dpi", (0, 0))]
2010-07-31 06:52:47 +04:00
quality = info.get("quality", -1)
2010-07-31 06:52:47 +04:00
subsampling = info.get("subsampling", -1)
qtables = info.get("qtables")
if quality == "keep":
quality = -1
subsampling = "keep"
qtables = "keep"
elif quality in presets:
preset = presets[quality]
quality = -1
2019-03-21 16:28:20 +03:00
subsampling = preset.get("subsampling", -1)
qtables = preset.get("quantization")
elif not isinstance(quality, int):
raise ValueError("Invalid quality setting")
else:
if subsampling in presets:
2019-03-21 16:28:20 +03:00
subsampling = presets[subsampling].get("subsampling", -1)
if isinstance(qtables, str) and qtables in presets:
2019-03-21 16:28:20 +03:00
qtables = presets[qtables].get("quantization")
2010-07-31 06:52:47 +04:00
if subsampling == "4:4:4":
subsampling = 0
elif subsampling == "4:2:2":
subsampling = 1
elif subsampling == "4:2:0":
subsampling = 2
2010-07-31 06:52:47 +04:00
elif subsampling == "4:1:1":
# For compatibility. Before Pillow 4.3, 4:1:1 actually meant 4:2:0.
# Set 4:2:0 if someone is still using that value.
2010-07-31 06:52:47 +04:00
subsampling = 2
elif subsampling == "keep":
if im.format != "JPEG":
2019-03-21 16:28:20 +03:00
raise ValueError("Cannot use 'keep' when original image is not a JPEG")
subsampling = get_sampling(im)
def validate_qtables(qtables):
if qtables is None:
return qtables
if isinstance(qtables, str):
try:
2019-03-21 16:28:20 +03:00
lines = [
int(num)
for line in qtables.splitlines()
for num in line.split("#", 1)[0].split()
]
except ValueError as e:
raise ValueError("Invalid quantization table") from e
else:
2019-03-21 16:28:20 +03:00
qtables = [lines[s : s + 64] for s in range(0, len(lines), 64)]
if isinstance(qtables, (tuple, list, dict)):
if isinstance(qtables, dict):
qtables = convert_dict_qtables(qtables)
elif isinstance(qtables, tuple):
qtables = list(qtables)
if not (0 < len(qtables) < 5):
raise ValueError("None or too many quantization tables")
for idx, table in enumerate(qtables):
try:
if len(table) != 64:
2018-06-07 12:43:13 +03:00
raise TypeError
table = array.array("H", table)
except TypeError as e:
raise ValueError("Invalid quantization table") from e
else:
qtables[idx] = list(table)
return qtables
if qtables == "keep":
if im.format != "JPEG":
2019-03-21 16:28:20 +03:00
raise ValueError("Cannot use 'keep' when original image is not a JPEG")
qtables = getattr(im, "quantization", None)
qtables = validate_qtables(qtables)
2010-07-31 06:52:47 +04:00
extra = b""
2010-07-31 06:52:47 +04:00
icc_profile = info.get("icc_profile")
if icc_profile:
ICC_OVERHEAD_LEN = 14
MAX_BYTES_IN_MARKER = 65533
MAX_DATA_BYTES_IN_MARKER = MAX_BYTES_IN_MARKER - ICC_OVERHEAD_LEN
markers = []
while icc_profile:
markers.append(icc_profile[:MAX_DATA_BYTES_IN_MARKER])
icc_profile = icc_profile[MAX_DATA_BYTES_IN_MARKER:]
i = 1
for marker in markers:
size = struct.pack(">H", 2 + ICC_OVERHEAD_LEN + len(marker))
2019-03-21 16:28:20 +03:00
extra += (
b"\xFF\xE2"
+ size
+ b"ICC_PROFILE\0"
+ o8(i)
+ o8(len(markers))
+ marker
)
i += 1
2010-07-31 06:52:47 +04:00
# "progressive" is the official name, but older documentation
# says "progression"
# FIXME: issue a warning if the wrong form is used (post-1.1.7)
2019-03-21 16:28:20 +03:00
progressive = info.get("progressive", False) or info.get("progression", False)
optimize = info.get("optimize", False)
2019-03-12 02:27:43 +03:00
exif = info.get("exif", b"")
2019-04-01 12:03:02 +03:00
if isinstance(exif, Image.Exif):
2019-03-31 00:09:01 +03:00
exif = exif.tobytes()
2019-03-12 02:27:43 +03:00
2010-07-31 06:52:47 +04:00
# get keyword arguments
im.encoderconfig = (
quality,
progressive,
2010-07-31 06:52:47 +04:00
info.get("smooth", 0),
optimize,
2010-07-31 06:52:47 +04:00
info.get("streamtype", 0),
2019-03-21 16:28:20 +03:00
dpi[0],
dpi[1],
2010-07-31 06:52:47 +04:00
subsampling,
qtables,
2010-07-31 06:52:47 +04:00
extra,
2019-03-21 16:28:20 +03:00
exif,
)
2010-07-31 06:52:47 +04:00
2014-05-05 23:41:09 +04:00
# if we optimize, libjpeg needs a buffer big enough to hold the whole image
2018-01-14 04:09:05 +03:00
# in a shot. Guessing on the size, at im.size bytes. (raw pixel size is
2014-05-05 23:41:09 +04:00
# channels*size, this is a value that's been used in a django patch.
2015-09-28 14:53:25 +03:00
# https://github.com/matthewwithanm/django-imagekit/issues/50
2014-05-05 23:41:09 +04:00
bufsize = 0
if optimize or progressive:
# CMYK can be bigger
2019-03-21 16:28:20 +03:00
if im.mode == "CMYK":
bufsize = 4 * im.size[0] * im.size[1]
# keep sets quality to -1, but the actual value may be high.
elif quality >= 95 or quality == -1:
bufsize = 2 * im.size[0] * im.size[1]
else:
bufsize = im.size[0] * im.size[1]
2019-03-05 10:11:46 +03:00
# The EXIF info needs to be written as one block, + APP1, + one spare byte.
# Ensure that our buffer is big enough. Same with the icc_profile block.
2019-03-21 16:28:20 +03:00
bufsize = max(ImageFile.MAXBLOCK, bufsize, len(exif) + 5, len(extra) + 1)
2014-05-05 23:41:09 +04:00
2019-03-21 16:28:20 +03:00
ImageFile._save(im, fp, [("jpeg", (0, 0) + im.size, 0, rawmode)], bufsize)
2010-07-31 06:52:47 +04:00
def _save_cjpeg(im, fp, filename):
# ALTERNATIVE: handle JPEGs via the IJG command line utilities.
tempfile = im._dump()
subprocess.check_call(["cjpeg", "-outfile", filename, tempfile])
2014-05-05 23:41:09 +04:00
try:
2015-05-08 07:28:49 +03:00
os.unlink(tempfile)
2015-12-02 08:23:49 +03:00
except OSError:
2014-05-05 23:41:09 +04:00
pass
2010-07-31 06:52:47 +04:00
##
# Factory for making JPEG and MPO instances
def jpeg_factory(fp=None, filename=None):
im = JpegImageFile(fp, filename)
try:
mpheader = im._getmp()
if mpheader[45057] > 1:
# It's actually an MPO
from .MpoImagePlugin import MpoImageFile
2019-03-21 16:28:20 +03:00
# Don't reload everything, just convert it.
im = MpoImageFile.adopt(im, mpheader)
except (TypeError, IndexError):
# It is really a JPEG
pass
except SyntaxError:
2019-03-21 16:28:20 +03:00
warnings.warn(
"Image appears to be a malformed MPO file, it will be "
"interpreted as a base JPEG file"
)
return im
2018-08-09 13:54:16 +03:00
# ---------------------------------------------------------------------
2010-07-31 06:52:47 +04:00
# Registry stuff
Image.register_open(JpegImageFile.format, jpeg_factory, _accept)
Image.register_save(JpegImageFile.format, _save)
2010-07-31 06:52:47 +04:00
2019-03-21 16:28:20 +03:00
Image.register_extensions(JpegImageFile.format, [".jfif", ".jpe", ".jpg", ".jpeg"])
2010-07-31 06:52:47 +04:00
Image.register_mime(JpegImageFile.format, "image/jpeg")