mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-07-04 03:43:13 +03:00
Merge pull request #6150 from radarhere/gif
This commit is contained in:
commit
e60ca89721
BIN
Tests/images/no_palette.gif
Normal file
BIN
Tests/images/no_palette.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 48 B |
Binary file not shown.
Before Width: | Height: | Size: 64 B After Width: | Height: | Size: 1.6 KiB |
|
@ -62,12 +62,46 @@ def test_invalid_file():
|
||||||
def test_l_mode_transparency():
|
def test_l_mode_transparency():
|
||||||
with Image.open("Tests/images/no_palette_with_transparency.gif") as im:
|
with Image.open("Tests/images/no_palette_with_transparency.gif") as im:
|
||||||
assert im.mode == "L"
|
assert im.mode == "L"
|
||||||
assert im.load()[0, 0] == 0
|
assert im.load()[0, 0] == 128
|
||||||
assert im.info["transparency"] == 255
|
assert im.info["transparency"] == 255
|
||||||
|
|
||||||
im.seek(1)
|
im.seek(1)
|
||||||
assert im.mode == "LA"
|
assert im.mode == "L"
|
||||||
assert im.load()[0, 0] == (0, 255)
|
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():
|
def test_optimize():
|
||||||
|
@ -394,18 +428,38 @@ def test_dispose_background_transparency():
|
||||||
assert px[35, 30][3] == 0
|
assert px[35, 30][3] == 0
|
||||||
|
|
||||||
|
|
||||||
def test_transparent_dispose():
|
@pytest.mark.parametrize(
|
||||||
expected_colors = [
|
"loading_strategy, 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)),
|
GifImagePlugin.LoadingStrategy.RGB_AFTER_FIRST,
|
||||||
]
|
(
|
||||||
with Image.open("Tests/images/transparent_dispose.gif") as img:
|
(2, 1, 2),
|
||||||
for frame in range(3):
|
((0, 255, 24, 255), (0, 0, 255, 255), (0, 255, 24, 255)),
|
||||||
img.seek(frame)
|
((0, 0, 0, 0), (0, 0, 255, 255), (0, 0, 0, 0)),
|
||||||
for x in range(3):
|
),
|
||||||
color = img.getpixel((x, 0))
|
),
|
||||||
assert color == expected_colors[frame][x]
|
(
|
||||||
|
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():
|
def test_dispose_previous():
|
||||||
|
|
|
@ -107,8 +107,34 @@ writes run-length encoded files in GIF87a by default, unless GIF89a features
|
||||||
are used or GIF89a is already in use.
|
are used or GIF89a is already in use.
|
||||||
|
|
||||||
GIF files are initially read as grayscale (``L``) or palette mode (``P``)
|
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
|
images. Seeking to later frames in a ``P`` image will change the image to
|
||||||
``RGB`` or ``RGBA``, depending on whether 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 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
|
The :py:meth:`~PIL.Image.open` method sets the following
|
||||||
:py:attr:`~PIL.Image.Image.info` properties:
|
:py:attr:`~PIL.Image.Image.info` properties:
|
||||||
|
|
|
@ -160,6 +160,26 @@ Added PyEncoder
|
||||||
written in Python. See :ref:`Writing Your Own File Codec in Python<file-codecs-py>` for
|
written in Python. See :ref:`Writing Your Own File Codec in Python<file-codecs-py>` for
|
||||||
more information.
|
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
|
Other Changes
|
||||||
=============
|
=============
|
||||||
|
|
||||||
|
|
|
@ -28,12 +28,25 @@ import itertools
|
||||||
import math
|
import math
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
|
from enum import IntEnum
|
||||||
|
|
||||||
from . import Image, ImageChops, ImageFile, ImagePalette, ImageSequence
|
from . import Image, ImageChops, ImageFile, ImagePalette, ImageSequence
|
||||||
from ._binary import i16le as i16
|
from ._binary import i16le as i16
|
||||||
from ._binary import o8
|
from ._binary import o8
|
||||||
from ._binary import o16le as o16
|
from ._binary import o16le as o16
|
||||||
|
|
||||||
|
|
||||||
|
class LoadingStrategy(IntEnum):
|
||||||
|
""".. versionadded:: 9.1.0"""
|
||||||
|
|
||||||
|
RGB_AFTER_FIRST = 0
|
||||||
|
RGB_AFTER_DIFFERENT_PALETTE_ONLY = 1
|
||||||
|
RGB_ALWAYS = 2
|
||||||
|
|
||||||
|
|
||||||
|
#: .. versionadded:: 9.1.0
|
||||||
|
LOADING_STRATEGY = LoadingStrategy.RGB_AFTER_FIRST
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# Identify/read GIF files
|
# Identify/read GIF files
|
||||||
|
|
||||||
|
@ -61,6 +74,12 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
return self.fp.read(s[0])
|
return self.fp.read(s[0])
|
||||||
return None
|
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):
|
def _open(self):
|
||||||
|
|
||||||
# Screen
|
# Screen
|
||||||
|
@ -79,11 +98,9 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
self.info["background"] = s[11]
|
self.info["background"] = s[11]
|
||||||
# check if palette contains colour indices
|
# check if palette contains colour indices
|
||||||
p = self.fp.read(3 << bits)
|
p = self.fp.read(3 << bits)
|
||||||
for i in range(0, len(p), 3):
|
if self._is_palette_needed(p):
|
||||||
if not (i // 3 == p[i] == p[i + 1] == p[i + 2]):
|
p = ImagePalette.raw("RGB", p)
|
||||||
p = ImagePalette.raw("RGB", p)
|
self.global_palette = self.palette = p
|
||||||
self.global_palette = self.palette = p
|
|
||||||
break
|
|
||||||
|
|
||||||
self.__fp = self.fp # FIXME: hack
|
self.__fp = self.fp # FIXME: hack
|
||||||
self.__rewind = self.fp.tell()
|
self.__rewind = self.fp.tell()
|
||||||
|
@ -143,7 +160,6 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
# rewind
|
# rewind
|
||||||
self.__offset = 0
|
self.__offset = 0
|
||||||
self.dispose = None
|
self.dispose = None
|
||||||
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.disposal_method = 0
|
self.disposal_method = 0
|
||||||
|
@ -171,32 +187,12 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
self.tile = []
|
self.tile = []
|
||||||
|
|
||||||
if update_image:
|
|
||||||
if self.__frame == 1:
|
|
||||||
self.pyaccess = None
|
|
||||||
if "transparency" in self.info:
|
|
||||||
if self.mode == "P":
|
|
||||||
self.im.putpalettealpha(self.info["transparency"], 0)
|
|
||||||
self.im = self.im.convert("RGBA", Image.Dither.FLOYDSTEINBERG)
|
|
||||||
self.mode = "RGBA"
|
|
||||||
else:
|
|
||||||
self.im = self.im.convert_transparent(
|
|
||||||
"LA", self.info["transparency"]
|
|
||||||
)
|
|
||||||
self.mode = "LA"
|
|
||||||
|
|
||||||
del self.info["transparency"]
|
|
||||||
else:
|
|
||||||
self.mode = "RGB"
|
|
||||||
self.im = self.im.convert("RGB", Image.Dither.FLOYDSTEINBERG)
|
|
||||||
if self.dispose:
|
|
||||||
self.im.paste(self.dispose, self.dispose_extent)
|
|
||||||
|
|
||||||
palette = None
|
palette = None
|
||||||
|
|
||||||
info = {}
|
info = {}
|
||||||
frame_transparency = None
|
frame_transparency = None
|
||||||
interlace = None
|
interlace = None
|
||||||
|
frame_dispose_extent = None
|
||||||
while True:
|
while True:
|
||||||
|
|
||||||
if not s:
|
if not s:
|
||||||
|
@ -263,14 +259,16 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
x1, y1 = x0 + i16(s, 4), y0 + i16(s, 6)
|
x1, y1 = x0 + i16(s, 4), y0 + i16(s, 6)
|
||||||
if (x1 > self.size[0] or y1 > self.size[1]) and update_image:
|
if (x1 > self.size[0] or y1 > self.size[1]) and update_image:
|
||||||
self._size = max(x1, self.size[0]), max(y1, self.size[1])
|
self._size = max(x1, self.size[0]), max(y1, self.size[1])
|
||||||
self.dispose_extent = x0, y0, x1, y1
|
frame_dispose_extent = x0, y0, x1, y1
|
||||||
flags = s[8]
|
flags = s[8]
|
||||||
|
|
||||||
interlace = (flags & 64) != 0
|
interlace = (flags & 64) != 0
|
||||||
|
|
||||||
if flags & 128:
|
if flags & 128:
|
||||||
bits = (flags & 7) + 1
|
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
|
# image data
|
||||||
bits = self.fp.read(1)[0]
|
bits = self.fp.read(1)[0]
|
||||||
|
@ -288,15 +286,48 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
if not update_image:
|
if not update_image:
|
||||||
return
|
return
|
||||||
|
|
||||||
frame_palette = palette or self.global_palette
|
if self.dispose:
|
||||||
|
self.im.paste(self.dispose, self.dispose_extent)
|
||||||
|
|
||||||
|
self._frame_palette = palette or self.global_palette
|
||||||
|
if frame == 0:
|
||||||
|
if self._frame_palette:
|
||||||
|
self.mode = (
|
||||||
|
"RGB" if LOADING_STRATEGY == LoadingStrategy.RGB_ALWAYS else "P"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.mode = "L"
|
||||||
|
|
||||||
|
if not palette and self.global_palette:
|
||||||
|
from copy import copy
|
||||||
|
|
||||||
|
palette = copy(self.global_palette)
|
||||||
|
self.palette = palette
|
||||||
|
else:
|
||||||
|
self._frame_transparency = frame_transparency
|
||||||
|
if self.mode == "P":
|
||||||
|
if (
|
||||||
|
LOADING_STRATEGY != LoadingStrategy.RGB_AFTER_DIFFERENT_PALETTE_ONLY
|
||||||
|
or palette
|
||||||
|
):
|
||||||
|
self.pyaccess = None
|
||||||
|
if "transparency" in self.info:
|
||||||
|
self.im.putpalettealpha(self.info["transparency"], 0)
|
||||||
|
self.im = self.im.convert("RGBA", Image.Dither.FLOYDSTEINBERG)
|
||||||
|
self.mode = "RGBA"
|
||||||
|
del self.info["transparency"]
|
||||||
|
else:
|
||||||
|
self.mode = "RGB"
|
||||||
|
self.im = self.im.convert("RGB", Image.Dither.FLOYDSTEINBERG)
|
||||||
|
|
||||||
def _rgb(color):
|
def _rgb(color):
|
||||||
if frame_palette:
|
if self._frame_palette:
|
||||||
color = tuple(frame_palette.palette[color * 3 : color * 3 + 3])
|
color = tuple(self._frame_palette.palette[color * 3 : color * 3 + 3])
|
||||||
else:
|
else:
|
||||||
color = (color, color, color)
|
color = (color, color, color)
|
||||||
return color
|
return color
|
||||||
|
|
||||||
|
self.dispose_extent = frame_dispose_extent
|
||||||
try:
|
try:
|
||||||
if self.disposal_method < 2:
|
if self.disposal_method < 2:
|
||||||
# do not dispose or none specified
|
# do not dispose or none specified
|
||||||
|
@ -311,13 +342,17 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
Image._decompression_bomb_check(dispose_size)
|
Image._decompression_bomb_check(dispose_size)
|
||||||
|
|
||||||
# by convention, attempt to use transparency first
|
# by convention, attempt to use transparency first
|
||||||
|
dispose_mode = "P"
|
||||||
color = self.info.get("transparency", frame_transparency)
|
color = self.info.get("transparency", frame_transparency)
|
||||||
if color is not None:
|
if color is not None:
|
||||||
dispose_mode = "RGBA"
|
if self.mode in ("RGB", "RGBA"):
|
||||||
color = _rgb(color) + (0,)
|
dispose_mode = "RGBA"
|
||||||
|
color = _rgb(color) + (0,)
|
||||||
else:
|
else:
|
||||||
dispose_mode = "RGB"
|
color = self.info.get("background", 0)
|
||||||
color = _rgb(self.info.get("background", 0))
|
if self.mode in ("RGB", "RGBA"):
|
||||||
|
dispose_mode = "RGB"
|
||||||
|
color = _rgb(color)
|
||||||
self.dispose = Image.core.fill(dispose_mode, dispose_size, color)
|
self.dispose = Image.core.fill(dispose_mode, dispose_size, color)
|
||||||
else:
|
else:
|
||||||
# replace with previous contents
|
# replace with previous contents
|
||||||
|
@ -329,21 +364,28 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
dispose_size = (x1 - x0, y1 - y0)
|
dispose_size = (x1 - x0, y1 - y0)
|
||||||
|
|
||||||
Image._decompression_bomb_check(dispose_size)
|
Image._decompression_bomb_check(dispose_size)
|
||||||
self.dispose = Image.core.fill(
|
dispose_mode = "P"
|
||||||
"RGBA", dispose_size, _rgb(frame_transparency) + (0,)
|
color = frame_transparency
|
||||||
)
|
if self.mode in ("RGB", "RGBA"):
|
||||||
|
dispose_mode = "RGBA"
|
||||||
|
color = _rgb(frame_transparency) + (0,)
|
||||||
|
self.dispose = Image.core.fill(dispose_mode, dispose_size, color)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if interlace is not None:
|
if interlace is not None:
|
||||||
if frame == 0 and frame_transparency is not None:
|
transparency = -1
|
||||||
self.info["transparency"] = frame_transparency
|
if frame_transparency is not None:
|
||||||
|
if frame == 0:
|
||||||
|
self.info["transparency"] = frame_transparency
|
||||||
|
elif self.mode not in ("RGB", "RGBA"):
|
||||||
|
transparency = frame_transparency
|
||||||
self.tile = [
|
self.tile = [
|
||||||
(
|
(
|
||||||
"gif",
|
"gif",
|
||||||
(x0, y0, x1, y1),
|
(x0, y0, x1, y1),
|
||||||
self.__offset,
|
self.__offset,
|
||||||
(bits, interlace),
|
(bits, interlace, transparency),
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -353,53 +395,47 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
elif k in self.info:
|
elif k in self.info:
|
||||||
del self.info[k]
|
del self.info[k]
|
||||||
|
|
||||||
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):
|
def load_prepare(self):
|
||||||
|
temp_mode = "P" if self._frame_palette else "L"
|
||||||
|
self._prev_im = None
|
||||||
if self.__frame == 0:
|
if self.__frame == 0:
|
||||||
if "transparency" in self.info:
|
if "transparency" in self.info:
|
||||||
self.im = Image.core.fill(
|
self.im = Image.core.fill(
|
||||||
self.mode, self.size, self.info["transparency"]
|
temp_mode, self.size, self.info["transparency"]
|
||||||
)
|
)
|
||||||
else:
|
elif self.mode in ("RGB", "RGBA"):
|
||||||
self._prev_im = self.im
|
self._prev_im = self.im
|
||||||
if self._frame_palette:
|
if self._frame_palette:
|
||||||
self.mode = "P"
|
|
||||||
self.im = Image.core.fill("P", self.size, self._frame_transparency or 0)
|
self.im = Image.core.fill("P", self.size, self._frame_transparency or 0)
|
||||||
self.im.putpalette(*self._frame_palette.getdata())
|
self.im.putpalette(*self._frame_palette.getdata())
|
||||||
self._frame_palette = None
|
|
||||||
else:
|
else:
|
||||||
self.mode = "L"
|
|
||||||
self.im = None
|
self.im = None
|
||||||
|
self.mode = temp_mode
|
||||||
|
self._frame_palette = None
|
||||||
|
|
||||||
super().load_prepare()
|
super().load_prepare()
|
||||||
|
|
||||||
def load_end(self):
|
def load_end(self):
|
||||||
if self.__frame == 0:
|
if self.__frame == 0:
|
||||||
|
if self.mode == "P" and LOADING_STRATEGY == LoadingStrategy.RGB_ALWAYS:
|
||||||
|
self.mode = "RGB"
|
||||||
|
self.im = self.im.convert("RGB", Image.Dither.FLOYDSTEINBERG)
|
||||||
return
|
return
|
||||||
if self._frame_transparency is not None:
|
if self.mode == "P" and self._prev_im:
|
||||||
if self.mode == "P":
|
if self._frame_transparency is not None:
|
||||||
self.im.putpalettealpha(self._frame_transparency, 0)
|
self.im.putpalettealpha(self._frame_transparency, 0)
|
||||||
frame_im = self.im.convert("RGBA")
|
frame_im = self.im.convert("RGBA")
|
||||||
else:
|
else:
|
||||||
frame_im = self.im.convert_transparent("LA", self._frame_transparency)
|
frame_im = self.im.convert("RGB")
|
||||||
else:
|
else:
|
||||||
frame_im = self.im.convert("RGB")
|
if not self._prev_im:
|
||||||
|
return
|
||||||
|
frame_im = self.im
|
||||||
frame_im = self._crop(frame_im, self.dispose_extent)
|
frame_im = self._crop(frame_im, self.dispose_extent)
|
||||||
|
|
||||||
self.im = self._prev_im
|
self.im = self._prev_im
|
||||||
self.mode = self.im.mode
|
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)
|
self.im.paste(frame_im, self.dispose_extent, frame_im)
|
||||||
else:
|
else:
|
||||||
self.im.paste(frame_im, self.dispose_extent)
|
self.im.paste(frame_im, self.dispose_extent)
|
||||||
|
|
|
@ -433,7 +433,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -451,6 +452,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,27 +248,33 @@ 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. */
|
||||||
|
|
||||||
if (i == 1) {
|
/* This cannot be used if there is transparency */
|
||||||
if (state->x < state->xsize - 1) {
|
if (context->transparency == -1) {
|
||||||
/* Single pixel, not at the end of the line. */
|
if (i == 1) {
|
||||||
*out++ = p[0];
|
if (state->x < state->xsize - 1) {
|
||||||
state->x++;
|
/* Single pixel, not at the end of the line. */
|
||||||
|
*out++ = p[0];
|
||||||
|
state->x++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else if (state->x + i <= state->xsize) {
|
||||||
|
/* This string fits into current line. */
|
||||||
|
memcpy(out, p, i);
|
||||||
|
out += i;
|
||||||
|
state->x += i;
|
||||||
|
if (state->x == state->xsize) {
|
||||||
|
NEWLINE(state, context);
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
} else if (state->x + i <= state->xsize) {
|
|
||||||
/* This string fits into current line. */
|
|
||||||
memcpy(out, p, i);
|
|
||||||
out += i;
|
|
||||||
state->x += i;
|
|
||||||
if (state->x == state->xsize) {
|
|
||||||
NEWLINE(state, context);
|
|
||||||
}
|
|
||||||
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