mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-02-04 21:50:54 +03:00
commit
7d378a8e4c
BIN
Tests/images/fujifilm.mpo
Normal file
BIN
Tests/images/fujifilm.mpo
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.5 MiB |
|
@ -64,6 +64,18 @@ class TestFileMpo(PillowTestCase):
|
||||||
im.seek(1)
|
im.seek(1)
|
||||||
self.assertEqual(im.size, (680, 480))
|
self.assertEqual(im.size, (680, 480))
|
||||||
|
|
||||||
|
def test_parallax(self):
|
||||||
|
# Nintendo
|
||||||
|
im = Image.open("Tests/images/sugarshack.mpo")
|
||||||
|
exif = im.getexif()
|
||||||
|
self.assertEqual(exif.get_ifd(0x927c)[0x1101]["Parallax"], -44.798187255859375)
|
||||||
|
|
||||||
|
# Fujifilm
|
||||||
|
im = Image.open("Tests/images/fujifilm.mpo")
|
||||||
|
im.seek(1)
|
||||||
|
exif = im.getexif()
|
||||||
|
self.assertEqual(exif.get_ifd(0x927c)[0xb211], -3.125)
|
||||||
|
|
||||||
def test_mp(self):
|
def test_mp(self):
|
||||||
for test_file in test_files:
|
for test_file in test_files:
|
||||||
im = Image.open(test_file)
|
im = Image.open(test_file)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from .helper import PillowTestCase, hopper, fromstring, tostring
|
from .helper import unittest, PillowTestCase, hopper, fromstring, tostring
|
||||||
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
|
@ -6,6 +6,12 @@ from PIL import Image
|
||||||
from PIL import ImageFile
|
from PIL import ImageFile
|
||||||
from PIL import EpsImagePlugin
|
from PIL import EpsImagePlugin
|
||||||
|
|
||||||
|
try:
|
||||||
|
from PIL import _webp
|
||||||
|
HAVE_WEBP = True
|
||||||
|
except ImportError:
|
||||||
|
HAVE_WEBP = False
|
||||||
|
|
||||||
|
|
||||||
codecs = dir(Image.core)
|
codecs = dir(Image.core)
|
||||||
|
|
||||||
|
@ -233,3 +239,97 @@ class TestPyDecoder(PillowTestCase):
|
||||||
im = MockImageFile(buf)
|
im = MockImageFile(buf)
|
||||||
self.assertIsNone(im.format)
|
self.assertIsNone(im.format)
|
||||||
self.assertIsNone(im.get_format_mimetype())
|
self.assertIsNone(im.get_format_mimetype())
|
||||||
|
|
||||||
|
def test_exif_jpeg(self):
|
||||||
|
im = Image.open("Tests/images/exif-72dpi-int.jpg") # Little endian
|
||||||
|
exif = im.getexif()
|
||||||
|
self.assertNotIn(258, exif)
|
||||||
|
self.assertIn(40960, exif)
|
||||||
|
self.assertEqual(exif[40963], 450)
|
||||||
|
self.assertEqual(exif[11], "gThumb 3.0.1")
|
||||||
|
|
||||||
|
out = self.tempfile('temp.jpg')
|
||||||
|
exif[258] = 8
|
||||||
|
del exif[40960]
|
||||||
|
exif[40963] = 455
|
||||||
|
exif[11] = "Pillow test"
|
||||||
|
im.save(out, exif=exif)
|
||||||
|
reloaded = Image.open(out)
|
||||||
|
reloaded_exif = reloaded.getexif()
|
||||||
|
self.assertEqual(reloaded_exif[258], 8)
|
||||||
|
self.assertNotIn(40960, exif)
|
||||||
|
self.assertEqual(reloaded_exif[40963], 455)
|
||||||
|
self.assertEqual(exif[11], "Pillow test")
|
||||||
|
|
||||||
|
im = Image.open("Tests/images/no-dpi-in-exif.jpg") # Big endian
|
||||||
|
exif = im.getexif()
|
||||||
|
self.assertNotIn(258, exif)
|
||||||
|
self.assertIn(40962, exif)
|
||||||
|
self.assertEqual(exif[40963], 200)
|
||||||
|
self.assertEqual(exif[305], "Adobe Photoshop CC 2017 (Macintosh)")
|
||||||
|
|
||||||
|
out = self.tempfile('temp.jpg')
|
||||||
|
exif[258] = 8
|
||||||
|
del exif[34665]
|
||||||
|
exif[40963] = 455
|
||||||
|
exif[305] = "Pillow test"
|
||||||
|
im.save(out, exif=exif)
|
||||||
|
reloaded = Image.open(out)
|
||||||
|
reloaded_exif = reloaded.getexif()
|
||||||
|
self.assertEqual(reloaded_exif[258], 8)
|
||||||
|
self.assertNotIn(40960, exif)
|
||||||
|
self.assertEqual(reloaded_exif[40963], 455)
|
||||||
|
self.assertEqual(exif[305], "Pillow test")
|
||||||
|
|
||||||
|
@unittest.skipIf(not HAVE_WEBP or not _webp.HAVE_WEBPANIM,
|
||||||
|
"WebP support not installed with animation")
|
||||||
|
def test_exif_webp(self):
|
||||||
|
im = Image.open("Tests/images/hopper.webp")
|
||||||
|
exif = im.getexif()
|
||||||
|
self.assertEqual(exif, {})
|
||||||
|
|
||||||
|
out = self.tempfile('temp.webp')
|
||||||
|
exif[258] = 8
|
||||||
|
exif[40963] = 455
|
||||||
|
exif[305] = "Pillow test"
|
||||||
|
|
||||||
|
def check_exif():
|
||||||
|
reloaded = Image.open(out)
|
||||||
|
reloaded_exif = reloaded.getexif()
|
||||||
|
self.assertEqual(reloaded_exif[258], 8)
|
||||||
|
self.assertEqual(reloaded_exif[40963], 455)
|
||||||
|
self.assertEqual(exif[305], "Pillow test")
|
||||||
|
im.save(out, exif=exif)
|
||||||
|
check_exif()
|
||||||
|
im.save(out, exif=exif, save_all=True)
|
||||||
|
check_exif()
|
||||||
|
|
||||||
|
def test_exif_png(self):
|
||||||
|
im = Image.open("Tests/images/exif.png")
|
||||||
|
exif = im.getexif()
|
||||||
|
self.assertEqual(exif, {274: 1})
|
||||||
|
|
||||||
|
out = self.tempfile('temp.png')
|
||||||
|
exif[258] = 8
|
||||||
|
del exif[274]
|
||||||
|
exif[40963] = 455
|
||||||
|
exif[305] = "Pillow test"
|
||||||
|
im.save(out, exif=exif)
|
||||||
|
|
||||||
|
reloaded = Image.open(out)
|
||||||
|
reloaded_exif = reloaded.getexif()
|
||||||
|
self.assertEqual(reloaded_exif, {
|
||||||
|
258: 8,
|
||||||
|
40963: 455,
|
||||||
|
305: 'Pillow test',
|
||||||
|
})
|
||||||
|
|
||||||
|
def test_exif_interop(self):
|
||||||
|
im = Image.open("Tests/images/flower.jpg")
|
||||||
|
exif = im.getexif()
|
||||||
|
self.assertEqual(exif.get_ifd(0xa005), {
|
||||||
|
1: 'R98',
|
||||||
|
2: b'0100',
|
||||||
|
4097: 2272,
|
||||||
|
4098: 1704,
|
||||||
|
})
|
||||||
|
|
|
@ -27,11 +27,20 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
from . import Image
|
from . import Image, TiffTags
|
||||||
from ._util import isPath
|
from ._binary import i32le
|
||||||
|
from ._util import isPath, py3
|
||||||
import io
|
import io
|
||||||
import sys
|
import sys
|
||||||
import struct
|
import struct
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Python 3
|
||||||
|
from collections.abc import MutableMapping
|
||||||
|
except ImportError:
|
||||||
|
# Python 2.7
|
||||||
|
from collections import MutableMapping
|
||||||
|
|
||||||
MAXBLOCK = 65536
|
MAXBLOCK = 65536
|
||||||
|
|
||||||
|
@ -293,6 +302,12 @@ class ImageFile(Image.Image):
|
||||||
|
|
||||||
return self.tell() != frame
|
return self.tell() != frame
|
||||||
|
|
||||||
|
def getexif(self):
|
||||||
|
exif = Exif()
|
||||||
|
if "exif" in self.info:
|
||||||
|
exif.load(self.info["exif"])
|
||||||
|
return exif
|
||||||
|
|
||||||
|
|
||||||
class StubImageFile(ImageFile):
|
class StubImageFile(ImageFile):
|
||||||
"""
|
"""
|
||||||
|
@ -672,3 +687,182 @@ class PyDecoder(object):
|
||||||
raise ValueError("not enough image data")
|
raise ValueError("not enough image data")
|
||||||
if s[1] != 0:
|
if s[1] != 0:
|
||||||
raise ValueError("cannot decode image data")
|
raise ValueError("cannot decode image data")
|
||||||
|
|
||||||
|
|
||||||
|
class Exif(MutableMapping):
|
||||||
|
endian = "<"
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._data = {}
|
||||||
|
self._ifds = {}
|
||||||
|
|
||||||
|
def _fixup_dict(self, src_dict):
|
||||||
|
# Helper function for _getexif()
|
||||||
|
# returns a dict with any single item tuples/lists as individual values
|
||||||
|
def _fixup(value):
|
||||||
|
try:
|
||||||
|
if len(value) == 1 and not isinstance(value, dict):
|
||||||
|
return value[0]
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return value
|
||||||
|
|
||||||
|
return {k: _fixup(v) for k, v in src_dict.items()}
|
||||||
|
|
||||||
|
def _get_ifd_dict(self, tag):
|
||||||
|
try:
|
||||||
|
# an offset pointer to the location of the nested embedded IFD.
|
||||||
|
# It should be a long, but may be corrupted.
|
||||||
|
self.fp.seek(self._data[tag])
|
||||||
|
except (KeyError, TypeError):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
from . import TiffImagePlugin
|
||||||
|
info = TiffImagePlugin.ImageFileDirectory_v1(self.head)
|
||||||
|
info.load(self.fp)
|
||||||
|
return self._fixup_dict(info)
|
||||||
|
|
||||||
|
def load(self, data):
|
||||||
|
# Extract EXIF information. This is highly experimental,
|
||||||
|
# and is likely to be replaced with something better in a future
|
||||||
|
# version.
|
||||||
|
|
||||||
|
# The EXIF record consists of a TIFF file embedded in a JPEG
|
||||||
|
# application marker (!).
|
||||||
|
self.fp = io.BytesIO(data[6:])
|
||||||
|
self.head = self.fp.read(8)
|
||||||
|
# process dictionary
|
||||||
|
from . import TiffImagePlugin
|
||||||
|
info = TiffImagePlugin.ImageFileDirectory_v1(self.head)
|
||||||
|
self.endian = info._endian
|
||||||
|
self.fp.seek(info.next)
|
||||||
|
info.load(self.fp)
|
||||||
|
self._data = dict(self._fixup_dict(info))
|
||||||
|
|
||||||
|
# get EXIF extension
|
||||||
|
ifd = self._get_ifd_dict(0x8769)
|
||||||
|
if ifd:
|
||||||
|
self._data.update(ifd)
|
||||||
|
self._ifds[0x8769] = ifd
|
||||||
|
|
||||||
|
# get gpsinfo extension
|
||||||
|
ifd = self._get_ifd_dict(0x8825)
|
||||||
|
if ifd:
|
||||||
|
self._data[0x8825] = ifd
|
||||||
|
self._ifds[0x8825] = ifd
|
||||||
|
|
||||||
|
def tobytes(self, offset=0):
|
||||||
|
from . import TiffImagePlugin
|
||||||
|
if self.endian == "<":
|
||||||
|
head = b"II\x2A\x00\x08\x00\x00\x00"
|
||||||
|
else:
|
||||||
|
head = b"MM\x00\x2A\x00\x00\x00\x08"
|
||||||
|
ifd = TiffImagePlugin.ImageFileDirectory_v2(ifh=head)
|
||||||
|
for tag, value in self._data.items():
|
||||||
|
ifd[tag] = value
|
||||||
|
return b"Exif\x00\x00"+head+ifd.tobytes(offset)
|
||||||
|
|
||||||
|
def get_ifd(self, tag):
|
||||||
|
if tag not in self._ifds and tag in self._data:
|
||||||
|
if tag == 0xa005: # interop
|
||||||
|
self._ifds[tag] = self._get_ifd_dict(tag)
|
||||||
|
elif tag == 0x927c: # makernote
|
||||||
|
from . import TiffImagePlugin
|
||||||
|
if self._data[0x927c][:8] == b"FUJIFILM":
|
||||||
|
exif_data = self._data[0x927c]
|
||||||
|
ifd_offset = i32le(exif_data[8:12])
|
||||||
|
ifd_data = exif_data[ifd_offset:]
|
||||||
|
|
||||||
|
makernote = {}
|
||||||
|
for i in range(0, struct.unpack("<H", ifd_data[:2])[0]):
|
||||||
|
ifd_tag, typ, count, data = struct.unpack(
|
||||||
|
"<HHL4s", ifd_data[i*12 + 2:(i+1)*12 + 2])
|
||||||
|
try:
|
||||||
|
unit_size, handler =\
|
||||||
|
TiffImagePlugin.ImageFileDirectory_v2._load_dispatch[
|
||||||
|
typ
|
||||||
|
]
|
||||||
|
except KeyError:
|
||||||
|
continue
|
||||||
|
size = count * unit_size
|
||||||
|
if size > 4:
|
||||||
|
offset, = struct.unpack("<L", data)
|
||||||
|
data = ifd_data[offset-12:offset+size-12]
|
||||||
|
else:
|
||||||
|
data = data[:size]
|
||||||
|
|
||||||
|
if len(data) != size:
|
||||||
|
warnings.warn("Possibly corrupt EXIF MakerNote data. "
|
||||||
|
"Expecting to read %d bytes but only got %d."
|
||||||
|
" Skipping tag %s"
|
||||||
|
% (size, len(data), ifd_tag))
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not data:
|
||||||
|
continue
|
||||||
|
|
||||||
|
makernote[ifd_tag] = handler(
|
||||||
|
TiffImagePlugin.ImageFileDirectory_v2(), data, False)
|
||||||
|
self._ifds[0x927c] = dict(self._fixup_dict(makernote))
|
||||||
|
elif self._data.get(0x010f) == "Nintendo":
|
||||||
|
ifd_data = self._data[0x927c]
|
||||||
|
|
||||||
|
makernote = {}
|
||||||
|
for i in range(0, struct.unpack(">H", ifd_data[:2])[0]):
|
||||||
|
ifd_tag, typ, count, data = struct.unpack(
|
||||||
|
">HHL4s", ifd_data[i*12 + 2:(i+1)*12 + 2])
|
||||||
|
if ifd_tag == 0x1101:
|
||||||
|
# CameraInfo
|
||||||
|
offset, = struct.unpack(">L", data)
|
||||||
|
self.fp.seek(offset)
|
||||||
|
|
||||||
|
camerainfo = {'ModelID': self.fp.read(4)}
|
||||||
|
|
||||||
|
self.fp.read(4)
|
||||||
|
# Seconds since 2000
|
||||||
|
camerainfo['TimeStamp'] = i32le(self.fp.read(12))
|
||||||
|
|
||||||
|
self.fp.read(4)
|
||||||
|
camerainfo['InternalSerialNumber'] = self.fp.read(4)
|
||||||
|
|
||||||
|
self.fp.read(12)
|
||||||
|
parallax = self.fp.read(4)
|
||||||
|
handler =\
|
||||||
|
TiffImagePlugin.ImageFileDirectory_v2._load_dispatch[
|
||||||
|
TiffTags.FLOAT
|
||||||
|
][1]
|
||||||
|
camerainfo['Parallax'] = handler(
|
||||||
|
TiffImagePlugin.ImageFileDirectory_v2(),
|
||||||
|
parallax, False)
|
||||||
|
|
||||||
|
self.fp.read(4)
|
||||||
|
camerainfo['Category'] = self.fp.read(2)
|
||||||
|
|
||||||
|
makernote = {0x1101: dict(self._fixup_dict(camerainfo))}
|
||||||
|
self._ifds[0x927c] = makernote
|
||||||
|
return self._ifds.get(tag, {})
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str(self._data)
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self._data)
|
||||||
|
|
||||||
|
def __getitem__(self, tag):
|
||||||
|
return self._data[tag]
|
||||||
|
|
||||||
|
def __contains__(self, tag):
|
||||||
|
return tag in self._data
|
||||||
|
|
||||||
|
if not py3:
|
||||||
|
def has_key(self, tag):
|
||||||
|
return tag in self
|
||||||
|
|
||||||
|
def __setitem__(self, tag, value):
|
||||||
|
self._data[tag] = value
|
||||||
|
|
||||||
|
def __delitem__(self, tag):
|
||||||
|
del self._data[tag]
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return iter(set(self._data))
|
||||||
|
|
|
@ -86,7 +86,7 @@ def APP(self, marker):
|
||||||
self.info["jfif_density"] = jfif_density
|
self.info["jfif_density"] = jfif_density
|
||||||
elif marker == 0xFFE1 and s[:5] == b"Exif\0":
|
elif marker == 0xFFE1 and s[:5] == b"Exif\0":
|
||||||
if "exif" not in self.info:
|
if "exif" not in self.info:
|
||||||
# extract Exif information (incomplete)
|
# extract EXIF information (incomplete)
|
||||||
self.info["exif"] = s # FIXME: value will change
|
self.info["exif"] = s # FIXME: value will change
|
||||||
elif marker == 0xFFE2 and s[:5] == b"FPXR\0":
|
elif marker == 0xFFE2 and s[:5] == b"FPXR\0":
|
||||||
# extract FlashPix information (incomplete)
|
# extract FlashPix information (incomplete)
|
||||||
|
@ -168,7 +168,7 @@ def APP(self, marker):
|
||||||
dpi *= 2.54
|
dpi *= 2.54
|
||||||
self.info["dpi"] = int(dpi + 0.5), int(dpi + 0.5)
|
self.info["dpi"] = int(dpi + 0.5), int(dpi + 0.5)
|
||||||
except (KeyError, SyntaxError, ZeroDivisionError):
|
except (KeyError, SyntaxError, ZeroDivisionError):
|
||||||
# SyntaxError for invalid/unreadable exif
|
# SyntaxError for invalid/unreadable EXIF
|
||||||
# KeyError for dpi not included
|
# KeyError for dpi not included
|
||||||
# ZeroDivisionError for invalid dpi rational value
|
# ZeroDivisionError for invalid dpi rational value
|
||||||
self.info["dpi"] = 72, 72
|
self.info["dpi"] = 72, 72
|
||||||
|
@ -472,65 +472,20 @@ class JpegImageFile(ImageFile.ImageFile):
|
||||||
def _fixup_dict(src_dict):
|
def _fixup_dict(src_dict):
|
||||||
# Helper function for _getexif()
|
# Helper function for _getexif()
|
||||||
# returns a dict with any single item tuples/lists as individual values
|
# returns a dict with any single item tuples/lists as individual values
|
||||||
def _fixup(value):
|
exif = ImageFile.Exif()
|
||||||
try:
|
return exif._fixup_dict(src_dict)
|
||||||
if len(value) == 1 and not isinstance(value, dict):
|
|
||||||
return value[0]
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
return value
|
|
||||||
|
|
||||||
return {k: _fixup(v) for k, v in src_dict.items()}
|
|
||||||
|
|
||||||
|
|
||||||
def _getexif(self):
|
def _getexif(self):
|
||||||
# Extract EXIF information. This method is highly experimental,
|
|
||||||
# and is likely to be replaced with something better in a future
|
|
||||||
# version.
|
|
||||||
|
|
||||||
# Use the cached version if possible
|
# Use the cached version if possible
|
||||||
try:
|
try:
|
||||||
return self.info["parsed_exif"]
|
return self.info["parsed_exif"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# The EXIF record consists of a TIFF file embedded in a JPEG
|
if "exif" not in self.info:
|
||||||
# application marker (!).
|
|
||||||
try:
|
|
||||||
data = self.info["exif"]
|
|
||||||
except KeyError:
|
|
||||||
return None
|
return None
|
||||||
fp = io.BytesIO(data[6:])
|
exif = dict(self.getexif())
|
||||||
head = fp.read(8)
|
|
||||||
# process dictionary
|
|
||||||
info = TiffImagePlugin.ImageFileDirectory_v1(head)
|
|
||||||
fp.seek(info.next)
|
|
||||||
info.load(fp)
|
|
||||||
exif = dict(_fixup_dict(info))
|
|
||||||
# get exif extension
|
|
||||||
try:
|
|
||||||
# exif field 0x8769 is an offset pointer to the location
|
|
||||||
# of the nested embedded exif ifd.
|
|
||||||
# It should be a long, but may be corrupted.
|
|
||||||
fp.seek(exif[0x8769])
|
|
||||||
except (KeyError, TypeError):
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
info = TiffImagePlugin.ImageFileDirectory_v1(head)
|
|
||||||
info.load(fp)
|
|
||||||
exif.update(_fixup_dict(info))
|
|
||||||
# get gpsinfo extension
|
|
||||||
try:
|
|
||||||
# exif field 0x8825 is an offset pointer to the location
|
|
||||||
# of the nested embedded gps exif ifd.
|
|
||||||
# It should be a long, but may be corrupted.
|
|
||||||
fp.seek(exif[0x8825])
|
|
||||||
except (KeyError, TypeError):
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
info = TiffImagePlugin.ImageFileDirectory_v1(head)
|
|
||||||
info.load(fp)
|
|
||||||
exif[0x8825] = _fixup_dict(info)
|
|
||||||
|
|
||||||
# Cache the result for future use
|
# Cache the result for future use
|
||||||
self.info["parsed_exif"] = exif
|
self.info["parsed_exif"] = exif
|
||||||
|
@ -769,6 +724,10 @@ def _save(im, fp, filename):
|
||||||
|
|
||||||
optimize = info.get("optimize", False)
|
optimize = info.get("optimize", False)
|
||||||
|
|
||||||
|
exif = info.get("exif", b"")
|
||||||
|
if isinstance(exif, ImageFile.Exif):
|
||||||
|
exif = exif.tobytes()
|
||||||
|
|
||||||
# get keyword arguments
|
# get keyword arguments
|
||||||
im.encoderconfig = (
|
im.encoderconfig = (
|
||||||
quality,
|
quality,
|
||||||
|
@ -780,7 +739,7 @@ def _save(im, fp, filename):
|
||||||
subsampling,
|
subsampling,
|
||||||
qtables,
|
qtables,
|
||||||
extra,
|
extra,
|
||||||
info.get("exif", b"")
|
exif
|
||||||
)
|
)
|
||||||
|
|
||||||
# if we optimize, libjpeg needs a buffer big enough to hold the whole image
|
# if we optimize, libjpeg needs a buffer big enough to hold the whole image
|
||||||
|
@ -798,9 +757,9 @@ def _save(im, fp, filename):
|
||||||
else:
|
else:
|
||||||
bufsize = im.size[0] * im.size[1]
|
bufsize = im.size[0] * im.size[1]
|
||||||
|
|
||||||
# The exif info needs to be written as one block, + APP1, + one spare byte.
|
# 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.
|
# Ensure that our buffer is big enough. Same with the icc_profile block.
|
||||||
bufsize = max(ImageFile.MAXBLOCK, bufsize, len(info.get("exif", b"")) + 5,
|
bufsize = max(ImageFile.MAXBLOCK, bufsize, len(exif) + 5,
|
||||||
len(extra) + 1)
|
len(extra) + 1)
|
||||||
|
|
||||||
ImageFile._save(im, fp, [("jpeg", (0, 0)+im.size, 0, rawmode)], bufsize)
|
ImageFile._save(im, fp, [("jpeg", (0, 0)+im.size, 0, rawmode)], bufsize)
|
||||||
|
|
|
@ -696,8 +696,14 @@ class PngImageFile(ImageFile.ImageFile):
|
||||||
def _getexif(self):
|
def _getexif(self):
|
||||||
if "exif" not in self.info:
|
if "exif" not in self.info:
|
||||||
self.load()
|
self.load()
|
||||||
from .JpegImagePlugin import _getexif
|
if "exif" not in self.info:
|
||||||
return _getexif(self)
|
return None
|
||||||
|
return dict(self.getexif())
|
||||||
|
|
||||||
|
def getexif(self):
|
||||||
|
if "exif" not in self.info:
|
||||||
|
self.load()
|
||||||
|
return ImageFile.ImageFile.getexif(self)
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
|
@ -880,6 +886,8 @@ def _save(im, fp, filename, chunk=putchunk):
|
||||||
|
|
||||||
exif = im.encoderinfo.get("exif", im.info.get("exif"))
|
exif = im.encoderinfo.get("exif", im.info.get("exif"))
|
||||||
if exif:
|
if exif:
|
||||||
|
if isinstance(exif, ImageFile.Exif):
|
||||||
|
exif = exif.tobytes(8)
|
||||||
if exif.startswith(b"Exif\x00\x00"):
|
if exif.startswith(b"Exif\x00\x00"):
|
||||||
exif = exif[6:]
|
exif = exif[6:]
|
||||||
chunk(fp, b"eXIf", exif)
|
chunk(fp, b"eXIf", exif)
|
||||||
|
|
|
@ -785,17 +785,12 @@ class ImageFileDirectory_v2(MutableMapping):
|
||||||
warnings.warn(str(msg))
|
warnings.warn(str(msg))
|
||||||
return
|
return
|
||||||
|
|
||||||
def save(self, fp):
|
def tobytes(self, offset=0):
|
||||||
|
|
||||||
if fp.tell() == 0: # skip TIFF header on subsequent pages
|
|
||||||
# tiff header -- PIL always starts the first IFD at offset 8
|
|
||||||
fp.write(self._prefix + self._pack("HL", 42, 8))
|
|
||||||
|
|
||||||
# FIXME What about tagdata?
|
# FIXME What about tagdata?
|
||||||
fp.write(self._pack("H", len(self._tags_v2)))
|
result = self._pack("H", len(self._tags_v2))
|
||||||
|
|
||||||
entries = []
|
entries = []
|
||||||
offset = fp.tell() + len(self._tags_v2) * 12 + 4
|
offset = offset + len(result) + len(self._tags_v2) * 12 + 4
|
||||||
stripoffsets = None
|
stripoffsets = None
|
||||||
|
|
||||||
# pass 1: convert tags to binary format
|
# pass 1: convert tags to binary format
|
||||||
|
@ -844,18 +839,29 @@ class ImageFileDirectory_v2(MutableMapping):
|
||||||
for tag, typ, count, value, data in entries:
|
for tag, typ, count, value, data in entries:
|
||||||
if DEBUG > 1:
|
if DEBUG > 1:
|
||||||
print(tag, typ, count, repr(value), repr(data))
|
print(tag, typ, count, repr(value), repr(data))
|
||||||
fp.write(self._pack("HHL4s", tag, typ, count, value))
|
result += self._pack("HHL4s", tag, typ, count, value)
|
||||||
|
|
||||||
# -- overwrite here for multi-page --
|
# -- overwrite here for multi-page --
|
||||||
fp.write(b"\0\0\0\0") # end of entries
|
result += b"\0\0\0\0" # end of entries
|
||||||
|
|
||||||
# pass 3: write auxiliary data to file
|
# pass 3: write auxiliary data to file
|
||||||
for tag, typ, count, value, data in entries:
|
for tag, typ, count, value, data in entries:
|
||||||
fp.write(data)
|
result += data
|
||||||
if len(data) & 1:
|
if len(data) & 1:
|
||||||
fp.write(b"\0")
|
result += b"\0"
|
||||||
|
|
||||||
return offset
|
return result
|
||||||
|
|
||||||
|
def save(self, fp):
|
||||||
|
|
||||||
|
if fp.tell() == 0: # skip TIFF header on subsequent pages
|
||||||
|
# tiff header -- PIL always starts the first IFD at offset 8
|
||||||
|
fp.write(self._prefix + self._pack("HL", 42, 8))
|
||||||
|
|
||||||
|
offset = fp.tell()
|
||||||
|
result = self.tobytes(offset)
|
||||||
|
fp.write(result)
|
||||||
|
return offset + len(result)
|
||||||
|
|
||||||
|
|
||||||
ImageFileDirectory_v2._load_dispatch = _load_dispatch
|
ImageFileDirectory_v2._load_dispatch = _load_dispatch
|
||||||
|
|
|
@ -93,8 +93,9 @@ class WebPImageFile(ImageFile.ImageFile):
|
||||||
self.seek(0)
|
self.seek(0)
|
||||||
|
|
||||||
def _getexif(self):
|
def _getexif(self):
|
||||||
from .JpegImagePlugin import _getexif
|
if "exif" not in self.info:
|
||||||
return _getexif(self)
|
return None
|
||||||
|
return dict(self.getexif())
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def n_frames(self):
|
def n_frames(self):
|
||||||
|
@ -216,6 +217,8 @@ def _save_all(im, fp, filename):
|
||||||
method = im.encoderinfo.get("method", 0)
|
method = im.encoderinfo.get("method", 0)
|
||||||
icc_profile = im.encoderinfo.get("icc_profile", "")
|
icc_profile = im.encoderinfo.get("icc_profile", "")
|
||||||
exif = im.encoderinfo.get("exif", "")
|
exif = im.encoderinfo.get("exif", "")
|
||||||
|
if isinstance(exif, ImageFile.Exif):
|
||||||
|
exif = exif.tobytes()
|
||||||
xmp = im.encoderinfo.get("xmp", "")
|
xmp = im.encoderinfo.get("xmp", "")
|
||||||
if allow_mixed:
|
if allow_mixed:
|
||||||
lossless = False
|
lossless = False
|
||||||
|
@ -315,6 +318,8 @@ def _save(im, fp, filename):
|
||||||
quality = im.encoderinfo.get("quality", 80)
|
quality = im.encoderinfo.get("quality", 80)
|
||||||
icc_profile = im.encoderinfo.get("icc_profile", "")
|
icc_profile = im.encoderinfo.get("icc_profile", "")
|
||||||
exif = im.encoderinfo.get("exif", "")
|
exif = im.encoderinfo.get("exif", "")
|
||||||
|
if isinstance(exif, ImageFile.Exif):
|
||||||
|
exif = exif.tobytes()
|
||||||
xmp = im.encoderinfo.get("xmp", "")
|
xmp = im.encoderinfo.get("xmp", "")
|
||||||
|
|
||||||
if im.mode not in _VALID_WEBP_LEGACY_MODES:
|
if im.mode not in _VALID_WEBP_LEGACY_MODES:
|
||||||
|
|
Loading…
Reference in New Issue
Block a user