Skip to content

Commit

Permalink
Try reopen heif in case when can't be opened without transformations
Browse files Browse the repository at this point in the history
  • Loading branch information
homm committed Aug 19, 2024
1 parent 51f173e commit 7dc436f
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 16 deletions.
29 changes: 21 additions & 8 deletions HeifImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,20 +99,20 @@ class HeifImageFile(ImageFile.ImageFile):
format = 'HEIF'
format_description = "HEIF/HEIC image"

def _open(self):
def _open_heif_file(self, apply_transformations):
try:
heif_file = pyheif.open(
self.fp, apply_transformations=Transformations is None)
self.fp, apply_transformations=apply_transformations)
except HeifError as e:
raise SyntaxError(str(e))

_extract_heif_exif(heif_file)

if Transformations is not None:
if apply_transformations:
self._size = heif_file.size
else:
heif_file = _rotate_heif_file(heif_file)
self._size = heif_file.transformations.crop[2:4]
else:
self._size = heif_file.size

if hasattr(self, "_mode"):
self._mode = heif_file.mode
Expand All @@ -121,6 +121,9 @@ def _open(self):
# https://pillow.readthedocs.io/en/stable/releasenotes/10.1.0.html#setting-image-mode
self.mode = heif_file.mode

self.info.pop('exif', None)
self.info.pop('icc_profile', None)

if heif_file.exif:
self.info['exif'] = heif_file.exif

Expand All @@ -135,20 +138,30 @@ def _open(self):
# We need to go deeper...
if heif_file.color_profile['type'] in ('rICC', 'prof'):
self.info['icc_profile'] = heif_file.color_profile['data']
return heif_file

def _open(self):
self.tile = []
self.heif_file = heif_file
self.heif_file = self._open_heif_file(Transformations is None)

def load(self):
heif_file, self.heif_file = self.heif_file, None
if heif_file:
try:
heif_file = heif_file.load()
try:
heif_file = heif_file.load()
except HeifError as e:
if not (e.code == 4 and e.subcode == 3003):
raise
# Unsupported feature: Unsupported color conversion
# https://github.com/strukturag/libheif/issues/1273
self.fp.seek(0)
heif_file = self._open_heif_file(True).load()
except HeifError as e:
# Ignore EOF error and return blank image otherwise
cropped_file = e.code == 7 and e.subcode == 100
if not cropped_file or not ImageFile.LOAD_TRUNCATED_IMAGES:
raise
# Ignore EOF error and return blank image otherwise

self.load_prepare()

Expand Down
Binary file added tests/images/unreadable-wo-transf.heic
Binary file not shown.
Binary file added tests/images/unreadable-wo-transf.ref.heic
Binary file not shown.
32 changes: 24 additions & 8 deletions tests/test_transformations.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
from unittest import mock

import pyheif
import pytest
from PIL import Image
from pyheif import open as pyheif_open

from HeifImagePlugin import Transformations

from . import respath
from . import avg_diff, respath


skip_if_no_transformations = pytest.mark.skipif(
skip_no_transformations = pytest.mark.skipif(
Transformations is None,
reason="pyheif doesn't support transformations")

skip_libheif_not_16 = pytest.mark.skipif(
pyheif.libheif_version() < '1.16.0',
reason="libheif < 1.16.0 can't decode odd sizes")


def open_with_custom_meta(path, *, exif_data=None, exif=None, crop=None, orientation=0):
def my_pyheif_open(*args, **kwargs):
Expand Down Expand Up @@ -47,14 +52,14 @@ def test_no_orientation_and_no_exif():
assert 'exif' not in image.info


@skip_if_no_transformations
@skip_no_transformations
def test_empty_exif():
image = open_with_custom_meta(respath('test2.heic'), exif_data=b'', orientation=1)
assert 'exif' in image.info
assert image.getexif()[274] == 1


@skip_if_no_transformations
@skip_no_transformations
def test_broken_exif():
broken = b'Exif\x00\x00II*\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00'
image = open_with_custom_meta(respath('test2.heic'),
Expand All @@ -63,7 +68,7 @@ def test_broken_exif():
assert image.getexif()[274] == 1


@skip_if_no_transformations
@skip_no_transformations
def test_orientation_and_no_exif():
image = open_with_custom_meta(respath('test2.heic'), orientation=7)

Expand All @@ -79,7 +84,7 @@ def test_no_orientation_and_exif_with_rotation():
assert image.getexif()[274] == 7


@skip_if_no_transformations
@skip_no_transformations
def test_orientation_and_exif_with_rotation():
# Orientation tag from file should suppress Exif value
image = open_with_custom_meta(
Expand All @@ -89,7 +94,7 @@ def test_orientation_and_exif_with_rotation():
assert image.getexif()[274] == 1


@skip_if_no_transformations
@skip_no_transformations
def test_orientation_and_exif_without_rotation():
image = open_with_custom_meta(
respath('test2.heic'), orientation=1, exif={270: "Sample image"})
Expand All @@ -98,7 +103,7 @@ def test_orientation_and_exif_without_rotation():
assert image.getexif()[274] == 1


@skip_if_no_transformations
@skip_no_transformations
def test_crop_on_load():
ref_image = Image.open(respath('test2.heic'))
assert ref_image.size == (1280, 720)
Expand All @@ -110,3 +115,14 @@ def test_crop_on_load():
image = open_with_custom_meta(respath('test2.heic'), crop=(99, 33, 512, 256))
assert image.size == (512, 256)
assert image.copy() == ref_image.crop((99, 33, 611, 289))


@skip_libheif_not_16
def test_fallback_to_transforms():
# Image with 695x472 color and 696x472 alpha with crop
image = Image.open(respath('unreadable-wo-transf.heic'))
assert image.size == (695, 472)

ref_image = Image.open(respath('unreadable-wo-transf.ref.heic'))
avg_diffs = avg_diff(image, ref_image)
assert max(avg_diffs) <= 0.01

0 comments on commit 7dc436f

Please sign in to comment.