From 3d22733d20960c26aff5edb1261c5ddd85052e9b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 5 Feb 2025 19:55:18 +1100 Subject: [PATCH 01/12] Indicate support of running with GIL disabled, as per #8216 --- src/_avif.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/_avif.c b/src/_avif.c index c43fddd6a7d..f9fbd168e3d 100644 --- a/src/_avif.c +++ b/src/_avif.c @@ -940,5 +940,9 @@ PyInit__avif(void) { return NULL; } +#ifdef Py_GIL_DISABLED + PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED); +#endif + return m; } From 8b8d2a697bdb378b77c66b5f4e5a2fa496f8ad7a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 6 Feb 2025 22:35:38 +1100 Subject: [PATCH 02/12] Pass size as tuple to C, as per #8733 --- src/PIL/AvifImagePlugin.py | 6 ++---- src/_avif.c | 4 ++-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/PIL/AvifImagePlugin.py b/src/PIL/AvifImagePlugin.py index 09f0cd95a8a..874db4f5b39 100644 --- a/src/PIL/AvifImagePlugin.py +++ b/src/PIL/AvifImagePlugin.py @@ -212,8 +212,7 @@ def _save( # Setup the AVIF encoder enc = _avif.AvifEncoder( - im.size[0], - im.size[1], + im.size, subsampling, quality, speed, @@ -260,8 +259,7 @@ def _save( enc.add( frame.tobytes("raw", rawmode), frame_dur, - frame.size[0], - frame.size[1], + frame.size, rawmode, is_single_frame, ) diff --git a/src/_avif.c b/src/_avif.c index f9fbd168e3d..69b9ba83b31 100644 --- a/src/_avif.c +++ b/src/_avif.c @@ -252,7 +252,7 @@ AvifEncoderNew(PyObject *self_, PyObject *args) { if (!PyArg_ParseTuple( args, - "IIsiiissiiOOy*y*iy*O", + "(II)siiissiiOOy*y*iy*O", &width, &height, &subsampling, @@ -479,7 +479,7 @@ _encoder_add(AvifEncoderObject *self, PyObject *args) { if (!PyArg_ParseTuple( args, - "z#IIIsO", + "z#I(II)sO", (char **)&rgb_bytes, &size, &duration, From 403ec098489f339390d352bea3f7ed8aff7960cd Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 8 Feb 2025 18:49:38 +1100 Subject: [PATCH 03/12] Changed error message --- src/PIL/AvifImagePlugin.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/PIL/AvifImagePlugin.py b/src/PIL/AvifImagePlugin.py index 874db4f5b39..c26f97f0204 100644 --- a/src/PIL/AvifImagePlugin.py +++ b/src/PIL/AvifImagePlugin.py @@ -61,10 +61,7 @@ class AvifImageFile(ImageFile.ImageFile): def _open(self) -> None: if not SUPPORTED: - msg = ( - "image file could not be identified because AVIF " - "support not installed" - ) + msg = "image file could not be opened because AVIF support not installed" raise SyntaxError(msg) if DECODE_CODEC_CHOICE != "auto" and not _avif.decoder_codec_available( From efaaad587c1575be9821622b94985bf04d4f0b59 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 8 Feb 2025 19:14:04 +1100 Subject: [PATCH 04/12] Changed argument type --- src/_avif.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_avif.c b/src/_avif.c index 69b9ba83b31..a9660deec88 100644 --- a/src/_avif.c +++ b/src/_avif.c @@ -154,7 +154,7 @@ exif_orientation_to_irot_imir(avifImage *image, int orientation) { } static int -_codec_available(const char *name, uint32_t flags) { +_codec_available(const char *name, avifCodecFlags flags) { avifCodecChoice codec = avifCodecChoiceFromName(name); if (codec == AVIF_CODEC_CHOICE_AUTO) { return 0; From 604adfcdda1b9b1f4a30e7cf72c6fdc08086dd66 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 8 Feb 2025 19:33:38 +1100 Subject: [PATCH 05/12] Use requested frame --- src/_avif.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_avif.c b/src/_avif.c index a9660deec88..2c3ab801b61 100644 --- a/src/_avif.c +++ b/src/_avif.c @@ -793,7 +793,7 @@ _decoder_get_frame(AvifDecoderObject *self, PyObject *args) { PyErr_Format( exc_type_for_avif_result(result), "Failed to decode frame %u: %s", - decoder->imageIndex + 1, + frame_index, avifResultToString(result) ); return NULL; From 2f8a635fea572fe493a836c31b2fccd227fb9e8b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 9 Feb 2025 07:17:41 +1100 Subject: [PATCH 06/12] Use AVIF_TRUE --- src/PIL/AvifImagePlugin.py | 9 +++++---- src/_avif.c | 8 ++------ 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/PIL/AvifImagePlugin.py b/src/PIL/AvifImagePlugin.py index c26f97f0204..14dcbe6114f 100644 --- a/src/PIL/AvifImagePlugin.py +++ b/src/PIL/AvifImagePlugin.py @@ -16,6 +16,7 @@ # Decoder options as module globals, until there is a way to pass parameters # to Image.open (see https://github.com/python-pillow/Pillow/issues/569) DECODE_CODEC_CHOICE = "auto" +# Decoding is only affected by this for libavif **0.8.4** or greater. DEFAULT_MAX_THREADS = 0 @@ -113,11 +114,11 @@ def seek(self, frame: int) -> None: def load(self) -> Image.core.PixelAccess | None: if self.tile: # We need to load the image data for this frame - data, timescale, tsp_in_ts, dur_in_ts = self._decoder.get_frame( - self.__frame + data, timescale, pts_in_timescales, dur_in_timescales = ( + self._decoder.get_frame(self.__frame) ) - self.info["timestamp"] = round(1000 * (tsp_in_ts / timescale)) - self.info["duration"] = round(1000 * (dur_in_ts / timescale)) + self.info["timestamp"] = round(1000 * (pts_in_timescales / timescale)) + self.info["duration"] = round(1000 * (dur_in_timescales / timescale)) # Set tile if self.fp and self._exclusive_fp: diff --git a/src/_avif.c b/src/_avif.c index 2c3ab801b61..598236ccba2 100644 --- a/src/_avif.c +++ b/src/_avif.c @@ -708,11 +708,7 @@ AvifDecoderNew(PyObject *self_, PyObject *args) { return NULL; } - if (decoder->alphaPresent) { - self->mode = "RGBA"; - } else { - self->mode = "RGB"; - } + self->mode = decoder->alphaPresent == AVIF_TRUE ? "RGBA" : "RGB"; self->decoder = decoder; self->buffer = buffer; @@ -806,7 +802,7 @@ _decoder_get_frame(AvifDecoderObject *self, PyObject *args) { rgb.depth = 8; - if (decoder->alphaPresent) { + if (decoder->alphaPresent == AVIF_TRUE) { rgb.format = AVIF_RGB_FORMAT_RGBA; } else { rgb.format = AVIF_RGB_FORMAT_RGB; From 0ca5dd7d71d52571d9fdf04060fb9d158c4c0149 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 8 Feb 2025 21:48:50 +1100 Subject: [PATCH 07/12] Removed mode from AvifDecoderObject --- src/PIL/AvifImagePlugin.py | 8 ++++---- src/_avif.c | 5 +---- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/PIL/AvifImagePlugin.py b/src/PIL/AvifImagePlugin.py index 14dcbe6114f..f92bde470e4 100644 --- a/src/PIL/AvifImagePlugin.py +++ b/src/PIL/AvifImagePlugin.py @@ -230,7 +230,7 @@ def _save( # Add each frame frame_idx = 0 - frame_dur = 0 + frame_duration = 0 cur_idx = im.tell() try: for ims in [im] + append_images: @@ -249,14 +249,14 @@ def _save( # Update frame duration if isinstance(duration, (list, tuple)): - frame_dur = duration[frame_idx] + frame_duration = duration[frame_idx] else: - frame_dur = duration + frame_duration = duration # Append the frame to the animation encoder enc.add( frame.tobytes("raw", rawmode), - frame_dur, + frame_duration, frame.size, rawmode, is_single_frame, diff --git a/src/_avif.c b/src/_avif.c index 598236ccba2..8d79e8162f4 100644 --- a/src/_avif.c +++ b/src/_avif.c @@ -16,7 +16,6 @@ static PyTypeObject AvifEncoder_Type; typedef struct { PyObject_HEAD avifDecoder *decoder; Py_buffer buffer; - char *mode; } AvifDecoderObject; static PyTypeObject AvifDecoder_Type; @@ -708,8 +707,6 @@ AvifDecoderNew(PyObject *self_, PyObject *args) { return NULL; } - self->mode = decoder->alphaPresent == AVIF_TRUE ? "RGBA" : "RGB"; - self->decoder = decoder; self->buffer = buffer; @@ -753,7 +750,7 @@ _decoder_get_info(AvifDecoderObject *self) { image->width, image->height, decoder->imageCount, - self->mode, + decoder->alphaPresent == AVIF_TRUE ? "RGBA" : "RGB", NULL == icc ? Py_None : icc, NULL == exif ? Py_None : exif, irot_imir_to_exif_orientation(image), From 8bf83187327c1d2e98f8bb171ae221550bf45eb4 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 8 Feb 2025 20:43:58 +1100 Subject: [PATCH 08/12] Use format argument for bytes --- src/_avif.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_avif.c b/src/_avif.c index 8d79e8162f4..9ceff347cc4 100644 --- a/src/_avif.c +++ b/src/_avif.c @@ -478,7 +478,7 @@ _encoder_add(AvifEncoderObject *self, PyObject *args) { if (!PyArg_ParseTuple( args, - "z#I(II)sO", + "y#I(II)sO", (char **)&rgb_bytes, &size, &duration, From afac98cf89bb30aae721a0430726b84a9f5ac056 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 8 Feb 2025 20:53:05 +1100 Subject: [PATCH 09/12] Replaced frame_index with first_frame --- src/_avif.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/_avif.c b/src/_avif.c index 9ceff347cc4..4de292155b7 100644 --- a/src/_avif.c +++ b/src/_avif.c @@ -7,7 +7,7 @@ typedef struct { PyObject_HEAD avifEncoder *encoder; avifImage *image; - int frame_index; + int first_frame; } AvifEncoderObject; static PyTypeObject AvifEncoder_Type; @@ -371,7 +371,7 @@ AvifEncoderNew(PyObject *self_, PyObject *args) { avifEncoderDestroy(encoder); return NULL; } - self->frame_index = -1; + self->first_frame = 1; avifResult result; if (icc_buffer.len) { @@ -468,7 +468,6 @@ _encoder_add(AvifEncoderObject *self, PyObject *args) { PyObject *is_single_frame = NULL; PyObject *ret = Py_None; - int is_first_frame; avifRGBImage rgb; avifResult result; @@ -490,8 +489,6 @@ _encoder_add(AvifEncoderObject *self, PyObject *args) { return NULL; } - is_first_frame = self->frame_index == -1; - if (image->width != width || image->height != height) { PyErr_Format( PyExc_ValueError, @@ -504,7 +501,7 @@ _encoder_add(AvifEncoderObject *self, PyObject *args) { return NULL; } - if (is_first_frame) { + if (self->first_frame) { // If we don't have an image populated with yuv planes, this is the first frame frame = image; } else { @@ -596,12 +593,12 @@ _encoder_add(AvifEncoderObject *self, PyObject *args) { end: avifRGBImageFreePixels(&rgb); - if (!is_first_frame) { + if (!self->first_frame) { avifImageDestroy(frame); } if (ret == Py_None) { - self->frame_index++; + self->first_frame = 0; Py_RETURN_NONE; } else { return ret; From ec87260081156082869f513c1eb79c2d1312113c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 8 Feb 2025 20:48:07 +1100 Subject: [PATCH 10/12] Use boolean format argument --- src/_avif.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/_avif.c b/src/_avif.c index 4de292155b7..f9f243584b3 100644 --- a/src/_avif.c +++ b/src/_avif.c @@ -465,7 +465,7 @@ _encoder_add(AvifEncoderObject *self, PyObject *args) { unsigned int width; unsigned int height; char *mode; - PyObject *is_single_frame = NULL; + unsigned int is_single_frame; PyObject *ret = Py_None; avifRGBImage rgb; @@ -477,7 +477,7 @@ _encoder_add(AvifEncoderObject *self, PyObject *args) { if (!PyArg_ParseTuple( args, - "y#I(II)sO", + "y#I(II)sp", (char **)&rgb_bytes, &size, &duration, @@ -573,7 +573,7 @@ _encoder_add(AvifEncoderObject *self, PyObject *args) { } uint32_t addImageFlags = AVIF_ADD_IMAGE_FLAG_NONE; - if (PyObject_IsTrue(is_single_frame)) { + if (is_single_frame) { addImageFlags |= AVIF_ADD_IMAGE_FLAG_SINGLE; } From f7f89db0a189b4e47bac157641e2060d0d4641d9 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 9 Feb 2025 07:13:14 +1100 Subject: [PATCH 11/12] Removed memset --- src/_avif.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/_avif.c b/src/_avif.c index f9f243584b3..f83f664aebc 100644 --- a/src/_avif.c +++ b/src/_avif.c @@ -791,7 +791,6 @@ _decoder_get_frame(AvifDecoderObject *self, PyObject *args) { image = decoder->image; - memset(&rgb, 0, sizeof(rgb)); avifRGBImageSetDefaults(&rgb, image); rgb.depth = 8; From 3156816e4087f738c7326386195b7633e05c0d6e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 9 Feb 2025 08:16:38 +1100 Subject: [PATCH 12/12] Removed ignoreAlpha --- src/_avif.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/_avif.c b/src/_avif.c index f83f664aebc..2b1c5fb5336 100644 --- a/src/_avif.c +++ b/src/_avif.c @@ -794,13 +794,8 @@ _decoder_get_frame(AvifDecoderObject *self, PyObject *args) { avifRGBImageSetDefaults(&rgb, image); rgb.depth = 8; - - if (decoder->alphaPresent == AVIF_TRUE) { - rgb.format = AVIF_RGB_FORMAT_RGBA; - } else { - rgb.format = AVIF_RGB_FORMAT_RGB; - rgb.ignoreAlpha = AVIF_TRUE; - } + rgb.format = + decoder->alphaPresent == AVIF_TRUE ? AVIF_RGB_FORMAT_RGBA : AVIF_RGB_FORMAT_RGB; result = avifRGBImageAllocatePixels(&rgb); if (result != AVIF_RESULT_OK) {