aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/Pillow/py3/PIL/GifImagePlugin.py
diff options
context:
space:
mode:
authorAlexSm <alex@ydb.tech>2024-01-26 16:00:50 +0100
committerGitHub <noreply@github.com>2024-01-26 16:00:50 +0100
commit7ebcfd058d924bcc8c23da70e034f7415687885c (patch)
treee4f00d163c77528c1855f2d7af54a8be83fc1ccb /contrib/python/Pillow/py3/PIL/GifImagePlugin.py
parent64ca2dcd06312b9eef624054ceb5f787e11be79a (diff)
parent6d79e7793c2c462134f4b4a7d911abc7b9b0766f (diff)
downloadydb-7ebcfd058d924bcc8c23da70e034f7415687885c.tar.gz
Merge pull request #1260 from ydb-platform/mergelibs10
mergelibs10
Diffstat (limited to 'contrib/python/Pillow/py3/PIL/GifImagePlugin.py')
-rw-r--r--contrib/python/Pillow/py3/PIL/GifImagePlugin.py171
1 files changed, 104 insertions, 67 deletions
diff --git a/contrib/python/Pillow/py3/PIL/GifImagePlugin.py b/contrib/python/Pillow/py3/PIL/GifImagePlugin.py
index 92074b0d49..57d87078bc 100644
--- a/contrib/python/Pillow/py3/PIL/GifImagePlugin.py
+++ b/contrib/python/Pillow/py3/PIL/GifImagePlugin.py
@@ -23,6 +23,7 @@
#
# See the README file for information on usage and redistribution.
#
+from __future__ import annotations
import itertools
import math
@@ -30,7 +31,15 @@ import os
import subprocess
from enum import IntEnum
-from . import Image, ImageChops, ImageFile, ImagePalette, ImageSequence
+from . import (
+ Image,
+ ImageChops,
+ ImageFile,
+ ImageMath,
+ ImageOps,
+ ImagePalette,
+ ImageSequence,
+)
from ._binary import i16le as i16
from ._binary import o8
from ._binary import o16le as o16
@@ -183,7 +192,8 @@ class GifImageFile(ImageFile.ImageFile):
s = self.fp.read(1)
if not s or s == b";":
- raise EOFError
+ msg = "no more images in GIF file"
+ raise EOFError(msg)
palette = None
@@ -280,15 +290,11 @@ class GifImageFile(ImageFile.ImageFile):
bits = self.fp.read(1)[0]
self.__offset = self.fp.tell()
break
-
- else:
- pass
- # raise OSError, "illegal GIF tag `%x`" % s[0]
s = None
if interlace is None:
- # self._fp = None
- raise EOFError
+ msg = "image not found in GIF frame"
+ raise EOFError(msg)
self.__frame = frame
if not update_image:
@@ -333,6 +339,8 @@ class GifImageFile(ImageFile.ImageFile):
def _rgb(color):
if self._frame_palette:
+ if color * 3 + 3 > len(self._frame_palette.palette):
+ color = 0
color = tuple(self._frame_palette.palette[color * 3 : color * 3 + 3])
else:
color = (color, color, color)
@@ -537,7 +545,15 @@ def _normalize_palette(im, palette, info):
else:
used_palette_colors = _get_optimize(im, info)
if used_palette_colors is not None:
- return im.remap_palette(used_palette_colors, source_palette)
+ im = im.remap_palette(used_palette_colors, source_palette)
+ if "transparency" in info:
+ try:
+ info["transparency"] = used_palette_colors.index(
+ info["transparency"]
+ )
+ except ValueError:
+ del info["transparency"]
+ return im
im.palette.palette = source_palette
return im
@@ -565,13 +581,11 @@ def _write_single_frame(im, fp, palette):
def _getbbox(base_im, im_frame):
- if _get_palette_bytes(im_frame) == _get_palette_bytes(base_im):
- delta = ImageChops.subtract_modulo(im_frame, base_im)
- else:
- delta = ImageChops.subtract_modulo(
- im_frame.convert("RGBA"), base_im.convert("RGBA")
- )
- return delta.getbbox(alpha_only=False)
+ if _get_palette_bytes(im_frame) != _get_palette_bytes(base_im):
+ im_frame = im_frame.convert("RGBA")
+ base_im = base_im.convert("RGBA")
+ delta = ImageChops.subtract_modulo(im_frame, base_im)
+ return delta, delta.getbbox(alpha_only=False)
def _write_multiple_frames(im, fp, palette):
@@ -579,6 +593,7 @@ def _write_multiple_frames(im, fp, palette):
disposal = im.encoderinfo.get("disposal", im.info.get("disposal"))
im_frames = []
+ previous_im = None
frame_count = 0
background_im = None
for imSequence in itertools.chain([im], im.encoderinfo.get("append_images", [])):
@@ -592,9 +607,9 @@ def _write_multiple_frames(im, fp, palette):
im.encoderinfo.setdefault(k, v)
encoderinfo = im.encoderinfo.copy()
- im_frame = _normalize_palette(im_frame, palette, encoderinfo)
if "transparency" in im_frame.info:
encoderinfo.setdefault("transparency", im_frame.info["transparency"])
+ im_frame = _normalize_palette(im_frame, palette, encoderinfo)
if isinstance(duration, (list, tuple)):
encoderinfo["duration"] = duration[frame_count]
elif duration is None and "duration" in im_frame.info:
@@ -603,14 +618,16 @@ def _write_multiple_frames(im, fp, palette):
encoderinfo["disposal"] = disposal[frame_count]
frame_count += 1
+ diff_frame = None
if im_frames:
# delta frame
- previous = im_frames[-1]
- bbox = _getbbox(previous["im"], im_frame)
+ delta, bbox = _getbbox(previous_im, im_frame)
if not bbox:
# This frame is identical to the previous frame
if encoderinfo.get("duration"):
- previous["encoderinfo"]["duration"] += encoderinfo["duration"]
+ im_frames[-1]["encoderinfo"]["duration"] += encoderinfo[
+ "duration"
+ ]
continue
if encoderinfo.get("disposal") == 2:
if background_im is None:
@@ -620,33 +637,67 @@ def _write_multiple_frames(im, fp, palette):
background = _get_background(im_frame, color)
background_im = Image.new("P", im_frame.size, background)
background_im.putpalette(im_frames[0]["im"].palette)
- bbox = _getbbox(background_im, im_frame)
+ delta, bbox = _getbbox(background_im, im_frame)
+ if encoderinfo.get("optimize") and im_frame.mode != "1":
+ if "transparency" not in encoderinfo:
+ try:
+ encoderinfo[
+ "transparency"
+ ] = im_frame.palette._new_color_index(im_frame)
+ except ValueError:
+ pass
+ if "transparency" in encoderinfo:
+ # When the delta is zero, fill the image with transparency
+ diff_frame = im_frame.copy()
+ fill = Image.new(
+ "P", diff_frame.size, encoderinfo["transparency"]
+ )
+ if delta.mode == "RGBA":
+ r, g, b, a = delta.split()
+ mask = ImageMath.eval(
+ "convert(max(max(max(r, g), b), a) * 255, '1')",
+ r=r,
+ g=g,
+ b=b,
+ a=a,
+ )
+ else:
+ if delta.mode == "P":
+ # Convert to L without considering palette
+ delta_l = Image.new("L", delta.size)
+ delta_l.putdata(delta.getdata())
+ delta = delta_l
+ mask = ImageMath.eval("convert(im * 255, '1')", im=delta)
+ diff_frame.paste(fill, mask=ImageOps.invert(mask))
else:
bbox = None
- im_frames.append({"im": im_frame, "bbox": bbox, "encoderinfo": encoderinfo})
-
- if len(im_frames) > 1:
- for frame_data in im_frames:
- im_frame = frame_data["im"]
- if not frame_data["bbox"]:
- # global header
- for s in _get_global_header(im_frame, frame_data["encoderinfo"]):
- fp.write(s)
- offset = (0, 0)
- else:
- # compress difference
- if not palette:
- frame_data["encoderinfo"]["include_color_table"] = True
-
- im_frame = im_frame.crop(frame_data["bbox"])
- offset = frame_data["bbox"][:2]
- _write_frame_data(fp, im_frame, offset, frame_data["encoderinfo"])
- return True
- elif "duration" in im.encoderinfo and isinstance(
- im.encoderinfo["duration"], (list, tuple)
- ):
- # Since multiple frames will not be written, add together the frame durations
- im.encoderinfo["duration"] = sum(im.encoderinfo["duration"])
+ previous_im = im_frame
+ im_frames.append(
+ {"im": diff_frame or im_frame, "bbox": bbox, "encoderinfo": encoderinfo}
+ )
+
+ if len(im_frames) == 1:
+ if "duration" in im.encoderinfo:
+ # Since multiple frames will not be written, use the combined duration
+ im.encoderinfo["duration"] = im_frames[0]["encoderinfo"]["duration"]
+ return
+
+ for frame_data in im_frames:
+ im_frame = frame_data["im"]
+ if not frame_data["bbox"]:
+ # global header
+ for s in _get_global_header(im_frame, frame_data["encoderinfo"]):
+ fp.write(s)
+ offset = (0, 0)
+ else:
+ # compress difference
+ if not palette:
+ frame_data["encoderinfo"]["include_color_table"] = True
+
+ im_frame = im_frame.crop(frame_data["bbox"])
+ offset = frame_data["bbox"][:2]
+ _write_frame_data(fp, im_frame, offset, frame_data["encoderinfo"])
+ return True
def _save_all(im, fp, filename):
@@ -659,7 +710,7 @@ def _save(im, fp, filename, save_all=False):
palette = im.encoderinfo.get("palette", im.info.get("palette"))
else:
palette = None
- im.encoderinfo["optimize"] = im.encoderinfo.get("optimize", True)
+ im.encoderinfo.setdefault("optimize", True)
if not save_all or not _write_multiple_frames(im, fp, palette):
_write_single_frame(im, fp, palette)
@@ -681,22 +732,10 @@ def get_interlace(im):
def _write_local_header(fp, im, offset, flags):
- transparent_color_exists = False
try:
- transparency = int(im.encoderinfo["transparency"])
- except (KeyError, ValueError):
- pass
- else:
- # optimize the block away if transparent color is not used
- transparent_color_exists = True
-
- used_palette_colors = _get_optimize(im, im.encoderinfo)
- if used_palette_colors is not None:
- # adjust the transparency index after optimize
- try:
- transparency = used_palette_colors.index(transparency)
- except ValueError:
- transparent_color_exists = False
+ transparency = im.encoderinfo["transparency"]
+ except KeyError:
+ transparency = None
if "duration" in im.encoderinfo:
duration = int(im.encoderinfo["duration"] / 10)
@@ -705,11 +744,9 @@ def _write_local_header(fp, im, offset, flags):
disposal = int(im.encoderinfo.get("disposal", 0))
- if transparent_color_exists or duration != 0 or disposal:
- packed_flag = 1 if transparent_color_exists else 0
+ if transparency is not None or duration != 0 or disposal:
+ packed_flag = 1 if transparency is not None else 0
packed_flag |= disposal << 2
- if not transparent_color_exists:
- transparency = 0
fp.write(
b"!"
@@ -717,7 +754,7 @@ def _write_local_header(fp, im, offset, flags):
+ o8(4) # length
+ o8(packed_flag) # packed fields
+ o16(duration) # duration
- + o8(transparency) # transparency index
+ + o8(transparency or 0) # transparency index
+ o8(0)
)
@@ -805,7 +842,7 @@ def _get_optimize(im, info):
:param info: encoderinfo
:returns: list of indexes of palette entries in use, or None
"""
- if im.mode in ("P", "L") and info and info.get("optimize", 0):
+ if im.mode in ("P", "L") and info and info.get("optimize"):
# Potentially expensive operation.
# The palette saves 3 bytes per color not used, but palette