Merge pull request #7123 from radarhere/apng

This commit is contained in:
Hugo van Kemenade 2023-06-30 09:24:20 +03:00 committed by GitHub
commit be4bfaac9e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 65 additions and 13 deletions

View File

@ -374,6 +374,20 @@ def test_apng_save(tmp_path):
assert im.getpixel((64, 32)) == (0, 255, 0, 255) 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): def test_apng_save_split_fdat(tmp_path):
# test to make sure we do not generate sequence errors when writing # test to make sure we do not generate sequence errors when writing
# frames with image data spanning multiple fdAT chunks (in this case # frames with image data spanning multiple fdAT chunks (in this case

View File

@ -1130,6 +1130,18 @@ def test_bbox(tmp_path):
assert reread.n_frames == 2 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): def test_palette_save_L(tmp_path):
# Generate an L mode image with a separate palette # Generate an L mode image with a separate palette

View File

@ -1,3 +1,5 @@
import pytest
from PIL import Image from PIL import Image
from .helper import hopper from .helper import hopper
@ -38,3 +40,16 @@ def test_bbox():
for color in ((0, 0), (127, 0), (255, 0)): for color in ((0, 0), (127, 0), (255, 0)):
im = Image.new(mode, (100, 100), color) im = Image.new(mode, (100, 100), color)
check(im, (255, 255)) 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

View File

@ -569,9 +569,9 @@ def _getbbox(base_im, im_frame):
delta = ImageChops.subtract_modulo(im_frame, base_im) delta = ImageChops.subtract_modulo(im_frame, base_im)
else: else:
delta = ImageChops.subtract_modulo( 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): def _write_multiple_frames(im, fp, palette):

View File

@ -1292,11 +1292,15 @@ class Image:
""" """
return ImageMode.getmode(self.mode).bands 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 Calculates the bounding box of the non-zero regions in the
image. 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 :returns: The bounding box is returned as a 4-tuple defining the
left, upper, right, and lower pixel coordinate. See left, upper, right, and lower pixel coordinate. See
:ref:`coordinate-system`. If the image is completely empty, this :ref:`coordinate-system`. If the image is completely empty, this
@ -1305,7 +1309,7 @@ class Image:
""" """
self.load() self.load()
return self.im.getbbox() return self.im.getbbox(alpha_only)
def getcolors(self, maxcolors=256): def getcolors(self, maxcolors=256):
""" """

View File

@ -1138,9 +1138,9 @@ def _write_multiple_frames(im, fp, chunk, rawmode, default_image, append_images)
else: else:
base_im = previous["im"] base_im = previous["im"]
delta = ImageChops.subtract_modulo( 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 ( if (
not bbox not bbox
and prev_disposal == encoderinfo.get("disposal") and prev_disposal == encoderinfo.get("disposal")

View File

@ -2159,9 +2159,15 @@ _isblock(ImagingObject *self) {
} }
static PyObject * static PyObject *
_getbbox(ImagingObject *self) { _getbbox(ImagingObject *self, PyObject *args) {
int bbox[4]; 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); Py_INCREF(Py_None);
return Py_None; return Py_None;
} }
@ -3573,7 +3579,7 @@ static struct PyMethodDef methods[] = {
{"isblock", (PyCFunction)_isblock, METH_NOARGS}, {"isblock", (PyCFunction)_isblock, METH_NOARGS},
{"getbbox", (PyCFunction)_getbbox, METH_NOARGS}, {"getbbox", (PyCFunction)_getbbox, METH_VARARGS},
{"getcolors", (PyCFunction)_getcolors, METH_VARARGS}, {"getcolors", (PyCFunction)_getcolors, METH_VARARGS},
{"getextrema", (PyCFunction)_getextrema, METH_NOARGS}, {"getextrema", (PyCFunction)_getextrema, METH_NOARGS},
{"getprojection", (PyCFunction)_getprojection, METH_NOARGS}, {"getprojection", (PyCFunction)_getprojection, METH_NOARGS},

View File

@ -19,7 +19,7 @@
#include "Imaging.h" #include "Imaging.h"
int 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.*/ /* Get the bounding box for any non-zero data in the image.*/
int x, y; int x, y;
@ -58,10 +58,11 @@ ImagingGetBBox(Imaging im, int bbox[4]) {
INT32 mask = 0xffffffff; INT32 mask = 0xffffffff;
if (im->bands == 3) { if (im->bands == 3) {
((UINT8 *)&mask)[3] = 0; ((UINT8 *)&mask)[3] = 0;
} else if ( } else if (alpha_only && (
strcmp(im->mode, "RGBa") == 0 || strcmp(im->mode, "RGBA") == 0 || strcmp(im->mode, "RGBa") == 0 || strcmp(im->mode, "RGBA") == 0 ||
strcmp(im->mode, "La") == 0 || strcmp(im->mode, "LA") == 0 || strcmp(im->mode, "La") == 0 || strcmp(im->mode, "LA") == 0 ||
strcmp(im->mode, "PA") == 0) { strcmp(im->mode, "PA") == 0
)) {
#ifdef WORDS_BIGENDIAN #ifdef WORDS_BIGENDIAN
mask = 0x000000ff; mask = 0x000000ff;
#else #else

View File

@ -317,7 +317,7 @@ ImagingMerge(const char *mode, Imaging bands[4]);
extern int extern int
ImagingSplit(Imaging im, Imaging bands[4]); ImagingSplit(Imaging im, Imaging bands[4]);
extern int extern int
ImagingGetBBox(Imaging im, int bbox[4]); ImagingGetBBox(Imaging im, int bbox[4], int alpha_only);
typedef struct { typedef struct {
int x, y; int x, y;
INT32 count; INT32 count;