Skip to content

Commit

Permalink
Merge pull request #7123 from radarhere/apng
Browse files Browse the repository at this point in the history
  • Loading branch information
hugovk committed Jun 30, 2023
2 parents bd795d7 + 541d260 commit be4bfaa
Show file tree
Hide file tree
Showing 9 changed files with 65 additions and 13 deletions.
14 changes: 14 additions & 0 deletions Tests/test_file_apng.py
Expand Up @@ -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
Expand Down
12 changes: 12 additions & 0 deletions Tests/test_file_gif.py
Expand Up @@ -1130,6 +1130,18 @@ def test_bbox(tmp_path):
assert reread.n_frames == 2


def test_bbox_alpha(tmp_path):
out = str(tmp_path / "temp.gif")

im = Image.new("RGBA", (1, 2), (255, 0, 0, 255))
im.putpixel((0, 1), (255, 0, 0, 0))
im2 = Image.new("RGBA", (1, 2), (255, 0, 0, 0))
im.save(out, save_all=True, append_images=[im2])

with Image.open(out) as reread:
assert reread.n_frames == 2


def test_palette_save_L(tmp_path):
# Generate an L mode image with a separate palette

Expand Down
15 changes: 15 additions & 0 deletions Tests/test_image_getbbox.py
@@ -1,3 +1,5 @@
import pytest

from PIL import Image

from .helper import hopper
Expand Down Expand Up @@ -38,3 +40,16 @@ def check(im, fill_color):
for color in ((0, 0), (127, 0), (255, 0)):
im = Image.new(mode, (100, 100), color)
check(im, (255, 255))


@pytest.mark.parametrize("mode", ("RGBA", "RGBa", "La", "LA", "PA"))
def test_bbox_alpha_only_false(mode):
im = Image.new(mode, (100, 100))
assert im.getbbox(alpha_only=False) is None

fill_color = [1] * Image.getmodebands(mode)
fill_color[-1] = 0
im.paste(tuple(fill_color), (25, 25, 75, 75))
assert im.getbbox(alpha_only=False) == (25, 25, 75, 75)

assert im.getbbox() is None
4 changes: 2 additions & 2 deletions src/PIL/GifImagePlugin.py
Expand Up @@ -569,9 +569,9 @@ def _getbbox(base_im, im_frame):
delta = ImageChops.subtract_modulo(im_frame, base_im)
else:
delta = ImageChops.subtract_modulo(
im_frame.convert("RGB"), base_im.convert("RGB")
im_frame.convert("RGBA"), base_im.convert("RGBA")
)
return delta.getbbox()
return delta.getbbox(alpha_only=False)


def _write_multiple_frames(im, fp, palette):
Expand Down
8 changes: 6 additions & 2 deletions src/PIL/Image.py
Expand Up @@ -1292,11 +1292,15 @@ def getbands(self):
"""
return ImageMode.getmode(self.mode).bands

def getbbox(self):
def getbbox(self, *, alpha_only=True):
"""
Calculates the bounding box of the non-zero regions in the
image.
:param alpha_only: Optional flag, defaulting to ``True``.
If ``True`` and the image has an alpha channel, trim transparent pixels.
Otherwise, trim pixels when all channels are zero.
Keyword-only argument.
:returns: The bounding box is returned as a 4-tuple defining the
left, upper, right, and lower pixel coordinate. See
:ref:`coordinate-system`. If the image is completely empty, this
Expand All @@ -1305,7 +1309,7 @@ def getbbox(self):
"""

self.load()
return self.im.getbbox()
return self.im.getbbox(alpha_only)

def getcolors(self, maxcolors=256):
"""
Expand Down
4 changes: 2 additions & 2 deletions src/PIL/PngImagePlugin.py
Expand Up @@ -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.getbbox(alpha_only=False)
if (
not bbox
and prev_disposal == encoderinfo.get("disposal")
Expand Down
12 changes: 9 additions & 3 deletions src/_imaging.c
Expand Up @@ -2159,9 +2159,15 @@ _isblock(ImagingObject *self) {
}

static PyObject *
_getbbox(ImagingObject *self) {
_getbbox(ImagingObject *self, PyObject *args) {
int bbox[4];
if (!ImagingGetBBox(self->image, bbox)) {

int alpha_only = 1;
if (!PyArg_ParseTuple(args, "|i", &alpha_only)) {
return NULL;
}

if (!ImagingGetBBox(self->image, bbox, alpha_only)) {
Py_INCREF(Py_None);
return Py_None;
}
Expand Down Expand Up @@ -3573,7 +3579,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},
Expand Down
7 changes: 4 additions & 3 deletions src/libImaging/GetBBox.c
Expand Up @@ -19,7 +19,7 @@
#include "Imaging.h"

int
ImagingGetBBox(Imaging im, int bbox[4]) {
ImagingGetBBox(Imaging im, int bbox[4], int alpha_only) {
/* Get the bounding box for any non-zero data in the image.*/

int x, y;
Expand Down Expand Up @@ -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 (alpha_only && (
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
Expand Down
2 changes: 1 addition & 1 deletion src/libImaging/Imaging.h
Expand Up @@ -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 alpha_only);
typedef struct {
int x, y;
INT32 count;
Expand Down

0 comments on commit be4bfaa

Please sign in to comment.