mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-01-25 00:34:14 +03:00
Merge pull request #5333 from radarhere/gif_frame_transparency
This commit is contained in:
commit
c54a7bb031
BIN
Tests/images/different_transparency.gif
Normal file
BIN
Tests/images/different_transparency.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.0 KiB |
BIN
Tests/images/different_transparency_merged.gif
Normal file
BIN
Tests/images/different_transparency_merged.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.0 KiB |
|
@ -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):
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 */
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user