From ebd3c47425fe5535f8d1c8eab15bbf1e52899939 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 29 Apr 2023 15:02:11 +1000 Subject: [PATCH] When saving, allow alpha differences to indicate different frames --- Tests/test_file_apng.py | 14 ++++++++++++++ src/PIL/PngImagePlugin.py | 4 ++-- src/_imaging.c | 12 +++++++++--- src/libImaging/GetBBox.c | 7 ++++--- src/libImaging/Imaging.h | 2 +- 5 files changed, 30 insertions(+), 9 deletions(-) diff --git a/Tests/test_file_apng.py b/Tests/test_file_apng.py index f78c086eb..cf1312b77 100644 --- a/Tests/test_file_apng.py +++ b/Tests/test_file_apng.py @@ -374,6 +374,20 @@ def test_apng_save(tmp_path): assert im.getpixel((64, 32)) == (0, 255, 0, 255) +def test_apng_save_alpha(tmp_path): + test_file = str(tmp_path / "temp.png") + + im = Image.new("RGBA", (1, 1), (255, 0, 0, 255)) + im2 = Image.new("RGBA", (1, 1), (255, 0, 0, 127)) + im.save(test_file, save_all=True, append_images=[im2]) + + with Image.open(test_file) as reloaded: + assert reloaded.getpixel((0, 0)) == (255, 0, 0, 255) + + reloaded.seek(1) + assert reloaded.getpixel((0, 0)) == (255, 0, 0, 127) + + def test_apng_save_split_fdat(tmp_path): # test to make sure we do not generate sequence errors when writing # frames with image data spanning multiple fdAT chunks (in this case diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index 82a74b267..7afd6a2a6 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -1138,9 +1138,9 @@ def _write_multiple_frames(im, fp, chunk, rawmode, default_image, append_images) else: base_im = previous["im"] delta = ImageChops.subtract_modulo( - im_frame.convert("RGB"), base_im.convert("RGB") + im_frame.convert("RGBA"), base_im.convert("RGBA") ) - bbox = delta.getbbox() + bbox = delta.im.getbbox(False) if ( not bbox and prev_disposal == encoderinfo.get("disposal") diff --git a/src/_imaging.c b/src/_imaging.c index 281f3a4d2..62e51da26 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -2160,9 +2160,15 @@ _isblock(ImagingObject *self) { } static PyObject * -_getbbox(ImagingObject *self) { +_getbbox(ImagingObject *self, PyObject *args) { int bbox[4]; - if (!ImagingGetBBox(self->image, bbox)) { + + int consider_alpha = 1; + if (!PyArg_ParseTuple(args, "|i", &consider_alpha)) { + return NULL; + } + + if (!ImagingGetBBox(self->image, bbox, consider_alpha)) { Py_INCREF(Py_None); return Py_None; } @@ -3574,7 +3580,7 @@ static struct PyMethodDef methods[] = { {"isblock", (PyCFunction)_isblock, METH_NOARGS}, - {"getbbox", (PyCFunction)_getbbox, METH_NOARGS}, + {"getbbox", (PyCFunction)_getbbox, METH_VARARGS}, {"getcolors", (PyCFunction)_getcolors, METH_VARARGS}, {"getextrema", (PyCFunction)_getextrema, METH_NOARGS}, {"getprojection", (PyCFunction)_getprojection, METH_NOARGS}, diff --git a/src/libImaging/GetBBox.c b/src/libImaging/GetBBox.c index e73153600..c1570cd3e 100644 --- a/src/libImaging/GetBBox.c +++ b/src/libImaging/GetBBox.c @@ -19,7 +19,7 @@ #include "Imaging.h" int -ImagingGetBBox(Imaging im, int bbox[4]) { +ImagingGetBBox(Imaging im, int bbox[4], int consider_alpha) { /* Get the bounding box for any non-zero data in the image.*/ int x, y; @@ -58,10 +58,11 @@ ImagingGetBBox(Imaging im, int bbox[4]) { INT32 mask = 0xffffffff; if (im->bands == 3) { ((UINT8 *)&mask)[3] = 0; - } else if ( + } else if (consider_alpha && ( strcmp(im->mode, "RGBa") == 0 || strcmp(im->mode, "RGBA") == 0 || strcmp(im->mode, "La") == 0 || strcmp(im->mode, "LA") == 0 || - strcmp(im->mode, "PA") == 0) { + strcmp(im->mode, "PA") == 0 + )) { #ifdef WORDS_BIGENDIAN mask = 0x000000ff; #else diff --git a/src/libImaging/Imaging.h b/src/libImaging/Imaging.h index d9ded1852..2563a0c62 100644 --- a/src/libImaging/Imaging.h +++ b/src/libImaging/Imaging.h @@ -317,7 +317,7 @@ ImagingMerge(const char *mode, Imaging bands[4]); extern int ImagingSplit(Imaging im, Imaging bands[4]); extern int -ImagingGetBBox(Imaging im, int bbox[4]); +ImagingGetBBox(Imaging im, int bbox[4], int consider_alpha); typedef struct { int x, y; INT32 count;