Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GIF seek performance improvements #6077

Merged
merged 4 commits into from
Mar 11, 2022
Merged
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 42 additions & 28 deletions src/PIL/GifImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ def n_frames(self):
current = self.tell()
try:
while True:
self.seek(self.tell() + 1)
self._seek(self.tell() + 1, False)
except EOFError:
self._n_frames = self.tell() + 1
self.seek(current)
Expand All @@ -110,14 +110,16 @@ def is_animated(self):
self._is_animated = self._n_frames != 1
else:
current = self.tell()

try:
self.seek(1)
if current:
self._is_animated = True
except EOFError:
self._is_animated = False
else:
try:
self._seek(1, False)
self._is_animated = True
except EOFError:
self._is_animated = False

self.seek(current)
self.seek(current)
return self._is_animated

def seek(self, frame):
Expand All @@ -135,7 +137,7 @@ def seek(self, frame):
self.seek(last_frame)
raise EOFError("no more images in GIF file") from e

def _seek(self, frame):
def _seek(self, frame, update_image=True):

if frame == 0:
# rewind
Expand All @@ -147,14 +149,11 @@ def _seek(self, frame):
self.disposal_method = 0
else:
# ensure that the previous frame was loaded
if self.tile:
if self.tile and update_image:
self.load()

if frame != self.__frame + 1:
raise ValueError(f"cannot seek to frame {frame}")
self.__frame = frame

self.tile = []

self.fp = self.__fp
if self.__offset:
Expand All @@ -164,19 +163,28 @@ def _seek(self, frame):
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.Dither.FLOYDSTEINBERG)
s = self.fp.read(1)
if not s or s == b";":
raise EOFError

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)
self.__frame = frame

self.tile = []

if update_image:
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.Dither.FLOYDSTEINBERG)

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

Expand All @@ -185,7 +193,8 @@ def _seek(self, frame):
interlace = None
while True:

s = self.fp.read(1)
if not s:
s = self.fp.read(1)
if not s or s == b";":
break

Expand Down Expand Up @@ -223,6 +232,7 @@ def _seek(self, frame):
else:
info["comment"] = block
block = self.data()
s = None
continue
elif s[0] == 255:
#
Expand Down Expand Up @@ -264,6 +274,13 @@ def _seek(self, frame):
else:
pass
# raise OSError, "illegal GIF tag `%x`" % s[0]
s = None

if interlace is None:
# self.__fp = None
raise EOFError
if not update_image:
return

frame_palette = palette or self.global_palette

Expand Down Expand Up @@ -323,9 +340,6 @@ def _rgb(color):
(bits, interlace),
)
]
else:
# self.__fp = None
raise EOFError

for k in ["duration", "comment", "extension", "loop"]:
if k in info:
Expand Down