Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added alpha_only argument to getbbox() #7123

Merged
merged 10 commits into from Jun 30, 2023
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 @@ -1293,11 +1293,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 @@ -1306,7 +1310,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