Skip to content

Commit 619e34b

Browse files
committed
Try reopen heif in case when can't be opened without transformations
1 parent 6b84085 commit 619e34b

File tree

4 files changed

+45
-16
lines changed

4 files changed

+45
-16
lines changed

HeifImagePlugin.py

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -99,20 +99,20 @@ class HeifImageFile(ImageFile.ImageFile):
9999
format = 'HEIF'
100100
format_description = "HEIF/HEIC image"
101101

102-
def _open(self):
102+
def _open_heif_file(self, apply_transformations):
103103
try:
104104
heif_file = pyheif.open(
105-
self.fp, apply_transformations=Transformations is None)
105+
self.fp, apply_transformations=apply_transformations)
106106
except HeifError as e:
107107
raise SyntaxError(str(e))
108108

109109
_extract_heif_exif(heif_file)
110110

111-
if Transformations is not None:
111+
if apply_transformations:
112+
self._size = heif_file.size
113+
else:
112114
heif_file = _rotate_heif_file(heif_file)
113115
self._size = heif_file.transformations.crop[2:4]
114-
else:
115-
self._size = heif_file.size
116116

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

124+
self.info.pop('exif', None)
125+
self.info.pop('icc_profile', None)
126+
124127
if heif_file.exif:
125128
self.info['exif'] = heif_file.exif
126129

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

143+
def _open(self):
139144
self.tile = []
140-
self.heif_file = heif_file
145+
self.heif_file = self._open_heif_file(Transformations is None)
141146

142147
def load(self):
143148
heif_file, self.heif_file = self.heif_file, None
144149
if heif_file:
145150
try:
146-
heif_file = heif_file.load()
151+
try:
152+
heif_file = heif_file.load()
153+
except HeifError as e:
154+
if not (e.code == 4 and e.subcode == 3003):
155+
raise
156+
# Unsupported feature: Unsupported color conversion
157+
# https://github.com/strukturag/libheif/issues/1273
158+
self.fp.seek(0)
159+
heif_file = self._open_heif_file(True).load()
147160
except HeifError as e:
161+
# Ignore EOF error and return blank image otherwise
148162
cropped_file = e.code == 7 and e.subcode == 100
149163
if not cropped_file or not ImageFile.LOAD_TRUNCATED_IMAGES:
150164
raise
151-
# Ignore EOF error and return blank image otherwise
152165

153166
self.load_prepare()
154167

71.9 KB
Binary file not shown.
52.1 KB
Binary file not shown.

tests/test_transformations.py

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,23 @@
11
from unittest import mock
22

3+
import pyheif
34
import pytest
45
from PIL import Image
56
from pyheif import open as pyheif_open
67

78
from HeifImagePlugin import Transformations
89

9-
from . import respath
10+
from . import avg_diff, respath
1011

1112

12-
skip_if_no_transformations = pytest.mark.skipif(
13+
skip_no_transformations = pytest.mark.skipif(
1314
Transformations is None,
1415
reason="pyheif doesn't support transformations")
1516

17+
skip_libheif_not_16 = pytest.mark.skipif(
18+
pyheif.libheif_version() < '1.16.0',
19+
reason="libheif < 1.16.0 can't decode odd sizes")
20+
1621

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

4954

50-
@skip_if_no_transformations
55+
@skip_no_transformations
5156
def test_empty_exif():
5257
image = open_with_custom_meta(respath('test2.heic'), exif_data=b'', orientation=1)
5358
assert 'exif' in image.info
5459
assert image.getexif()[274] == 1
5560

5661

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

6570

66-
@skip_if_no_transformations
71+
@skip_no_transformations
6772
def test_orientation_and_no_exif():
6873
image = open_with_custom_meta(respath('test2.heic'), orientation=7)
6974

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

8186

82-
@skip_if_no_transformations
87+
@skip_no_transformations
8388
def test_orientation_and_exif_with_rotation():
8489
# Orientation tag from file should suppress Exif value
8590
image = open_with_custom_meta(
@@ -89,7 +94,7 @@ def test_orientation_and_exif_with_rotation():
8994
assert image.getexif()[274] == 1
9095

9196

92-
@skip_if_no_transformations
97+
@skip_no_transformations
9398
def test_orientation_and_exif_without_rotation():
9499
image = open_with_custom_meta(
95100
respath('test2.heic'), orientation=1, exif={270: "Sample image"})
@@ -98,7 +103,7 @@ def test_orientation_and_exif_without_rotation():
98103
assert image.getexif()[274] == 1
99104

100105

101-
@skip_if_no_transformations
106+
@skip_no_transformations
102107
def test_crop_on_load():
103108
ref_image = Image.open(respath('test2.heic'))
104109
assert ref_image.size == (1280, 720)
@@ -110,3 +115,14 @@ def test_crop_on_load():
110115
image = open_with_custom_meta(respath('test2.heic'), crop=(99, 33, 512, 256))
111116
assert image.size == (512, 256)
112117
assert image.copy() == ref_image.crop((99, 33, 611, 289))
118+
119+
120+
@skip_libheif_not_16
121+
def test_fallback_to_transforms():
122+
# Image with 695x472 color and 696x472 alpha with crop
123+
image = Image.open(respath('unreadable-wo-transf.heic'))
124+
assert image.size == (695, 472)
125+
126+
ref_image = Image.open(respath('unreadable-wo-transf.ref.heic'))
127+
avg_diffs = avg_diff(image, ref_image)
128+
assert max(avg_diffs) <= 0.01

0 commit comments

Comments
 (0)