Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update pyheif #28

Merged
merged 4 commits into from
Sep 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 5 additions & 10 deletions .github/workflows/Check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,20 @@ on: [push]

jobs:
check:
name: py${{ matrix.python }} pillow-${{ matrix.pillow }} libheif-${{ matrix.libheif }} pyheif-${{ matrix.pyheif }}
name: py${{ matrix.python }} pillow-${{ matrix.pillow }} libheif-${{ matrix.libheif }} no-binary-${{ matrix.no-binary }}
runs-on: ubuntu-20.04
timeout-minutes: 3
strategy:
matrix:
python: ['3.8', '3.11']
pillow: [prod, latest]
libheif: ['1.16.2-6ee6762-3f6b709', '1.18.2-bf35e9e-47f4fc0']
pyheif: ['', '0.7.1']
no-binary: ['pyheif', ':none:']
exclude:
- python: '3.11'
pyheif: '0.7.1'
no-binary: ':none:'
- libheif: '1.18.2-bf35e9e-47f4fc0'
pyheif: '0.7.1'
no-binary: ':none:'

steps:
- uses: actions/checkout@v2
Expand All @@ -32,12 +32,7 @@ jobs:
- name: Update pip
run: pip install pip==23.2.1
- name: Install dependencies
run: make install-pillow-${{ matrix.pillow }}
- name: Install ovsolete pyheif
if: ${{ matrix.pyheif }}
run: |
pip uninstall -y pyheif
pip install pyheif==${{ matrix.pyheif }}
run: make install-pillow-${{ matrix.pillow }} no-binary=${{ matrix.no-binary }}
- name: Check
run: make check
- name: Upload coverage to Codecov
Expand Down
11 changes: 2 additions & 9 deletions HeifImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,6 @@
from pyheif.error import HeifError


try:
from pyheif.transformations import Transformations
except ImportError:
Transformations = None


@dataclass
class LibheifError:
code: int
Expand Down Expand Up @@ -159,7 +153,7 @@ def _open_heif_file(self, apply_transformations):

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

def load(self):
heif_file, self.heif_file = self.heif_file, None
Expand All @@ -183,8 +177,7 @@ def load(self):
self.load_prepare()

if heif_file.data:
if Transformations is not None:
heif_file = _crop_heif_file(heif_file)
heif_file = _crop_heif_file(heif_file)
self.frombytes(heif_file.data, "raw", (self.mode, heif_file.stride))

heif_file.data = None
Expand Down
10 changes: 5 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@ docker_build:
docker_shell: docker_build
docker run --platform=linux/amd64 --rm -it -v .:/src heif-image-plugin:latest

no-binary ?= pyheif

.PHONY: install-pillow-latest
install-pillow-latest:
pip install .[test] \
git+https://github.com/uploadcare/[email protected]#egg=pyheif
pip install --no-binary $(no-binary) .[test]

.PHONY: install-pillow-prod
install-pillow-prod:
pip install .[test] \
pip install --no-binary $(no-binary) .[test] \
./pip-stubs/pillow \
git+https://github.com/uploadcare/pillow-simd.git@simd/9.5-png-truncated#egg=pillow-simd \
git+https://github.com/uploadcare/[email protected]#egg=pyheif
git+https://github.com/uploadcare/pillow-simd.git@simd/9.5-png-truncated#egg=pillow-simd
16 changes: 14 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,24 @@ You can install **heif-image-plugin** from *PyPI*:

`pip install heif-image-plugin`

### Install libheif binaries for saving capabilities

Ubuntu:

`apt install libheif-examples libheif-plugin-x265 libheif-plugin-aomenc`

## How to use

Just import once before opening an image.

```python
from PIL import Image
from PIL import Image, ImageOps
import HeifImagePlugin

image = Image.open('test.heic')
image.load()
ImageOps.exif_transpose(image, in_place=True)
# requires `heif-enc` binary with installed codecs or plugins
image.save('test.avif')
```

## How to contribute
Expand All @@ -42,6 +50,10 @@ This is not a big library but if you want to contribute is very easy!

## Changelog

### 0.7.0

* Depends on pyheif>=0.8.0, drop older versions support

### 0.6.2

* Fix for buggy LA mode in libheif 1.17.0 - 1.18.2
Expand Down
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from setuptools import setup


__version__ = '0.6.2'
__version__ = '0.7.0'

github_url = 'https://github.com/uploadcare'
package_name = 'heif-image-plugin'
Expand All @@ -26,7 +26,7 @@
download_url='%s/%s/archive/v%s.tar.gz' % (github_url, package_name, __version__),
keywords=['heif', 'heic', 'Pillow', 'plugin', 'pyhief'],
install_requires=[
"pyheif>=0.7.1",
"pyheif>=0.8.0",
"piexif>=1.1.3",
],
extras_require={
Expand Down
28 changes: 13 additions & 15 deletions tests/test_reading.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
import pytest
from PIL import Image, ImageCms, ImageOps
from pyheif.error import HeifError
from pyheif.transformations import Transformations

from HeifImagePlugin import Transformations, check_heif_magic
from HeifImagePlugin import check_heif_magic

from . import avg_diff, respath

Expand Down Expand Up @@ -69,8 +70,7 @@ def test_open_image_metadata(open_mock):
m.size = (10, 20)
m.mode = 'RGB'
m.data = b'rgb' * 10 * 20
if Transformations is not None:
m.transformations = Transformations(10, 20)
m.transformations = Transformations(10, 20)
m.metadata = [
{'type': 'foo', 'data': 'bar'},
{'type': 'bar', 'data': 'foo'},
Expand Down Expand Up @@ -102,25 +102,23 @@ def test_check_heif_magic_wrong():
def test_orientation(orientation, orientation_ref_image):
image = Image.open(respath('orientation', f'Landscape_{orientation}.heic'))

if Transformations is not None:
# There should be exif in each image, even if Orientation is 0
assert 'exif' in image.info
# There should be exif in each image, even if Orientation is 0
assert 'exif' in image.info

# There should be Orientation tag for each image
exif = image.getexif()
assert 0x0112 in exif
# There should be Orientation tag for each image
exif = image.getexif()
assert 0x0112 in exif

# And this orientation should be the same as in filename
assert exif[0x0112] == orientation
# And this orientation should be the same as in filename
assert exif[0x0112] == orientation

# Transposed image shoud be Landscape
transposed = ImageOps.exif_transpose(image)
assert transposed.size == (600, 450)

if Transformations is not None:
# Image should change after transposition
if orientation != 1:
assert image != transposed
# Image should change after transposition
if orientation != 1:
assert image != transposed

# The average diff between transposed and original image should be small
avg_diffs = avg_diff(transposed, orientation_ref_image, threshold=20)
Expand Down
30 changes: 5 additions & 25 deletions tests/test_transformations.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,12 @@
from unittest import mock

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

from HeifImagePlugin import Transformations
from pyheif.transformations import Transformations

from . import avg_diff, respath


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):
nonlocal exif_data
Expand All @@ -32,11 +20,10 @@ def my_pyheif_open(*args, **kwargs):
heif.metadata = [{'type': 'Exif', 'data': exif_data}]
else:
heif.metadata = None
if Transformations is not None:
heif.transformations = Transformations(*heif.size)
heif.transformations.orientation_tag = orientation
if crop:
heif.transformations.crop = crop
heif.transformations = Transformations(*heif.size)
heif.transformations.orientation_tag = orientation
if crop:
heif.transformations.crop = crop
return heif

with mock.patch('pyheif.open') as open_mock:
Expand All @@ -52,14 +39,12 @@ def test_no_orientation_and_no_exif():
assert 'exif' not in image.info


@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_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 @@ -68,7 +53,6 @@ def test_broken_exif():
assert image.getexif()[274] == 1


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

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


@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 @@ -94,7 +77,6 @@ def test_orientation_and_exif_with_rotation():
assert image.getexif()[274] == 1


@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 @@ -103,7 +85,6 @@ def test_orientation_and_exif_without_rotation():
assert image.getexif()[274] == 1


@skip_no_transformations
def test_crop_on_load():
ref_image = Image.open(respath('test2.heic'))
assert ref_image.size == (1280, 720)
Expand All @@ -117,7 +98,6 @@ def test_crop_on_load():
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'))
Expand Down
Loading