From 144c040e6ebe60ac35ee50ac4fa2526d1fdafd8f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 10 Feb 2025 07:44:05 +1100 Subject: [PATCH 01/18] Simplify Python code by receiving tuple from C, as per #8740 --- src/PIL/AvifImagePlugin.py | 3 +-- src/_avif.c | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/PIL/AvifImagePlugin.py b/src/PIL/AvifImagePlugin.py index f92bde470e4..fedccaaea80 100644 --- a/src/PIL/AvifImagePlugin.py +++ b/src/PIL/AvifImagePlugin.py @@ -77,10 +77,9 @@ def _open(self) -> None: ) # Get info from decoder - width, height, n_frames, mode, icc, exif, exif_orientation, xmp = ( + self._size, n_frames, mode, icc, exif, exif_orientation, xmp = ( self._decoder.get_info() ) - self._size = width, height self.n_frames = n_frames self.is_animated = self.n_frames > 1 self._mode = mode diff --git a/src/_avif.c b/src/_avif.c index 2b1c5fb5336..9ccffacd834 100644 --- a/src/_avif.c +++ b/src/_avif.c @@ -743,7 +743,7 @@ _decoder_get_info(AvifDecoderObject *self) { } ret = Py_BuildValue( - "IIIsSSIS", + "(II)IsSSIS", image->width, image->height, decoder->imageCount, From d991b28d3f7d323f5f99d807db57e44a805d7ae5 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 10 Feb 2025 18:49:16 +1100 Subject: [PATCH 02/18] Use default PyTypeObject value --- src/_avif.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/_avif.c b/src/_avif.c index 9ccffacd834..cab139fe25d 100644 --- a/src/_avif.c +++ b/src/_avif.c @@ -875,7 +875,6 @@ static struct PyMethodDef _decoder_methods[] = { static PyTypeObject AvifDecoder_Type = { PyVarObject_HEAD_INIT(NULL, 0).tp_name = "AvifDecoder", .tp_basicsize = sizeof(AvifDecoderObject), - .tp_itemsize = 0, .tp_dealloc = (destructor)_decoder_dealloc, .tp_flags = Py_TPFLAGS_DEFAULT, .tp_methods = _decoder_methods, From 5368cd12e1e50454cd1ae02e918764d5382fa045 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 11 Feb 2025 12:17:11 +1100 Subject: [PATCH 03/18] Removed AVIF_TRUE --- src/_avif.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/_avif.c b/src/_avif.c index cab139fe25d..454ef5a49b7 100644 --- a/src/_avif.c +++ b/src/_avif.c @@ -747,7 +747,7 @@ _decoder_get_info(AvifDecoderObject *self) { image->width, image->height, decoder->imageCount, - decoder->alphaPresent == AVIF_TRUE ? "RGBA" : "RGB", + decoder->alphaPresent ? "RGBA" : "RGB", NULL == icc ? Py_None : icc, NULL == exif ? Py_None : exif, irot_imir_to_exif_orientation(image), @@ -794,8 +794,7 @@ _decoder_get_frame(AvifDecoderObject *self, PyObject *args) { avifRGBImageSetDefaults(&rgb, image); rgb.depth = 8; - rgb.format = - decoder->alphaPresent == AVIF_TRUE ? AVIF_RGB_FORMAT_RGBA : AVIF_RGB_FORMAT_RGB; + rgb.format = decoder->alphaPresent ? AVIF_RGB_FORMAT_RGBA : AVIF_RGB_FORMAT_RGB; result = avifRGBImageAllocatePixels(&rgb); if (result != AVIF_RESULT_OK) { From c14f3d2ceeef00fdb3febee80fc33475f54e9e0f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 11 Feb 2025 19:59:39 +1100 Subject: [PATCH 04/18] Width and height are already set on first frame --- src/_avif.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/_avif.c b/src/_avif.c index 454ef5a49b7..0539aa4268b 100644 --- a/src/_avif.c +++ b/src/_avif.c @@ -507,6 +507,8 @@ _encoder_add(AvifEncoderObject *self, PyObject *args) { } else { frame = avifImageCreateEmpty(); + frame->width = width; + frame->height = height; frame->colorPrimaries = image->colorPrimaries; frame->transferCharacteristics = image->transferCharacteristics; frame->matrixCoefficients = image->matrixCoefficients; @@ -518,9 +520,6 @@ _encoder_add(AvifEncoderObject *self, PyObject *args) { #endif } - frame->width = width; - frame->height = height; - memset(&rgb, 0, sizeof(avifRGBImage)); avifRGBImageSetDefaults(&rgb, frame); From 2be978c1ffd2041c39034d0d64f8b269a42efd19 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 11 Feb 2025 20:01:44 +1100 Subject: [PATCH 05/18] Removed memset --- src/_avif.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/_avif.c b/src/_avif.c index 0539aa4268b..aa7a4059b6e 100644 --- a/src/_avif.c +++ b/src/_avif.c @@ -520,8 +520,6 @@ _encoder_add(AvifEncoderObject *self, PyObject *args) { #endif } - memset(&rgb, 0, sizeof(avifRGBImage)); - avifRGBImageSetDefaults(&rgb, frame); rgb.depth = 8; From 4e36e73d8f0db2a218f7abdb0642a2a6df50806d Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 11 Feb 2025 20:38:54 +1100 Subject: [PATCH 06/18] Depth is set by avifRGBImageSetDefaults --- src/_avif.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/_avif.c b/src/_avif.c index aa7a4059b6e..997ceaa0ecd 100644 --- a/src/_avif.c +++ b/src/_avif.c @@ -521,7 +521,6 @@ _encoder_add(AvifEncoderObject *self, PyObject *args) { } avifRGBImageSetDefaults(&rgb, frame); - rgb.depth = 8; if (strcmp(mode, "RGBA") == 0) { rgb.format = AVIF_RGB_FORMAT_RGBA; @@ -542,7 +541,7 @@ _encoder_add(AvifEncoderObject *self, PyObject *args) { if (rgb.rowBytes * rgb.height != size) { PyErr_Format( PyExc_RuntimeError, - "rgb data is incorrect size: %u * %u (%u) != %u", + "rgb data has incorrect size: %u * %u (%u) != %u", rgb.rowBytes, rgb.height, rgb.rowBytes * rgb.height, @@ -569,10 +568,8 @@ _encoder_add(AvifEncoderObject *self, PyObject *args) { goto end; } - uint32_t addImageFlags = AVIF_ADD_IMAGE_FLAG_NONE; - if (is_single_frame) { - addImageFlags |= AVIF_ADD_IMAGE_FLAG_SINGLE; - } + uint32_t addImageFlags = + is_single_frame ? AVIF_ADD_IMAGE_FLAG_SINGLE : AVIF_ADD_IMAGE_FLAG_NONE; Py_BEGIN_ALLOW_THREADS; result = avifEncoderAddImage(encoder, frame, duration, addImageFlags); From ac7d77ea724772f3b96c7fc7e8ae0ca7a1eaa9d3 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 11 Feb 2025 20:47:31 +1100 Subject: [PATCH 07/18] Replace PyObject with int --- src/_avif.c | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/_avif.c b/src/_avif.c index 997ceaa0ecd..bf797e6e604 100644 --- a/src/_avif.c +++ b/src/_avif.c @@ -466,7 +466,7 @@ _encoder_add(AvifEncoderObject *self, PyObject *args) { unsigned int height; char *mode; unsigned int is_single_frame; - PyObject *ret = Py_None; + int error = 0; avifRGBImage rgb; avifResult result; @@ -547,7 +547,7 @@ _encoder_add(AvifEncoderObject *self, PyObject *args) { rgb.rowBytes * rgb.height, size ); - ret = NULL; + error = 1; goto end; } @@ -564,7 +564,7 @@ _encoder_add(AvifEncoderObject *self, PyObject *args) { "Conversion to YUV failed: %s", avifResultToString(result) ); - ret = NULL; + error = 1; goto end; } @@ -581,7 +581,7 @@ _encoder_add(AvifEncoderObject *self, PyObject *args) { "Failed to encode image: %s", avifResultToString(result) ); - ret = NULL; + error = 1; goto end; } @@ -591,12 +591,11 @@ _encoder_add(AvifEncoderObject *self, PyObject *args) { avifImageDestroy(frame); } - if (ret == Py_None) { - self->first_frame = 0; - Py_RETURN_NONE; - } else { - return ret; + if (error) { + return NULL; } + self->first_frame = 0; + Py_RETURN_NONE; } PyObject * From 90917bd7ddaa57769c25b1467ed2e531c6b5c3dd Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 11 Feb 2025 20:54:21 +1100 Subject: [PATCH 08/18] After a failed pixel allocation, destroy non-first frame --- src/_avif.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/_avif.c b/src/_avif.c index bf797e6e604..bf89ddf8786 100644 --- a/src/_avif.c +++ b/src/_avif.c @@ -535,7 +535,8 @@ _encoder_add(AvifEncoderObject *self, PyObject *args) { "Pixel allocation failed: %s", avifResultToString(result) ); - return NULL; + error = 1; + goto end; } if (rgb.rowBytes * rgb.height != size) { @@ -586,7 +587,9 @@ _encoder_add(AvifEncoderObject *self, PyObject *args) { } end: - avifRGBImageFreePixels(&rgb); + if (&rgb) { + avifRGBImageFreePixels(&rgb); + } if (!self->first_frame) { avifImageDestroy(frame); } From 461ccddce1c3392eed76350056858ff5869c8252 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 11 Feb 2025 21:34:46 +1100 Subject: [PATCH 09/18] Added error if avifImageCreateEmpty returns NULL --- src/_avif.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/_avif.c b/src/_avif.c index bf89ddf8786..0ee12b2948f 100644 --- a/src/_avif.c +++ b/src/_avif.c @@ -275,6 +275,10 @@ AvifEncoderNew(PyObject *self_, PyObject *args) { // Create a new animation encoder and picture frame avifImage *image = avifImageCreateEmpty(); + if (image == NULL) { + PyErr_SetString(PyExc_ValueError, "Image creation failed"); + return NULL; + } // Set these in advance so any upcoming RGB -> YUV use the proper coefficients if (strcmp(range, "full") == 0) { @@ -506,6 +510,10 @@ _encoder_add(AvifEncoderObject *self, PyObject *args) { frame = image; } else { frame = avifImageCreateEmpty(); + if (image == NULL) { + PyErr_SetString(PyExc_ValueError, "Image creation failed"); + return NULL; + } frame->width = width; frame->height = height; From e339a8795c00475ec124cb6e937faf2e75d2a106 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 11 Feb 2025 21:11:37 +1100 Subject: [PATCH 10/18] Python images cannot have negative dimensions --- src/PIL/AvifImagePlugin.py | 3 +-- src/_avif.c | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/PIL/AvifImagePlugin.py b/src/PIL/AvifImagePlugin.py index fedccaaea80..b76302a84dd 100644 --- a/src/PIL/AvifImagePlugin.py +++ b/src/PIL/AvifImagePlugin.py @@ -150,8 +150,6 @@ def _save( for ims in [im] + append_images: total += getattr(ims, "n_frames", 1) - is_single_frame = total == 1 - quality = info.get("quality", 75) if not isinstance(quality, int) or quality < 0 or quality > 100: msg = "Invalid quality setting" @@ -231,6 +229,7 @@ def _save( frame_idx = 0 frame_duration = 0 cur_idx = im.tell() + is_single_frame = total == 1 try: for ims in [im] + append_images: # Get # of frames in this image diff --git a/src/_avif.c b/src/_avif.c index 0ee12b2948f..6385caa5246 100644 --- a/src/_avif.c +++ b/src/_avif.c @@ -305,7 +305,7 @@ AvifEncoderNew(PyObject *self_, PyObject *args) { } // Validate canvas dimensions - if (width <= 0 || height <= 0) { + if (width == 0 || height == 0) { PyErr_SetString(PyExc_ValueError, "invalid canvas dimensions"); avifImageDestroy(image); return NULL; From 84e6fa8fe16aa8a1817cf93fd7161dbd563b5015 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 11 Feb 2025 21:24:30 +1100 Subject: [PATCH 11/18] Test invalid canvas dimensions --- Tests/test_file_avif.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Tests/test_file_avif.py b/Tests/test_file_avif.py index f34fed46687..692adc07c5f 100644 --- a/Tests/test_file_avif.py +++ b/Tests/test_file_avif.py @@ -156,6 +156,12 @@ def test_AvifDecoder_with_invalid_args(self) -> None: with pytest.raises(TypeError): _avif.AvifDecoder() + def test_invalid_dimensions(self, tmp_path: Path) -> None: + test_file = str(tmp_path / "temp.avif") + im = Image.new("RGB", (0, 0)) + with pytest.raises(ValueError): + im.save(test_file) + def test_encoder_finish_none_error( self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path ) -> None: From 629c4942e0ec7189828fc1053d606caf583fea48 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 11 Feb 2025 21:32:27 +1100 Subject: [PATCH 12/18] Use boolean format argument --- src/_avif.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/_avif.c b/src/_avif.c index 6385caa5246..fd579aa611d 100644 --- a/src/_avif.c +++ b/src/_avif.c @@ -239,8 +239,8 @@ AvifEncoderNew(PyObject *self_, PyObject *args) { Py_buffer icc_buffer; Py_buffer exif_buffer; Py_buffer xmp_buffer; - PyObject *alpha_premultiplied; - PyObject *autotiling; + int alpha_premultiplied; + int autotiling; int tile_rows_log2; int tile_cols_log2; @@ -251,7 +251,7 @@ AvifEncoderNew(PyObject *self_, PyObject *args) { if (!PyArg_ParseTuple( args, - "(II)siiissiiOOy*y*iy*O", + "(II)siiissiippy*y*iy*O", &width, &height, &subsampling, @@ -315,7 +315,7 @@ AvifEncoderNew(PyObject *self_, PyObject *args) { image->depth = 8; #if AVIF_VERSION >= 90000 - image->alphaPremultiplied = alpha_premultiplied == Py_True ? AVIF_TRUE : AVIF_FALSE; + image->alphaPremultiplied = alpha_premultiplied ? AVIF_TRUE : AVIF_FALSE; #endif encoder = avifEncoderCreate(); @@ -348,7 +348,7 @@ AvifEncoderNew(PyObject *self_, PyObject *args) { encoder->tileColsLog2 = normalize_tiles_log2(tile_cols_log2); #if AVIF_VERSION >= 110000 - encoder->autoTiling = autotiling == Py_True ? AVIF_TRUE : AVIF_FALSE; + encoder->autoTiling = autotiling ? AVIF_TRUE : AVIF_FALSE; #endif if (advanced != Py_None) { From d6692e0a5c03d5664077839e0111c1d23d6b1533 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 11 Feb 2025 22:25:01 +1100 Subject: [PATCH 13/18] Handle avifDecoderCreate and avifEncoderCreate errors --- src/_avif.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/_avif.c b/src/_avif.c index fd579aa611d..4aa718545cc 100644 --- a/src/_avif.c +++ b/src/_avif.c @@ -319,6 +319,11 @@ AvifEncoderNew(PyObject *self_, PyObject *args) { #endif encoder = avifEncoderCreate(); + if (!encoder) { + PyErr_SetString(PyExc_MemoryError, "Can't allocate encoder"); + avifImageDestroy(image); + return NULL; + } int is_aom_encode = strcmp(codec, "aom") == 0 || (strcmp(codec, "auto") == 0 && @@ -669,6 +674,12 @@ AvifDecoderNew(PyObject *self_, PyObject *args) { } decoder = avifDecoderCreate(); + if (!decoder) { + PyErr_SetString(PyExc_MemoryError, "Can't allocate decoder"); + PyBuffer_Release(&buffer); + PyObject_Del(self); + return NULL; + } #if AVIF_VERSION >= 80400 decoder->maxThreads = max_threads; #endif From bb874556765405daef2c0e2913756aa0beba04af Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 11 Feb 2025 21:57:32 +1100 Subject: [PATCH 14/18] tileRowsLog2 and tileColsLog2 are ignored if autotiling is enabled --- docs/handbook/image-file-formats.rst | 3 ++- src/_avif.c | 9 +++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index 63aa7f1decb..1731b1745e1 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -1388,7 +1388,8 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options: **tile_rows** / **tile_cols** For tile encoding, the (log 2) number of tile rows and columns to use. - Valid values are 0-6, default 0. + Valid values are 0-6, default 0. Ignored if "autotiling" is set to true in libavif + version **0.11.0** or greater. **autotiling** Split the image up to allow parallelization. Enabled automatically if "tile_rows" diff --git a/src/_avif.c b/src/_avif.c index 4aa718545cc..4b6f1a6bd8c 100644 --- a/src/_avif.c +++ b/src/_avif.c @@ -349,11 +349,16 @@ AvifEncoderNew(PyObject *self_, PyObject *args) { } encoder->speed = speed; encoder->timescale = (uint64_t)1000; - encoder->tileRowsLog2 = normalize_tiles_log2(tile_rows_log2); - encoder->tileColsLog2 = normalize_tiles_log2(tile_cols_log2); #if AVIF_VERSION >= 110000 encoder->autoTiling = autotiling ? AVIF_TRUE : AVIF_FALSE; + if (!autotiling) { + encoder->tileRowsLog2 = normalize_tiles_log2(tile_rows_log2); + encoder->tileColsLog2 = normalize_tiles_log2(tile_cols_log2); + } +#else + encoder->tileRowsLog2 = normalize_tiles_log2(tile_rows_log2); + encoder->tileColsLog2 = normalize_tiles_log2(tile_cols_log2); #endif if (advanced != Py_None) { From 06a261ff52b57fdb8055a9fcd1b590e69b658cc9 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 11 Feb 2025 21:59:34 +1100 Subject: [PATCH 15/18] Only define _add_codec_specific_options if it may be used --- src/_avif.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/_avif.c b/src/_avif.c index 4b6f1a6bd8c..e547a78ee62 100644 --- a/src/_avif.c +++ b/src/_avif.c @@ -182,6 +182,7 @@ _encoder_codec_available(PyObject *self, PyObject *args) { return PyBool_FromLong(is_available); } +#if AVIF_VERSION >= 80200 static int _add_codec_specific_options(avifEncoder *encoder, PyObject *opts) { Py_ssize_t i, size; @@ -223,6 +224,7 @@ _add_codec_specific_options(avifEncoder *encoder, PyObject *opts) { } return 0; } +#endif // Encoder functions PyObject * From 8330b6986b1f32e6b2d5395fb7b982b21848d8c1 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 12 Feb 2025 19:31:35 +1100 Subject: [PATCH 16/18] Test non-string advanced value --- Tests/test_file_avif.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_file_avif.py b/Tests/test_file_avif.py index 692adc07c5f..a87717fd461 100644 --- a/Tests/test_file_avif.py +++ b/Tests/test_file_avif.py @@ -467,7 +467,7 @@ def test_encoder_advanced_codec_options( assert ctrl_buf.getvalue() != test_buf.getvalue() @skip_unless_avif_encoder("aom") - @pytest.mark.parametrize("advanced", [{"foo": "bar"}, 1234]) + @pytest.mark.parametrize("advanced", [{"foo": "bar"}, {"foo": 1234}, 1234]) def test_encoder_advanced_codec_options_invalid( self, tmp_path: Path, advanced: dict[str, str] | int ) -> None: From 0bdde65e75249045ca971e85439c1aaa4c627421 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 12 Feb 2025 20:00:38 +1100 Subject: [PATCH 17/18] Simplified error handling in AvifEncoderNew --- src/_avif.c | 82 +++++++++++++++++++++++++++-------------------------- 1 file changed, 42 insertions(+), 40 deletions(-) diff --git a/src/_avif.c b/src/_avif.c index e547a78ee62..cc5a9c38519 100644 --- a/src/_avif.c +++ b/src/_avif.c @@ -231,7 +231,7 @@ PyObject * AvifEncoderNew(PyObject *self_, PyObject *args) { unsigned int width, height; AvifEncoderObject *self = NULL; - avifEncoder *encoder; + avifEncoder *encoder = NULL; char *subsampling; int quality; @@ -250,6 +250,7 @@ AvifEncoderNew(PyObject *self_, PyObject *args) { char *range; PyObject *advanced; + int error = 0; if (!PyArg_ParseTuple( args, @@ -279,7 +280,8 @@ AvifEncoderNew(PyObject *self_, PyObject *args) { avifImage *image = avifImageCreateEmpty(); if (image == NULL) { PyErr_SetString(PyExc_ValueError, "Image creation failed"); - return NULL; + error = 1; + goto end; } // Set these in advance so any upcoming RGB -> YUV use the proper coefficients @@ -289,8 +291,8 @@ AvifEncoderNew(PyObject *self_, PyObject *args) { image->yuvRange = AVIF_RANGE_LIMITED; } else { PyErr_SetString(PyExc_ValueError, "Invalid range"); - avifImageDestroy(image); - return NULL; + error = 1; + goto end; } if (strcmp(subsampling, "4:0:0") == 0) { image->yuvFormat = AVIF_PIXEL_FORMAT_YUV400; @@ -302,15 +304,15 @@ AvifEncoderNew(PyObject *self_, PyObject *args) { image->yuvFormat = AVIF_PIXEL_FORMAT_YUV444; } else { PyErr_Format(PyExc_ValueError, "Invalid subsampling: %s", subsampling); - avifImageDestroy(image); - return NULL; + error = 1; + goto end; } // Validate canvas dimensions if (width == 0 || height == 0) { PyErr_SetString(PyExc_ValueError, "invalid canvas dimensions"); - avifImageDestroy(image); - return NULL; + error = 1; + goto end; } image->width = width; image->height = height; @@ -323,8 +325,8 @@ AvifEncoderNew(PyObject *self_, PyObject *args) { encoder = avifEncoderCreate(); if (!encoder) { PyErr_SetString(PyExc_MemoryError, "Can't allocate encoder"); - avifImageDestroy(image); - return NULL; + error = 1; + goto end; } int is_aom_encode = strcmp(codec, "aom") == 0 || @@ -366,26 +368,23 @@ AvifEncoderNew(PyObject *self_, PyObject *args) { if (advanced != Py_None) { #if AVIF_VERSION >= 80200 if (_add_codec_specific_options(encoder, advanced)) { - avifImageDestroy(image); - avifEncoderDestroy(encoder); - return NULL; + error = 1; + goto end; } #else PyErr_SetString( PyExc_ValueError, "Advanced codec options require libavif >= 0.8.2" ); - avifImageDestroy(image); - avifEncoderDestroy(encoder); - return NULL; + error = 1; + goto end; #endif } self = PyObject_New(AvifEncoderObject, &AvifEncoder_Type); if (!self) { PyErr_SetString(PyExc_RuntimeError, "could not create encoder object"); - avifImageDestroy(image); - avifEncoderDestroy(encoder); - return NULL; + error = 1; + goto end; } self->first_frame = 1; @@ -398,13 +397,8 @@ AvifEncoderNew(PyObject *self_, PyObject *args) { "Setting ICC profile failed: %s", avifResultToString(result) ); - avifImageDestroy(image); - avifEncoderDestroy(encoder); - PyBuffer_Release(&icc_buffer); - PyBuffer_Release(&exif_buffer); - PyBuffer_Release(&xmp_buffer); - PyObject_Del(self); - return NULL; + error = 1; + goto end; } // colorPrimaries and transferCharacteristics are ignored when an ICC // profile is present, so set them to UNSPECIFIED. @@ -415,7 +409,6 @@ AvifEncoderNew(PyObject *self_, PyObject *args) { image->transferCharacteristics = AVIF_TRANSFER_CHARACTERISTICS_SRGB; } image->matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_BT601; - PyBuffer_Release(&icc_buffer); if (exif_buffer.len) { result = avifImageSetMetadataExif(image, exif_buffer.buf, exif_buffer.len); @@ -425,15 +418,10 @@ AvifEncoderNew(PyObject *self_, PyObject *args) { "Setting EXIF data failed: %s", avifResultToString(result) ); - avifImageDestroy(image); - avifEncoderDestroy(encoder); - PyBuffer_Release(&exif_buffer); - PyBuffer_Release(&xmp_buffer); - PyObject_Del(self); - return NULL; + error = 1; + goto end; } } - PyBuffer_Release(&exif_buffer); if (xmp_buffer.len) { result = avifImageSetMetadataXMP(image, xmp_buffer.buf, xmp_buffer.len); @@ -443,14 +431,10 @@ AvifEncoderNew(PyObject *self_, PyObject *args) { "Setting XMP data failed: %s", avifResultToString(result) ); - avifImageDestroy(image); - avifEncoderDestroy(encoder); - PyBuffer_Release(&xmp_buffer); - PyObject_Del(self); - return NULL; + error = 1; + goto end; } } - PyBuffer_Release(&xmp_buffer); if (exif_orientation > 1) { exif_orientation_to_irot_imir(image, exif_orientation); @@ -459,6 +443,24 @@ AvifEncoderNew(PyObject *self_, PyObject *args) { self->image = image; self->encoder = encoder; +end: + PyBuffer_Release(&icc_buffer); + PyBuffer_Release(&exif_buffer); + PyBuffer_Release(&xmp_buffer); + + if (error) { + if (image) { + avifImageDestroy(image); + } + if (encoder) { + avifEncoderDestroy(encoder); + } + if (self) { + PyObject_Del(self); + } + return NULL; + } + return (PyObject *)self; } From 56f85512816e12628cb3a7e9bde88cc877e72584 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 12 Feb 2025 22:47:53 +1100 Subject: [PATCH 18/18] Corrected heading --- docs/handbook/image-file-formats.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index 1731b1745e1..69fb7f3b0c0 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -1413,7 +1413,7 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options: The XMP data to include in the saved file. Saving sequences -~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~ When calling :py:meth:`~PIL.Image.Image.save` to write an AVIF file, by default only the first frame of a multiframe image will be saved. If the ``save_all``