Skip to content

Commit

Permalink
Merge pull request #6150 from radarhere/gif
Browse files Browse the repository at this point in the history
  • Loading branch information
hugovk committed Mar 30, 2022
2 parents 43faf9c + 24054d8 commit e60ca89
Show file tree
Hide file tree
Showing 9 changed files with 244 additions and 97 deletions.
Binary file added Tests/images/no_palette.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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.
84 changes: 69 additions & 15 deletions Tests/test_file_gif.py
Expand Up @@ -62,12 +62,46 @@ def test_invalid_file():
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():
with Image.open("Tests/images/chi.gif") as im:
expected_zero = im.convert("RGB")

im.seek(1)
expected_one = im.convert("RGB")

try:
GifImagePlugin.LOADING_STRATEGY = GifImagePlugin.LoadingStrategy.RGB_ALWAYS
with Image.open("Tests/images/chi.gif") as im:
assert im.mode == "RGB"
assert_image_equal(im, expected_zero)

GifImagePlugin.LOADING_STRATEGY = (
GifImagePlugin.LoadingStrategy.RGB_AFTER_DIFFERENT_PALETTE_ONLY
)
# Stay in P mode with only a global palette
with Image.open("Tests/images/chi.gif") as im:
assert im.mode == "P"

im.seek(1)
assert im.mode == "P"
assert_image_equal(im.convert("RGB"), expected_one)

# Change to RGB mode when a frame has an individual palette
with Image.open("Tests/images/iss634.gif") as im:
assert im.mode == "P"

im.seek(1)
assert im.mode == "RGB"
finally:
GifImagePlugin.LOADING_STRATEGY = GifImagePlugin.LoadingStrategy.RGB_AFTER_FIRST


def test_optimize():
Expand Down Expand Up @@ -394,18 +428,38 @@ def test_dispose_background_transparency():
assert px[35, 30][3] == 0


def test_transparent_dispose():
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)
for x in range(3):
color = img.getpixel((x, 0))
assert color == expected_colors[frame][x]
@pytest.mark.parametrize(
"loading_strategy, expected_colors",
(
(
GifImagePlugin.LoadingStrategy.RGB_AFTER_FIRST,
(
(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.LoadingStrategy.RGB_AFTER_DIFFERENT_PALETTE_ONLY,
(
(2, 1, 2),
(0, 1, 0),
(2, 1, 2),
),
),
),
)
def test_transparent_dispose(loading_strategy, expected_colors):
GifImagePlugin.LOADING_STRATEGY = loading_strategy
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.LOADING_STRATEGY = GifImagePlugin.LoadingStrategy.RGB_AFTER_FIRST


def test_dispose_previous():
Expand Down
30 changes: 28 additions & 2 deletions docs/handbook/image-file-formats.rst
Expand Up @@ -107,8 +107,34 @@ writes run-length encoded files in GIF87a by default, unless GIF89a features
are used or GIF89a is already in use.

GIF files are initially read as grayscale (``L``) or palette mode (``P``)
images, but seeking to later frames in an image will change the mode to either
``RGB`` or ``RGBA``, depending on whether the first frame had transparency.
images. Seeking to later frames in a ``P`` image will change the image to
``RGB`` (or ``RGBA`` if the first frame had transparency).

``P`` mode images are changed to ``RGB`` because each frame of a GIF may contain
its own individual palette of up to 256 colors. When a new frame is placed onto a
previous frame, those colors may combine to exceed the ``P`` mode limit of 256
colors. Instead, the image is converted to ``RGB`` handle this.

If you would prefer the first ``P`` image frame to be ``RGB`` as well, so that
every ``P`` frame is converted to ``RGB`` or ``RGBA`` mode, there is a setting
available::

from PIL import GifImagePlugin
GifImagePlugin.LOADING_STRATEGY = GifImagePlugin.LoadingStrategy.RGB_ALWAYS

GIF frames do not always contain individual palettes however. If there is only
a global palette, then all of the colors can fit within ``P`` mode. If you would
prefer the frames to be kept as ``P`` in that case, there is also a setting
available::

from PIL import GifImagePlugin
GifImagePlugin.LOADING_STRATEGY = GifImagePlugin.LoadingStrategy.RGB_AFTER_DIFFERENT_PALETTE_ONLY

To restore the default behavior, where ``P`` mode images are only converted to
``RGB`` or ``RGBA`` after the first frame::

from PIL import GifImagePlugin
GifImagePlugin.LOADING_STRATEGY = GifImagePlugin.LoadingStrategy.RGB_AFTER_FIRST

The :py:meth:`~PIL.Image.open` method sets the following
:py:attr:`~PIL.Image.Image.info` properties:
Expand Down
20 changes: 20 additions & 0 deletions docs/releasenotes/9.1.0.rst
Expand Up @@ -160,6 +160,26 @@ Added PyEncoder
written in Python. See :ref:`Writing Your Own File Codec in Python<file-codecs-py>` for
more information.

GifImagePlugin loading strategy
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Pillow 9.0.0 introduced the conversion of subsequent GIF frames to ``RGB`` or ``RGBA``. This
behaviour can now be changed so that the first ``P`` frame is converted to ``RGB`` as
well.

.. code-block:: python
from PIL import GifImagePlugin
GifImagePlugin.LOADING_STRATEGY = GifImagePlugin.LoadingStrategy.RGB_ALWAYS
Or subsequent frames can be kept in ``P`` mode as long as there is only a single
palette.

.. code-block:: python
from PIL import GifImagePlugin
GifImagePlugin.LOADING_STRATEGY = GifImagePlugin.LoadingStrategy.RGB_AFTER_DIFFERENT_PALETTE_ONLY
Other Changes
=============

Expand Down

0 comments on commit e60ca89

Please sign in to comment.