mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-07-02 02:43:06 +03:00
Merge pull request #3761 from radarhere/imageops
Improve exif_transpose
This commit is contained in:
commit
fb79e15f99
|
@ -1,7 +1,7 @@
|
||||||
from .helper import PillowTestCase, hopper
|
from .helper import PillowTestCase, hopper
|
||||||
|
|
||||||
from PIL import ImageOps
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
from PIL import ImageOps
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from PIL import _webp
|
from PIL import _webp
|
||||||
|
@ -239,9 +239,23 @@ class TestImageOps(PillowTestCase):
|
||||||
for i in range(2, 9):
|
for i in range(2, 9):
|
||||||
im = Image.open("Tests/images/hopper_orientation_"+str(i)+ext)
|
im = Image.open("Tests/images/hopper_orientation_"+str(i)+ext)
|
||||||
orientations.append(im)
|
orientations.append(im)
|
||||||
for im in orientations:
|
for i, orientation_im in enumerate(orientations):
|
||||||
|
for im in [
|
||||||
|
orientation_im, # ImageFile
|
||||||
|
orientation_im.copy() # Image
|
||||||
|
]:
|
||||||
|
if i == 0:
|
||||||
|
self.assertNotIn("exif", im.info)
|
||||||
|
else:
|
||||||
|
original_exif = im.info["exif"]
|
||||||
transposed_im = ImageOps.exif_transpose(im)
|
transposed_im = ImageOps.exif_transpose(im)
|
||||||
self.assert_image_similar(base_im, transposed_im, 17)
|
self.assert_image_similar(base_im, transposed_im, 17)
|
||||||
|
if i == 0:
|
||||||
|
self.assertNotIn("exif", im.info)
|
||||||
|
else:
|
||||||
|
self.assertNotEqual(transposed_im.info["exif"], original_exif)
|
||||||
|
|
||||||
|
self.assertNotIn(0x0112, transposed_im.getexif())
|
||||||
|
|
||||||
# Repeat the operation, to test that it does not keep transposing
|
# Repeat the operation, to test that it does not keep transposing
|
||||||
transposed_im2 = ImageOps.exif_transpose(transposed_im)
|
transposed_im2 = ImageOps.exif_transpose(transposed_im)
|
||||||
|
|
|
@ -464,8 +464,9 @@ Pillow identifies, reads, and writes PNG files containing ``1``, ``L``, ``LA``,
|
||||||
v1.1.7.
|
v1.1.7.
|
||||||
|
|
||||||
As of Pillow 6.0, EXIF data can be read from PNG images. However, unlike other
|
As of Pillow 6.0, EXIF data can be read from PNG images. However, unlike other
|
||||||
image formats, EXIF data is not guaranteed to have been read until
|
image formats, EXIF data is not guaranteed to be present in
|
||||||
:py:meth:`~PIL.Image.Image.load` has been called.
|
:py:attr:`~PIL.Image.Image.info` until :py:meth:`~PIL.Image.Image.load` has been
|
||||||
|
called.
|
||||||
|
|
||||||
The :py:meth:`~PIL.Image.Image.open` method sets the following
|
The :py:meth:`~PIL.Image.Image.open` method sets the following
|
||||||
:py:attr:`~PIL.Image.Image.info` properties, when appropriate:
|
:py:attr:`~PIL.Image.Image.info` properties, when appropriate:
|
||||||
|
|
|
@ -165,12 +165,20 @@ language-specific glyphs and ligatures from the font:
|
||||||
* ``ImageFont.ImageFont.getsize_multiline()``
|
* ``ImageFont.ImageFont.getsize_multiline()``
|
||||||
* ``ImageFont.ImageFont.getsize()``
|
* ``ImageFont.ImageFont.getsize()``
|
||||||
|
|
||||||
|
Added EXIF class
|
||||||
|
^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
:py:meth:`~PIL.Image.Image.getexif` has been added, and returning an
|
||||||
|
:py:class:`~PIL.Image.Exif` instance. Values can be retrieved and set like a
|
||||||
|
dictionary. When saving JPEG, PNG or WEBP, the instance can be passed as an
|
||||||
|
``exif`` argument to include any changes in the output image.
|
||||||
|
|
||||||
PNG EXIF data
|
PNG EXIF data
|
||||||
^^^^^^^^^^^^^
|
^^^^^^^^^^^^^
|
||||||
|
|
||||||
EXIF data can now be read from and saved to PNG images. However, unlike other image
|
EXIF data can now be read from and saved to PNG images. However, unlike other image
|
||||||
formats, EXIF data is not guaranteed to have been read until
|
formats, EXIF data is not guaranteed to be present in :py:attr:`~PIL.Image.Image.info`
|
||||||
:py:meth:`~PIL.Image.Image.load` has been called.
|
until :py:meth:`~PIL.Image.Image.load` has been called.
|
||||||
|
|
||||||
Other Changes
|
Other Changes
|
||||||
=============
|
=============
|
||||||
|
|
193
src/PIL/Image.py
193
src/PIL/Image.py
|
@ -40,8 +40,8 @@ except ImportError:
|
||||||
import __builtin__
|
import __builtin__
|
||||||
builtins = __builtin__
|
builtins = __builtin__
|
||||||
|
|
||||||
from . import ImageMode
|
from . import ImageMode, TiffTags
|
||||||
from ._binary import i8
|
from ._binary import i8, i32le
|
||||||
from ._util import isPath, isStringType, deferred_error
|
from ._util import isPath, isStringType, deferred_error
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
@ -54,10 +54,10 @@ import atexit
|
||||||
import numbers
|
import numbers
|
||||||
try:
|
try:
|
||||||
# Python 3
|
# Python 3
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable, MutableMapping
|
||||||
except ImportError:
|
except ImportError:
|
||||||
# Python 2.7
|
# Python 2.7
|
||||||
from collections import Callable
|
from collections import Callable, MutableMapping
|
||||||
|
|
||||||
|
|
||||||
# Silence warning
|
# Silence warning
|
||||||
|
@ -1297,6 +1297,12 @@ class Image(object):
|
||||||
return tuple(extrema)
|
return tuple(extrema)
|
||||||
return self.im.getextrema()
|
return self.im.getextrema()
|
||||||
|
|
||||||
|
def getexif(self):
|
||||||
|
exif = Exif()
|
||||||
|
if "exif" in self.info:
|
||||||
|
exif.load(self.info["exif"])
|
||||||
|
return exif
|
||||||
|
|
||||||
def getim(self):
|
def getim(self):
|
||||||
"""
|
"""
|
||||||
Returns a capsule that points to the internal image memory.
|
Returns a capsule that points to the internal image memory.
|
||||||
|
@ -3005,3 +3011,182 @@ def _apply_env_variables(env=None):
|
||||||
|
|
||||||
_apply_env_variables()
|
_apply_env_variables()
|
||||||
atexit.register(core.clear_cache)
|
atexit.register(core.clear_cache)
|
||||||
|
|
||||||
|
|
||||||
|
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))
|
||||||
|
|
|
@ -27,20 +27,11 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
from . import Image, TiffTags
|
from . import Image
|
||||||
from ._binary import i32le
|
from ._util import isPath
|
||||||
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
|
||||||
|
|
||||||
|
@ -302,12 +293,6 @@ 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):
|
||||||
"""
|
"""
|
||||||
|
@ -687,182 +672,3 @@ 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))
|
|
||||||
|
|
|
@ -532,9 +532,7 @@ def exif_transpose(image):
|
||||||
:param image: The image to transpose.
|
:param image: The image to transpose.
|
||||||
:return: An image.
|
:return: An image.
|
||||||
"""
|
"""
|
||||||
if not hasattr(image, '_exif_transposed') and hasattr(image, '_getexif'):
|
exif = image.getexif()
|
||||||
exif = image._getexif()
|
|
||||||
if exif:
|
|
||||||
orientation = exif.get(0x0112)
|
orientation = exif.get(0x0112)
|
||||||
method = {
|
method = {
|
||||||
2: Image.FLIP_LEFT_RIGHT,
|
2: Image.FLIP_LEFT_RIGHT,
|
||||||
|
@ -547,6 +545,7 @@ def exif_transpose(image):
|
||||||
}.get(orientation)
|
}.get(orientation)
|
||||||
if method is not None:
|
if method is not None:
|
||||||
transposed_image = image.transpose(method)
|
transposed_image = image.transpose(method)
|
||||||
transposed_image._exif_transposed = True
|
del exif[0x0112]
|
||||||
|
transposed_image.info["exif"] = exif.tobytes()
|
||||||
return transposed_image
|
return transposed_image
|
||||||
return image.copy()
|
return image.copy()
|
||||||
|
|
|
@ -472,7 +472,7 @@ 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
|
||||||
exif = ImageFile.Exif()
|
exif = Image.Exif()
|
||||||
return exif._fixup_dict(src_dict)
|
return exif._fixup_dict(src_dict)
|
||||||
|
|
||||||
|
|
||||||
|
@ -725,7 +725,7 @@ def _save(im, fp, filename):
|
||||||
optimize = info.get("optimize", False)
|
optimize = info.get("optimize", False)
|
||||||
|
|
||||||
exif = info.get("exif", b"")
|
exif = info.get("exif", b"")
|
||||||
if isinstance(exif, ImageFile.Exif):
|
if isinstance(exif, Image.Exif):
|
||||||
exif = exif.tobytes()
|
exif = exif.tobytes()
|
||||||
|
|
||||||
# get keyword arguments
|
# get keyword arguments
|
||||||
|
|
|
@ -886,7 +886,7 @@ 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):
|
if isinstance(exif, Image.Exif):
|
||||||
exif = exif.tobytes(8)
|
exif = exif.tobytes(8)
|
||||||
if exif.startswith(b"Exif\x00\x00"):
|
if exif.startswith(b"Exif\x00\x00"):
|
||||||
exif = exif[6:]
|
exif = exif[6:]
|
||||||
|
|
|
@ -217,7 +217,7 @@ 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):
|
if isinstance(exif, Image.Exif):
|
||||||
exif = exif.tobytes()
|
exif = exif.tobytes()
|
||||||
xmp = im.encoderinfo.get("xmp", "")
|
xmp = im.encoderinfo.get("xmp", "")
|
||||||
if allow_mixed:
|
if allow_mixed:
|
||||||
|
@ -318,7 +318,7 @@ 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):
|
if isinstance(exif, Image.Exif):
|
||||||
exif = exif.tobytes()
|
exif = exif.tobytes()
|
||||||
xmp = im.encoderinfo.get("xmp", "")
|
xmp = im.encoderinfo.get("xmp", "")
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user