Merge pull request #5333 from radarhere/gif_frame_transparency

This commit is contained in:
Hugo van Kemenade 2021-03-31 18:08:11 +03:00 committed by GitHub
commit c54a7bb031
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 64 additions and 43 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@ -468,12 +468,25 @@ def test_dispose2_background(tmp_path):
assert im.getpixel((0, 0)) == 0 assert im.getpixel((0, 0)) == 0
def test_iss634(): def test_transparency_in_second_frame():
with Image.open("Tests/images/different_transparency.gif") as im:
assert im.info["transparency"] == 0
# Seek to the second frame
im.seek(im.tell() + 1)
assert im.info["transparency"] == 0
assert_image_equal_tofile(im, "Tests/images/different_transparency_merged.gif")
def test_no_transparency_in_second_frame():
with Image.open("Tests/images/iss634.gif") as img: with Image.open("Tests/images/iss634.gif") as img:
# Seek to the second frame # Seek to the second frame
img.seek(img.tell() + 1) img.seek(img.tell() + 1)
assert "transparency" not in img.info
# All transparent pixels should be replaced with the color from the first frame # All transparent pixels should be replaced with the color from the first frame
assert img.histogram()[img.info["transparency"]] == 0 assert img.histogram()[255] == 0
def test_duration(tmp_path): def test_duration(tmp_path):

View File

@ -45,12 +45,12 @@ def test_write_animation_L(tmp_path):
# Compare first and last frames to the original animated GIF # Compare first and last frames to the original animated GIF
orig.load() orig.load()
im.load() im.load()
assert_image_similar(im, orig.convert("RGBA"), 25.0) assert_image_similar(im, orig.convert("RGBA"), 32.9)
orig.seek(orig.n_frames - 1) orig.seek(orig.n_frames - 1)
im.seek(im.n_frames - 1) im.seek(im.n_frames - 1)
orig.load() orig.load()
im.load() im.load()
assert_image_similar(im, orig.convert("RGBA"), 25.0) assert_image_similar(im, orig.convert("RGBA"), 32.9)
@pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian") @pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian")

View File

@ -145,7 +145,6 @@ class GifImageFile(ImageFile.ImageFile):
self.dispose_extent = [0, 0, 0, 0] # x0, y0, x1, y1 self.dispose_extent = [0, 0, 0, 0] # x0, y0, x1, y1
self.__frame = -1 self.__frame = -1
self.__fp.seek(self.__rewind) self.__fp.seek(self.__rewind)
self._prev_im = None
self.disposal_method = 0 self.disposal_method = 0
else: else:
# ensure that the previous frame was loaded # ensure that the previous frame was loaded
@ -174,6 +173,8 @@ class GifImageFile(ImageFile.ImageFile):
self.palette = copy(self.global_palette) self.palette = copy(self.global_palette)
info = {} info = {}
frame_transparency = None
interlace = None
while True: while True:
s = self.fp.read(1) s = self.fp.read(1)
@ -192,7 +193,7 @@ class GifImageFile(ImageFile.ImageFile):
# #
flags = block[0] flags = block[0]
if flags & 1: if flags & 1:
info["transparency"] = block[3] frame_transparency = block[3]
info["duration"] = i16(block, 1) * 10 info["duration"] = i16(block, 1) * 10
# disposal method - find the value of bits 4 - 6 # disposal method - find the value of bits 4 - 6
@ -250,9 +251,6 @@ class GifImageFile(ImageFile.ImageFile):
# image data # image data
bits = self.fp.read(1)[0] bits = self.fp.read(1)[0]
self.__offset = self.fp.tell() self.__offset = self.fp.tell()
self.tile = [
("gif", (x0, y0, x1, y1), self.__offset, (bits, interlace))
]
break break
else: else:
@ -282,11 +280,26 @@ class GifImageFile(ImageFile.ImageFile):
except (AttributeError, KeyError): except (AttributeError, KeyError):
pass pass
if not self.tile: if interlace is not None:
transparency = -1
if frame_transparency is not None:
if frame == 0:
self.info["transparency"] = frame_transparency
else:
transparency = frame_transparency
self.tile = [
(
"gif",
(x0, y0, x1, y1),
self.__offset,
(bits, interlace, transparency),
)
]
else:
# self.__fp = None # self.__fp = None
raise EOFError raise EOFError
for k in ["transparency", "duration", "comment", "extension", "loop"]: for k in ["duration", "comment", "extension", "loop"]:
if k in info: if k in info:
self.info[k] = info[k] self.info[k] = info[k]
elif k in self.info: elif k in self.info:
@ -299,20 +312,6 @@ class GifImageFile(ImageFile.ImageFile):
def tell(self): def tell(self):
return self.__frame return self.__frame
def load_end(self):
ImageFile.ImageFile.load_end(self)
# if the disposal method is 'do not dispose', transparent
# pixels should show the content of the previous frame
if self._prev_im and self._prev_disposal_method == 1:
# we do this by pasting the updated area onto the previous
# frame which we then use as the current image content
updated = self._crop(self.im, self.dispose_extent)
self._prev_im.paste(updated, self.dispose_extent, updated.convert("RGBA"))
self.im = self._prev_im
self._prev_im = self.im.copy()
self._prev_disposal_method = self.disposal_method
def _close__fp(self): def _close__fp(self):
try: try:
if self.__fp != self.fp: if self.__fp != self.fp:

View File

@ -430,7 +430,8 @@ PyImaging_GifDecoderNew(PyObject *self, PyObject *args) {
char *mode; char *mode;
int bits = 8; int bits = 8;
int interlace = 0; int interlace = 0;
if (!PyArg_ParseTuple(args, "s|ii", &mode, &bits, &interlace)) { int transparency = -1;
if (!PyArg_ParseTuple(args, "s|iii", &mode, &bits, &interlace, &transparency)) {
return NULL; return NULL;
} }
@ -448,6 +449,7 @@ PyImaging_GifDecoderNew(PyObject *self, PyObject *args) {
((GIFDECODERSTATE *)decoder->state.context)->bits = bits; ((GIFDECODERSTATE *)decoder->state.context)->bits = bits;
((GIFDECODERSTATE *)decoder->state.context)->interlace = interlace; ((GIFDECODERSTATE *)decoder->state.context)->interlace = interlace;
((GIFDECODERSTATE *)decoder->state.context)->transparency = transparency;
return (PyObject *)decoder; return (PyObject *)decoder;
} }

View File

@ -30,6 +30,9 @@ typedef struct {
*/ */
int interlace; int interlace;
/* The transparent palette index, or -1 for no transparency. */
int transparency;
/* PRIVATE CONTEXT (set by decoder) */ /* PRIVATE CONTEXT (set by decoder) */
/* Interlace parameters */ /* Interlace parameters */

View File

@ -248,8 +248,8 @@ ImagingGifDecode(Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t
/* To squeeze some extra pixels out of this loop, we test for /* To squeeze some extra pixels out of this loop, we test for
some common cases and handle them separately. */ some common cases and handle them separately. */
/* FIXME: should we handle the transparency index in here??? */ /* If we have transparency, we need to use the regular loop. */
if (context->transparency == -1) {
if (i == 1) { if (i == 1) {
if (state->x < state->xsize - 1) { if (state->x < state->xsize - 1) {
/* Single pixel, not at the end of the line. */ /* Single pixel, not at the end of the line. */
@ -267,10 +267,14 @@ ImagingGifDecode(Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t
} }
continue; continue;
} }
}
/* No shortcut, copy pixel by pixel */ /* No shortcut, copy pixel by pixel */
for (c = 0; c < i; c++) { for (c = 0; c < i; c++) {
*out++ = p[c]; if (p[c] != context->transparency) {
*out = p[c];
}
out++;
if (++state->x >= state->xsize) { if (++state->x >= state->xsize) {
NEWLINE(state, context); NEWLINE(state, context);
} }