diff --git a/Tests/images/no_palette_with_transparency.gif b/Tests/images/no_palette_with_transparency.gif index 3cd1c0c48eb..031bdcfce10 100644 Binary files a/Tests/images/no_palette_with_transparency.gif and b/Tests/images/no_palette_with_transparency.gif differ diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index df6142ec7f4..54cc4524990 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -59,23 +59,15 @@ def test_invalid_file(): GifImagePlugin.GifImageFile(invalid_file) -def test_l_mode_subsequent_frames(): - with Image.open("Tests/images/no_palette.gif") as im: - assert im.mode == "L" - assert im.load()[0, 0] == 0 - - im.seek(1) - assert im.mode == "L" - assert im.load()[0, 0] == 0 - +def test_l_mode_transparency(): with Image.open("Tests/images/no_palette_with_transparency.gif") as im: assert im.mode == "L" - assert im.load()[0, 0] == 0 + assert im.load()[0, 0] == 128 assert im.info["transparency"] == 255 im.seek(1) - assert im.mode == "LA" - assert im.load()[0, 0] == (0, 255) + assert im.mode == "L" + assert im.load()[0, 0] == 128 def test_strategy(): @@ -440,31 +432,34 @@ def test_dispose_background_transparency(): "mode_strategy, expected_colors", ( ( - GifImagePlugin.ModeStrategy.DIFFERENT_PALETTE_ONLY, + GifImagePlugin.ModeStrategy.AFTER_FIRST, ( (2, 1, 2), - (0, 1, 0), - (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)), ), ), ( - GifImagePlugin.ModeStrategy.AFTER_FIRST, + GifImagePlugin.ModeStrategy.DIFFERENT_PALETTE_ONLY, ( (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)), + (0, 1, 0), + (2, 1, 2), ), ), ), ) def test_transparent_dispose(mode_strategy, expected_colors): GifImagePlugin.PALETTE_TO_RGB = mode_strategy - with Image.open("Tests/images/transparent_dispose.gif") as img: - for frame in range(3): - img.seek(frame) - for x in range(3): - color = img.getpixel((x, 0)) - assert color == expected_colors[frame][x] + try: + with Image.open("Tests/images/transparent_dispose.gif") as img: + for frame in range(3): + img.seek(frame) + for x in range(3): + color = img.getpixel((x, 0)) + assert color == expected_colors[frame][x] + finally: + GifImagePlugin.PALETTE_TO_RGB = GifImagePlugin.ModeStrategy.AFTER_FIRST def test_dispose_previous(): diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index 6dffe834e92..6309562501e 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -107,8 +107,7 @@ are used or GIF89a is already in use. GIF files are initially read as grayscale (``L``) or palette mode (``P``) images. Seeking to later frames in a ``P`` image will change the image to -``RGB`` (or ``RGBA`` if the first frame had transparency). ``L`` images will -stay in ``L`` mode (or change to ``LA`` if the first frame had transparency). +``RGB`` (or ``RGBA`` if the first frame had transparency). ``P`` mode images are changed to ``RGB`` because each frame of a GIF may introduce up to 256 colors. Because ``P`` can only have up to 256 colors, the diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index d22d2896640..1d59edf091a 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -71,6 +71,12 @@ def data(self): return self.fp.read(s[0]) return None + def _is_palette_needed(self, p): + for i in range(0, len(p), 3): + if not (i // 3 == p[i] == p[i + 1] == p[i + 2]): + return True + return False + def _open(self): # Screen @@ -89,11 +95,9 @@ def _open(self): self.info["background"] = s[11] # check if palette contains colour indices p = self.fp.read(3 << bits) - for i in range(0, len(p), 3): - if not (i // 3 == p[i] == p[i + 1] == p[i + 2]): - p = ImagePalette.raw("RGB", p) - self.global_palette = self.palette = p - break + if self._is_palette_needed(p): + p = ImagePalette.raw("RGB", p) + self.global_palette = self.palette = p self.__fp = self.fp # FIXME: hack self.__rewind = self.fp.tell() @@ -259,7 +263,9 @@ def _seek(self, frame, update_image=True): if flags & 128: bits = (flags & 7) + 1 - palette = ImagePalette.raw("RGB", self.fp.read(3 << bits)) + p = self.fp.read(3 << bits) + if self._is_palette_needed(p): + palette = ImagePalette.raw("RGB", p) # image data bits = self.fp.read(1)[0] @@ -305,11 +311,6 @@ def _seek(self, frame, update_image=True): else: self.mode = "RGB" self.im = self.im.convert("RGB", Image.Dither.FLOYDSTEINBERG) - elif self.mode == "L" and "transparency" in self.info: - self.pyaccess = None - self.im = self.im.convert_transparent("LA", self.info["transparency"]) - self.mode = "LA" - del self.info["transparency"] def _rgb(color): if self._frame_palette: @@ -369,7 +370,7 @@ def _rgb(color): if frame_transparency is not None: if frame == 0: self.info["transparency"] = frame_transparency - elif self.mode not in ("RGB", "RGBA", "LA"): + elif self.mode not in ("RGB", "RGBA"): transparency = frame_transparency self.tile = [ ( @@ -394,7 +395,7 @@ def load_prepare(self): self.im = Image.core.fill( temp_mode, self.size, self.info["transparency"] ) - elif self.mode in ("RGB", "RGBA", "LA"): + elif self.mode in ("RGB", "RGBA"): self._prev_im = self.im if self._frame_palette: self.im = Image.core.fill("P", self.size, self._frame_transparency or 0) @@ -418,8 +419,6 @@ def load_end(self): frame_im = self.im.convert("RGBA") else: frame_im = self.im.convert("RGB") - elif self.mode == "L" and self._frame_transparency is not None: - frame_im = self.im.convert_transparent("LA", self._frame_transparency) else: if not self._prev_im: return @@ -428,7 +427,7 @@ def load_end(self): self.im = self._prev_im self.mode = self.im.mode - if frame_im.mode in ("LA", "RGBA"): + if frame_im.mode == "RGBA": self.im.paste(frame_im, self.dispose_extent, frame_im) else: self.im.paste(frame_im, self.dispose_extent)