mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-12-26 01:46:18 +03:00
Merge pull request #7092 from radarhere/exif_transpose
Added in_place argument to ImageOps.exif_transpose()
This commit is contained in:
commit
561986ee71
BIN
Tests/images/orientation_rectangle.jpg
Normal file
BIN
Tests/images/orientation_rectangle.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 669 B |
|
@ -404,6 +404,18 @@ def test_exif_transpose():
|
||||||
assert 0x0112 not in transposed_im.getexif()
|
assert 0x0112 not in transposed_im.getexif()
|
||||||
|
|
||||||
|
|
||||||
|
def test_exif_transpose_in_place():
|
||||||
|
with Image.open("Tests/images/orientation_rectangle.jpg") as im:
|
||||||
|
assert im.size == (2, 1)
|
||||||
|
assert im.getexif()[0x0112] == 8
|
||||||
|
expected = im.rotate(90, expand=True)
|
||||||
|
|
||||||
|
ImageOps.exif_transpose(im, in_place=True)
|
||||||
|
assert im.size == (1, 2)
|
||||||
|
assert 0x0112 not in im.getexif()
|
||||||
|
assert_image_equal(im, expected)
|
||||||
|
|
||||||
|
|
||||||
def test_autocontrast_cutoff():
|
def test_autocontrast_cutoff():
|
||||||
# Test the cutoff argument of autocontrast
|
# Test the cutoff argument of autocontrast
|
||||||
with Image.open("Tests/images/bw_gradient.png") as img:
|
with Image.open("Tests/images/bw_gradient.png") as img:
|
||||||
|
|
|
@ -1433,12 +1433,12 @@ class Image:
|
||||||
self._exif.load(exif_info)
|
self._exif.load(exif_info)
|
||||||
|
|
||||||
# XMP tags
|
# XMP tags
|
||||||
if 0x0112 not in self._exif:
|
if ExifTags.Base.Orientation not in self._exif:
|
||||||
xmp_tags = self.info.get("XML:com.adobe.xmp")
|
xmp_tags = self.info.get("XML:com.adobe.xmp")
|
||||||
if xmp_tags:
|
if xmp_tags:
|
||||||
match = re.search(r'tiff:Orientation(="|>)([0-9])', xmp_tags)
|
match = re.search(r'tiff:Orientation(="|>)([0-9])', xmp_tags)
|
||||||
if match:
|
if match:
|
||||||
self._exif[0x0112] = int(match[2])
|
self._exif[ExifTags.Base.Orientation] = int(match[2])
|
||||||
|
|
||||||
return self._exif
|
return self._exif
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ import functools
|
||||||
import operator
|
import operator
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from . import Image, ImagePalette
|
from . import ExifTags, Image, ImagePalette
|
||||||
|
|
||||||
#
|
#
|
||||||
# helpers
|
# helpers
|
||||||
|
@ -576,19 +576,20 @@ def solarize(image, threshold=128):
|
||||||
return _lut(image, lut)
|
return _lut(image, lut)
|
||||||
|
|
||||||
|
|
||||||
def exif_transpose(image):
|
def exif_transpose(image, *, in_place=False):
|
||||||
"""
|
"""
|
||||||
If an image has an EXIF Orientation tag, other than 1, return a new image
|
If an image has an EXIF Orientation tag, other than 1, transpose the image
|
||||||
that is transposed accordingly. The new image will have the orientation
|
accordingly, and remove the orientation data.
|
||||||
data removed.
|
|
||||||
|
|
||||||
Otherwise, return a copy of the image.
|
|
||||||
|
|
||||||
:param image: The image to transpose.
|
:param image: The image to transpose.
|
||||||
:return: An image.
|
:param in_place: Boolean. Keyword-only argument.
|
||||||
|
If ``True``, the original image is modified in-place, and ``None`` is returned.
|
||||||
|
If ``False`` (default), a new :py:class:`~PIL.Image.Image` object is returned
|
||||||
|
with the transposition applied. If there is no transposition, a copy of the
|
||||||
|
image will be returned.
|
||||||
"""
|
"""
|
||||||
exif = image.getexif()
|
image_exif = image.getexif()
|
||||||
orientation = exif.get(0x0112)
|
orientation = image_exif.get(ExifTags.Base.Orientation)
|
||||||
method = {
|
method = {
|
||||||
2: Image.Transpose.FLIP_LEFT_RIGHT,
|
2: Image.Transpose.FLIP_LEFT_RIGHT,
|
||||||
3: Image.Transpose.ROTATE_180,
|
3: Image.Transpose.ROTATE_180,
|
||||||
|
@ -600,22 +601,28 @@ 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_exif = transposed_image.getexif()
|
if in_place:
|
||||||
if 0x0112 in transposed_exif:
|
image.im = transposed_image.im
|
||||||
del transposed_exif[0x0112]
|
image.pyaccess = None
|
||||||
if "exif" in transposed_image.info:
|
image._size = transposed_image._size
|
||||||
transposed_image.info["exif"] = transposed_exif.tobytes()
|
exif_image = image if in_place else transposed_image
|
||||||
elif "Raw profile type exif" in transposed_image.info:
|
|
||||||
transposed_image.info[
|
exif = exif_image.getexif()
|
||||||
"Raw profile type exif"
|
if ExifTags.Base.Orientation in exif:
|
||||||
] = transposed_exif.tobytes().hex()
|
del exif[ExifTags.Base.Orientation]
|
||||||
elif "XML:com.adobe.xmp" in transposed_image.info:
|
if "exif" in exif_image.info:
|
||||||
|
exif_image.info["exif"] = exif.tobytes()
|
||||||
|
elif "Raw profile type exif" in exif_image.info:
|
||||||
|
exif_image.info["Raw profile type exif"] = exif.tobytes().hex()
|
||||||
|
elif "XML:com.adobe.xmp" in exif_image.info:
|
||||||
for pattern in (
|
for pattern in (
|
||||||
r'tiff:Orientation="([0-9])"',
|
r'tiff:Orientation="([0-9])"',
|
||||||
r"<tiff:Orientation>([0-9])</tiff:Orientation>",
|
r"<tiff:Orientation>([0-9])</tiff:Orientation>",
|
||||||
):
|
):
|
||||||
transposed_image.info["XML:com.adobe.xmp"] = re.sub(
|
exif_image.info["XML:com.adobe.xmp"] = re.sub(
|
||||||
pattern, "", transposed_image.info["XML:com.adobe.xmp"]
|
pattern, "", exif_image.info["XML:com.adobe.xmp"]
|
||||||
)
|
)
|
||||||
|
if not in_place:
|
||||||
return transposed_image
|
return transposed_image
|
||||||
|
elif not in_place:
|
||||||
return image.copy()
|
return image.copy()
|
||||||
|
|
|
@ -49,7 +49,7 @@ from collections.abc import MutableMapping
|
||||||
from fractions import Fraction
|
from fractions import Fraction
|
||||||
from numbers import Number, Rational
|
from numbers import Number, Rational
|
||||||
|
|
||||||
from . import Image, ImageFile, ImageOps, ImagePalette, TiffTags
|
from . import ExifTags, Image, ImageFile, ImageOps, ImagePalette, TiffTags
|
||||||
from ._binary import i16be as i16
|
from ._binary import i16be as i16
|
||||||
from ._binary import i32be as i32
|
from ._binary import i32be as i32
|
||||||
from ._binary import o8
|
from ._binary import o8
|
||||||
|
@ -1185,7 +1185,7 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
:returns: Photoshop "Image Resource Blocks" in a dictionary.
|
:returns: Photoshop "Image Resource Blocks" in a dictionary.
|
||||||
"""
|
"""
|
||||||
blocks = {}
|
blocks = {}
|
||||||
val = self.tag_v2.get(0x8649)
|
val = self.tag_v2.get(ExifTags.Base.ImageResources)
|
||||||
if val:
|
if val:
|
||||||
while val[:4] == b"8BIM":
|
while val[:4] == b"8BIM":
|
||||||
id = i16(val[4:6])
|
id = i16(val[4:6])
|
||||||
|
@ -1550,7 +1550,7 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
palette = [o8(b // 256) for b in self.tag_v2[COLORMAP]]
|
palette = [o8(b // 256) for b in self.tag_v2[COLORMAP]]
|
||||||
self.palette = ImagePalette.raw("RGB;L", b"".join(palette))
|
self.palette = ImagePalette.raw("RGB;L", b"".join(palette))
|
||||||
|
|
||||||
self._tile_orientation = self.tag_v2.get(0x0112)
|
self._tile_orientation = self.tag_v2.get(ExifTags.Base.Orientation)
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
|
Loading…
Reference in New Issue
Block a user