From 5cf02f816f102bcce0c76e0e57fd582bd113228e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 15 Apr 2022 16:46:33 +1000 Subject: [PATCH 1/2] Moved Netscape extension after global color table when saving --- src/PIL/GifImagePlugin.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index c91c1fbffb0..1b317d23350 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -716,18 +716,6 @@ def _write_local_header(fp, im, offset, flags): + o8(0) ) - if "loop" in im.encoderinfo: - number_of_loops = im.encoderinfo["loop"] - fp.write( - b"!" - + o8(255) # extension intro - + o8(11) - + b"NETSCAPE2.0" - + o8(3) - + o8(1) - + o16(number_of_loops) # number of loops - + o8(0) - ) include_color_table = im.encoderinfo.get("include_color_table") if include_color_table: palette_bytes = _get_palette_bytes(im) @@ -933,6 +921,17 @@ def _get_global_header(im, info): # Global Color Table _get_header_palette(palette_bytes), ] + if "loop" in info: + header.append( + b"!" + + o8(255) # extension intro + + o8(11) + + b"NETSCAPE2.0" + + o8(3) + + o8(1) + + o16(info["loop"]) # number of loops + + o8(0) + ) if info.get("comment"): comment_block = b"!" + o8(254) # extension intro From 2457eafabdcb617628a4bb9bee9cafda332636a3 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 15 Apr 2022 16:44:23 +1000 Subject: [PATCH 2/2] Only read the number of loops from the first frame --- Tests/images/duplicate_number_of_loops.gif | Bin 0 -> 1622 bytes Tests/test_file_gif.py | 9 ++++++++- src/PIL/GifImagePlugin.py | 6 +++--- 3 files changed, 11 insertions(+), 4 deletions(-) create mode 100644 Tests/images/duplicate_number_of_loops.gif diff --git a/Tests/images/duplicate_number_of_loops.gif b/Tests/images/duplicate_number_of_loops.gif new file mode 100644 index 0000000000000000000000000000000000000000..ac315ee99f312dec957aaa08cc3aa065acad4993 GIT binary patch literal 1622 zcmZ?wbhEHbWMp7uXlED&qaiS&LqPFAx1VcBu(M-;tC5}oGb0lNgAOP_K-q(VgN1>S ag@plK4KtFO_WvU~_(nZH(nA26N2~$y{0yQ1 literal 0 HcmV?d00001 diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index c4f634faea2..dcdf2179afa 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -772,9 +772,16 @@ def test_number_of_loops(tmp_path): im = Image.new("L", (100, 100), "#000") im.save(out, loop=number_of_loops) with Image.open(out) as reread: - assert reread.info["loop"] == number_of_loops + # Check that even if a subsequent GIF frame has the number of loops specified, + # only the value from the first frame is used + with Image.open("Tests/images/duplicate_number_of_loops.gif") as im: + assert im.info["loop"] == 2 + + im.seek(1) + assert im.info["loop"] == 2 + def test_background(tmp_path): out = str(tmp_path / "temp.gif") diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index 1b317d23350..33b968a8f3f 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -244,7 +244,7 @@ def _seek(self, frame, update_image=True): info["comment"] = comment s = None continue - elif s[0] == 255: + elif s[0] == 255 and frame == 0: # # application extension # @@ -252,7 +252,7 @@ def _seek(self, frame, update_image=True): if block[:11] == b"NETSCAPE2.0": block = self.data() if len(block) >= 3 and block[0] == 1: - info["loop"] = i16(block, 1) + self.info["loop"] = i16(block, 1) while self.data(): pass @@ -399,7 +399,7 @@ def _rgb(color): if info.get("comment"): self.info["comment"] = info["comment"] - for k in ["duration", "extension", "loop"]: + for k in ["duration", "extension"]: if k in info: self.info[k] = info[k] elif k in self.info: