diff --git a/Tests/images/different_transparency_merged.gif b/Tests/images/different_transparency_merged.gif deleted file mode 100644 index 94d0f53e0..000000000 Binary files a/Tests/images/different_transparency_merged.gif and /dev/null differ diff --git a/Tests/images/different_transparency_merged.png b/Tests/images/different_transparency_merged.png new file mode 100644 index 000000000..3438f62a6 Binary files /dev/null and b/Tests/images/different_transparency_merged.png differ diff --git a/Tests/images/dispose_none_load_end_second.gif b/Tests/images/dispose_none_load_end_second.gif deleted file mode 100644 index 5d8462ceb..000000000 Binary files a/Tests/images/dispose_none_load_end_second.gif and /dev/null differ diff --git a/Tests/images/dispose_none_load_end_second.png b/Tests/images/dispose_none_load_end_second.png new file mode 100644 index 000000000..dc01ccbdd Binary files /dev/null and b/Tests/images/dispose_none_load_end_second.png differ diff --git a/Tests/images/dispose_prev_first_frame_seeked.gif b/Tests/images/dispose_prev_first_frame_seeked.gif deleted file mode 100644 index bc3eb1393..000000000 Binary files a/Tests/images/dispose_prev_first_frame_seeked.gif and /dev/null differ diff --git a/Tests/images/dispose_prev_first_frame_seeked.png b/Tests/images/dispose_prev_first_frame_seeked.png new file mode 100644 index 000000000..85a3753e1 Binary files /dev/null and b/Tests/images/dispose_prev_first_frame_seeked.png differ diff --git a/Tests/images/missing_background_first_frame.gif b/Tests/images/missing_background_first_frame.gif deleted file mode 100644 index be2c95b99..000000000 Binary files a/Tests/images/missing_background_first_frame.gif and /dev/null differ diff --git a/Tests/images/missing_background_first_frame.png b/Tests/images/missing_background_first_frame.png new file mode 100644 index 000000000..25237ba5d Binary files /dev/null and b/Tests/images/missing_background_first_frame.png differ diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index fae7e912c..497cc5ed3 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -324,7 +324,7 @@ def test_dispose_none_load_end(): with Image.open("Tests/images/dispose_none_load_end.gif") as img: img.seek(1) - assert_image_equal_tofile(img, "Tests/images/dispose_none_load_end_second.gif") + assert_image_equal_tofile(img, "Tests/images/dispose_none_load_end_second.png") def test_dispose_background(): @@ -340,12 +340,16 @@ def test_dispose_background(): def test_dispose_background_transparency(): with Image.open("Tests/images/dispose_bgnd_transparency.gif") as img: img.seek(2) - px = img.convert("RGBA").load() + px = img.load() assert px[35, 30][3] == 0 def test_transparent_dispose(): - expected_colors = [(2, 1, 2), (0, 1, 0), (2, 1, 2)] + expected_colors = [ + (2, 1, 2), + ((0, 255, 24, 255), (0, 0, 255, 255), (0, 255, 24, 255)), + ((0, 0, 0, 0), (0, 0, 255, 255), (0, 0, 0, 0)), + ] with Image.open("Tests/images/transparent_dispose.gif") as img: for frame in range(3): img.seek(frame) @@ -368,7 +372,7 @@ def test_dispose_previous_first_frame(): with Image.open("Tests/images/dispose_prev_first_frame.gif") as im: im.seek(1) assert_image_equal_tofile( - im, "Tests/images/dispose_prev_first_frame_seeked.gif" + im, "Tests/images/dispose_prev_first_frame_seeked.png" ) @@ -508,7 +512,7 @@ def test_dispose2_background(tmp_path): with Image.open(out) as im: im.seek(1) - assert im.getpixel((0, 0)) == 0 + assert im.getpixel((0, 0)) == (255, 0, 0) def test_transparency_in_second_frame(): @@ -517,9 +521,9 @@ def test_transparency_in_second_frame(): # Seek to the second frame im.seek(im.tell() + 1) - assert im.info["transparency"] == 0 + assert "transparency" not in im.info - assert_image_equal_tofile(im, "Tests/images/different_transparency_merged.gif") + assert_image_equal_tofile(im, "Tests/images/different_transparency_merged.png") def test_no_transparency_in_second_frame(): @@ -926,4 +930,4 @@ def test_missing_background(): # but the disposal method is "Restore to background color" with Image.open("Tests/images/missing_background.gif") as im: im.seek(1) - assert_image_equal_tofile(im, "Tests/images/missing_background_first_frame.gif") + assert_image_equal_tofile(im, "Tests/images/missing_background_first_frame.png") diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index 5b4668844..44d15b596 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -124,8 +124,7 @@ class GifImageFile(ImageFile.ImageFile): if not self._seek_check(frame): return if frame < self.__frame: - if frame != 0: - self.im = None + self.im = None self._seek(0) last_frame = self.__frame @@ -165,12 +164,21 @@ class GifImageFile(ImageFile.ImageFile): pass self.__offset = 0 + if self.__frame == 1: + self.pyaccess = None + if "transparency" in self.info: + self.mode = "RGBA" + self.im.putpalettealpha(self.info["transparency"], 0) + self.im = self.im.convert("RGBA", Image.FLOYDSTEINBERG) + + del self.info["transparency"] + else: + self.mode = "RGB" + self.im = self.im.convert("RGB", Image.FLOYDSTEINBERG) if self.dispose: self.im.paste(self.dispose, self.dispose_extent) - from copy import copy - - self.palette = copy(self.global_palette) + palette = None info = {} frame_transparency = None @@ -246,7 +254,7 @@ class GifImageFile(ImageFile.ImageFile): if flags & 128: bits = (flags & 7) + 1 - self.palette = ImagePalette.raw("RGB", self.fp.read(3 << bits)) + palette = ImagePalette.raw("RGB", self.fp.read(3 << bits)) # image data bits = self.fp.read(1)[0] @@ -257,6 +265,15 @@ class GifImageFile(ImageFile.ImageFile): pass # raise OSError, "illegal GIF tag `%x`" % s[0] + frame_palette = palette or self.global_palette + + def _rgb(color): + if frame_palette: + color = tuple(frame_palette.palette[color * 3 : color * 3 + 3]) + else: + color = (color, color, color) + return color + try: if self.disposal_method < 2: # do not dispose or none specified @@ -272,9 +289,13 @@ class GifImageFile(ImageFile.ImageFile): # by convention, attempt to use transparency first color = self.info.get("transparency", frame_transparency) - if color is None: - color = self.info.get("background", 0) - self.dispose = Image.core.fill("P", dispose_size, color) + if color is not None: + dispose_mode = "RGBA" + color = _rgb(color) + (0,) + else: + dispose_mode = "RGB" + color = _rgb(self.info.get("background", 0)) + self.dispose = Image.core.fill(dispose_mode, dispose_size, color) else: # replace with previous contents if self.im: @@ -286,7 +307,7 @@ class GifImageFile(ImageFile.ImageFile): Image._decompression_bomb_check(dispose_size) self.dispose = Image.core.fill( - "P", dispose_size, frame_transparency + "RGBA", dispose_size, _rgb(frame_transparency) + (0,) ) except AttributeError: pass @@ -316,16 +337,54 @@ class GifImageFile(ImageFile.ImageFile): elif k in self.info: del self.info[k] - self.mode = "L" - if self.palette: - self.mode = "P" + if frame == 0: + self.mode = "P" if frame_palette else "L" + + if self.mode == "P" and not palette: + from copy import copy + + palette = copy(self.global_palette) + self.palette = palette + else: + self._frame_palette = frame_palette + self._frame_transparency = frame_transparency def load_prepare(self): - if not self.im and "transparency" in self.info: - self.im = Image.core.fill(self.mode, self.size, self.info["transparency"]) + if self.__frame == 0: + if "transparency" in self.info: + self.im = Image.core.fill( + self.mode, self.size, self.info["transparency"] + ) + else: + self._prev_im = self.im + if self._frame_palette: + self.mode = "P" + self.im = Image.core.fill("P", self.size, self._frame_transparency or 0) + self.im.putpalette(*self._frame_palette.getdata()) + self._frame_palette = None + else: + self.mode = "L" + self.im = None super().load_prepare() + def load_end(self): + if self.__frame == 0: + return + if self._frame_transparency is not None: + self.im.putpalettealpha(self._frame_transparency, 0) + frame_im = self.im.convert("RGBA") + else: + frame_im = self.im.convert("RGB") + frame_im = self._crop(frame_im, self.dispose_extent) + + self.im = self._prev_im + self.mode = self.im.mode + if frame_im.mode == "RGBA": + self.im.paste(frame_im, self.dispose_extent, frame_im) + else: + self.im.paste(frame_im, self.dispose_extent) + def tell(self): return self.__frame diff --git a/src/PIL/PdfImagePlugin.py b/src/PIL/PdfImagePlugin.py index 49ba077e6..1131c6325 100644 --- a/src/PIL/PdfImagePlugin.py +++ b/src/PIL/PdfImagePlugin.py @@ -135,7 +135,7 @@ def _save(im, fp, filename, save_all=False): procset = "ImageB" # grayscale elif im.mode == "P": filter = "ASCIIHexDecode" - palette = im.im.getpalette("RGB") + palette = im.getpalette() colorspace = [ PdfParser.PdfName("Indexed"), PdfParser.PdfName("DeviceRGB"),