Skip to content

Commit

Permalink
Reverted converting L with transparency to LA after first frame
Browse files Browse the repository at this point in the history
  • Loading branch information
radarhere committed Mar 22, 2022
1 parent ce8c682 commit c5efe60
Show file tree
Hide file tree
Showing 4 changed files with 35 additions and 42 deletions.
Binary file modified Tests/images/no_palette_with_transparency.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
43 changes: 19 additions & 24 deletions Tests/test_file_gif.py
Expand Up @@ -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():
Expand Down Expand Up @@ -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():
Expand Down
3 changes: 1 addition & 2 deletions docs/handbook/image-file-formats.rst
Expand Up @@ -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
Expand Down
31 changes: 15 additions & 16 deletions src/PIL/GifImagePlugin.py
Expand Up @@ -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
Expand All @@ -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()
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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 = [
(
Expand All @@ -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)
Expand All @@ -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
Expand All @@ -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)
Expand Down

0 comments on commit c5efe60

Please sign in to comment.