From c9f2e692cf87c65e0ae696ae676aed591544c7a4 Mon Sep 17 00:00:00 2001 From: Alexander Piskun <13381981+bigcat88@users.noreply.github.com> Date: Tue, 24 Oct 2023 09:15:34 +0300 Subject: [PATCH] libheif 1.17.1 support (#156) * fixed "heif_writer callback returned a null error text" * fix of writing NCLX color profile for libheif 1.17.0 * disabled two AVIF tests with aom=3.3.0 --------- Signed-off-by: Alexander Piskun --- .github/workflows/analysis-coverage.yml | 8 ++++---- CHANGELOG.md | 6 ++++++ pillow_heif/_pillow_heif.c | 25 ++++++++++++++----------- tests/basic_test.py | 1 + tests/metadata_etc_test.py | 8 ++++++++ tests/write_test.py | 7 +++++-- 6 files changed, 38 insertions(+), 17 deletions(-) diff --git a/.github/workflows/analysis-coverage.yml b/.github/workflows/analysis-coverage.yml index a8b9d156..9372c66d 100644 --- a/.github/workflows/analysis-coverage.yml +++ b/.github/workflows/analysis-coverage.yml @@ -35,13 +35,13 @@ jobs: coverage-linux: runs-on: ubuntu-22.04 - name: Coverage(Linux) • 🐍3.12 + name: Coverage(Linux) • 🐍3.11 steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v4 with: - python-version: '3.12' + python-version: '3.11' - name: Prepare system run: | @@ -78,13 +78,13 @@ jobs: coverage-linux-pillow-dev: runs-on: ubuntu-22.04 - name: Coverage(Linux, Pillow-dev) • 🐍3.11 + name: Coverage(Linux, Pillow-dev) • 🐍3.12 steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v4 with: - python-version: '3.11' + python-version: '3.12' - name: Prepare system run: | diff --git a/CHANGELOG.md b/CHANGELOG.md index 09b42c9a..f56cfc86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ All notable changes to this project will be documented in this file. +## [0.14.0 - 2023-11-xx] + +### Fixed + +- Support of libheif `1.17.0`. #156 + ## [0.13.1 - 2023-10-15] ### Added diff --git a/pillow_heif/_pillow_heif.c b/pillow_heif/_pillow_heif.c index d64a85fa..adb1f051 100644 --- a/pillow_heif/_pillow_heif.c +++ b/pillow_heif/_pillow_heif.c @@ -8,7 +8,7 @@ #define RETURN_NONE Py_INCREF(Py_None); return Py_None; -static struct heif_error heif_error_no = { .code = 0, .subcode = 0, .message = NULL }; +static struct heif_error heif_error_no = { .code = 0, .subcode = 0, .message = "" }; int check_error(struct heif_error error) { if (error.code == heif_error_Ok) { @@ -57,6 +57,7 @@ typedef struct { enum heif_chroma chroma; struct heif_image* image; struct heif_image_handle* handle; + struct heif_color_profile_nclx* output_nclx_color_profile; } CtxWriteImageObject; static PyTypeObject CtxWriteImage_Type; @@ -110,6 +111,8 @@ static void _CtxWriteImage_destructor(CtxWriteImageObject* self) { heif_image_handle_release(self->handle); if (self->image) heif_image_release(self->image); + if (self->output_nclx_color_profile) + heif_nclx_color_profile_free(self->output_nclx_color_profile); PyObject_Del(self); } @@ -496,22 +499,17 @@ static PyObject* _CtxWriteImage_set_icc_profile(CtxWriteImageObject* self, PyObj static PyObject* _CtxWriteImage_set_nclx_profile(CtxWriteImageObject* self, PyObject* args) { /* color_primaries: int, transfer_characteristics: int, matrix_coefficients: int, full_range_flag: int */ - struct heif_error error; int color_primaries, transfer_characteristics, matrix_coefficients, full_range_flag; if (!PyArg_ParseTuple(args, "iiii", &color_primaries, &transfer_characteristics, &matrix_coefficients, &full_range_flag)) return NULL; - struct heif_color_profile_nclx* nclx_color_profile = heif_nclx_color_profile_alloc(); - nclx_color_profile->color_primaries = color_primaries; - nclx_color_profile->transfer_characteristics = transfer_characteristics; - nclx_color_profile->matrix_coefficients = matrix_coefficients; - nclx_color_profile->full_range_flag = full_range_flag; - error = heif_image_set_nclx_color_profile(self->image, nclx_color_profile); - heif_nclx_color_profile_free(nclx_color_profile); - if (check_error(error)) - return NULL; + self->output_nclx_color_profile = heif_nclx_color_profile_alloc(); + self->output_nclx_color_profile->color_primaries = color_primaries; + self->output_nclx_color_profile->transfer_characteristics = transfer_characteristics; + self->output_nclx_color_profile->matrix_coefficients = matrix_coefficients; + self->output_nclx_color_profile->full_range_flag = full_range_flag; RETURN_NONE } @@ -528,6 +526,10 @@ static PyObject* _CtxWriteImage_encode(CtxWriteImageObject* self, PyObject* args Py_BEGIN_ALLOW_THREADS options = heif_encoding_options_alloc(); options->macOS_compatibility_workaround_no_nclx_profile = !save_nclx; + if (!self->output_nclx_color_profile && save_nclx) + self->output_nclx_color_profile = heif_nclx_color_profile_alloc(); + if (self->output_nclx_color_profile) + options->output_nclx_profile = self->output_nclx_color_profile; error = heif_context_encode_image(ctx_write->ctx, self->image, ctx_write->encoder, options, &self->handle); heif_encoding_options_free(options); Py_END_ALLOW_THREADS @@ -679,6 +681,7 @@ static PyObject* _CtxWriteImage_create(CtxWriteObject* self, PyObject* args) { ctx_write_image->chroma = chroma; ctx_write_image->image = image; ctx_write_image->handle = NULL; + ctx_write_image->output_nclx_color_profile = NULL; return (PyObject*)ctx_write_image; } diff --git a/tests/basic_test.py b/tests/basic_test.py index bf53266a..f9bdbb76 100644 --- a/tests/basic_test.py +++ b/tests/basic_test.py @@ -25,6 +25,7 @@ def test_libheif_info(): "1.15.2", "1.16.1", "1.16.2", + "1.17.1", ) diff --git a/tests/metadata_etc_test.py b/tests/metadata_etc_test.py index 4e322b68..7479f75a 100644 --- a/tests/metadata_etc_test.py +++ b/tests/metadata_etc_test.py @@ -66,6 +66,10 @@ def test_pillow_primary_image(save_format): @pytest.mark.skipif(not aom(), reason="Requires AVIF support.") @pytest.mark.skipif(not hevc_enc(), reason="Requires HEVC encoder.") +@pytest.mark.skipif( + pillow_heif.libheif_info().get("AVIF", "").find("v3.3.0") != -1, + reason="libheif 1.17.1 fails this test with this AOM version.", +) @pytest.mark.parametrize("save_format", ("HEIF", "AVIF")) def test_heif_info_changing(save_format): xmp = b"LeagueOf" @@ -111,6 +115,10 @@ def test_heif_info_changing(save_format): @pytest.mark.skipif(not aom(), reason="Requires AVIF support.") @pytest.mark.skipif(not hevc_enc(), reason="Requires HEVC encoder.") +@pytest.mark.skipif( + pillow_heif.libheif_info().get("AVIF", "").find("v3.3.0") != -1, + reason="libheif 1.17.1 fails this test with this AOM version.", +) @pytest.mark.parametrize("save_format", ("HEIF", "AVIF")) def test_pillow_info_changing(save_format): xmp = b"LeagueOf" diff --git a/tests/write_test.py b/tests/write_test.py index 3fcfaf18..46cd1414 100644 --- a/tests/write_test.py +++ b/tests/write_test.py @@ -7,6 +7,7 @@ import helpers import pytest +from packaging.version import parse as parse_version from PIL import Image, ImageSequence import pillow_heif @@ -503,7 +504,9 @@ def test_nclx_profile_write(): } im_rgb.save(buf, format="HEIF") nclx_out = Image.open(buf).info["nclx_profile"] - for k in im_rgb.info["nclx_profile"]: - assert im_rgb.info["nclx_profile"][k] == nclx_out[k] + if parse_version(pillow_heif.libheif_version()) >= parse_version("1.17.0"): + # in libheif 1.17.0 logic of this was corrected: https://github.com/strukturag/libheif/issues/995 + for k in im_rgb.info["nclx_profile"]: + assert im_rgb.info["nclx_profile"][k] == nclx_out[k] finally: pillow_heif.options.SAVE_NCLX_PROFILE = False