mirror of
				https://github.com/python-pillow/Pillow.git
				synced 2025-11-04 01:47:47 +03:00 
			
		
		
		
	Added type hints
This commit is contained in:
		
							parent
							
								
									9a8759d91b
								
							
						
					
					
						commit
						9afe9d2769
					
				| 
						 | 
					@ -53,6 +53,7 @@ def test_closed_file() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_seek_after_close() -> None:
 | 
					def test_seek_after_close() -> None:
 | 
				
			||||||
    im = Image.open("Tests/images/iss634.gif")
 | 
					    im = Image.open("Tests/images/iss634.gif")
 | 
				
			||||||
 | 
					    assert isinstance(im, GifImagePlugin.GifImageFile)
 | 
				
			||||||
    im.load()
 | 
					    im.load()
 | 
				
			||||||
    im.close()
 | 
					    im.close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -377,7 +378,8 @@ def test_save_netpbm_bmp_mode(tmp_path: Path) -> None:
 | 
				
			||||||
        img = img.convert("RGB")
 | 
					        img = img.convert("RGB")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        tempfile = str(tmp_path / "temp.gif")
 | 
					        tempfile = str(tmp_path / "temp.gif")
 | 
				
			||||||
        GifImagePlugin._save_netpbm(img, 0, tempfile)
 | 
					        b = BytesIO()
 | 
				
			||||||
 | 
					        GifImagePlugin._save_netpbm(img, b, tempfile)
 | 
				
			||||||
        with Image.open(tempfile) as reloaded:
 | 
					        with Image.open(tempfile) as reloaded:
 | 
				
			||||||
            assert_image_similar(img, reloaded.convert("RGB"), 0)
 | 
					            assert_image_similar(img, reloaded.convert("RGB"), 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -388,7 +390,8 @@ def test_save_netpbm_l_mode(tmp_path: Path) -> None:
 | 
				
			||||||
        img = img.convert("L")
 | 
					        img = img.convert("L")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        tempfile = str(tmp_path / "temp.gif")
 | 
					        tempfile = str(tmp_path / "temp.gif")
 | 
				
			||||||
        GifImagePlugin._save_netpbm(img, 0, tempfile)
 | 
					        b = BytesIO()
 | 
				
			||||||
 | 
					        GifImagePlugin._save_netpbm(img, b, tempfile)
 | 
				
			||||||
        with Image.open(tempfile) as reloaded:
 | 
					        with Image.open(tempfile) as reloaded:
 | 
				
			||||||
            assert_image_similar(img, reloaded.convert("L"), 0)
 | 
					            assert_image_similar(img, reloaded.convert("L"), 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -648,7 +651,7 @@ def test_dispose2_palette(tmp_path: Path) -> None:
 | 
				
			||||||
            assert rgb_img.getpixel((50, 50)) == circle
 | 
					            assert rgb_img.getpixel((50, 50)) == circle
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # Check that frame transparency wasn't added unnecessarily
 | 
					            # Check that frame transparency wasn't added unnecessarily
 | 
				
			||||||
            assert img._frame_transparency is None
 | 
					            assert getattr(img, "_frame_transparency") is None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_dispose2_diff(tmp_path: Path) -> None:
 | 
					def test_dispose2_diff(tmp_path: Path) -> None:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -29,9 +29,10 @@ import itertools
 | 
				
			||||||
import math
 | 
					import math
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
import subprocess
 | 
					import subprocess
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
from enum import IntEnum
 | 
					from enum import IntEnum
 | 
				
			||||||
from functools import cached_property
 | 
					from functools import cached_property
 | 
				
			||||||
from typing import IO
 | 
					from typing import IO, TYPE_CHECKING, Any, List, Literal, NamedTuple, Union
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from . import (
 | 
					from . import (
 | 
				
			||||||
    Image,
 | 
					    Image,
 | 
				
			||||||
| 
						 | 
					@ -46,6 +47,9 @@ 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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if TYPE_CHECKING:
 | 
				
			||||||
 | 
					    from . import _imaging
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class LoadingStrategy(IntEnum):
 | 
					class LoadingStrategy(IntEnum):
 | 
				
			||||||
    """.. versionadded:: 9.1.0"""
 | 
					    """.. versionadded:: 9.1.0"""
 | 
				
			||||||
| 
						 | 
					@ -118,7 +122,7 @@ class GifImageFile(ImageFile.ImageFile):
 | 
				
			||||||
        self._seek(0)  # get ready to read first frame
 | 
					        self._seek(0)  # get ready to read first frame
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def n_frames(self):
 | 
					    def n_frames(self) -> int:
 | 
				
			||||||
        if self._n_frames is None:
 | 
					        if self._n_frames is None:
 | 
				
			||||||
            current = self.tell()
 | 
					            current = self.tell()
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
| 
						 | 
					@ -163,11 +167,11 @@ class GifImageFile(ImageFile.ImageFile):
 | 
				
			||||||
                msg = "no more images in GIF file"
 | 
					                msg = "no more images in GIF file"
 | 
				
			||||||
                raise EOFError(msg) from e
 | 
					                raise EOFError(msg) from e
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _seek(self, frame, update_image=True):
 | 
					    def _seek(self, frame: int, update_image: bool = True) -> None:
 | 
				
			||||||
        if frame == 0:
 | 
					        if frame == 0:
 | 
				
			||||||
            # rewind
 | 
					            # rewind
 | 
				
			||||||
            self.__offset = 0
 | 
					            self.__offset = 0
 | 
				
			||||||
            self.dispose = None
 | 
					            self.dispose: _imaging.ImagingCore | None = None
 | 
				
			||||||
            self.__frame = -1
 | 
					            self.__frame = -1
 | 
				
			||||||
            self._fp.seek(self.__rewind)
 | 
					            self._fp.seek(self.__rewind)
 | 
				
			||||||
            self.disposal_method = 0
 | 
					            self.disposal_method = 0
 | 
				
			||||||
| 
						 | 
					@ -195,9 +199,9 @@ class GifImageFile(ImageFile.ImageFile):
 | 
				
			||||||
            msg = "no more images in GIF file"
 | 
					            msg = "no more images in GIF file"
 | 
				
			||||||
            raise EOFError(msg)
 | 
					            raise EOFError(msg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        palette = None
 | 
					        palette: ImagePalette.ImagePalette | Literal[False] | None = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        info = {}
 | 
					        info: dict[str, Any] = {}
 | 
				
			||||||
        frame_transparency = None
 | 
					        frame_transparency = None
 | 
				
			||||||
        interlace = None
 | 
					        interlace = None
 | 
				
			||||||
        frame_dispose_extent = None
 | 
					        frame_dispose_extent = None
 | 
				
			||||||
| 
						 | 
					@ -213,7 +217,7 @@ class GifImageFile(ImageFile.ImageFile):
 | 
				
			||||||
                #
 | 
					                #
 | 
				
			||||||
                s = self.fp.read(1)
 | 
					                s = self.fp.read(1)
 | 
				
			||||||
                block = self.data()
 | 
					                block = self.data()
 | 
				
			||||||
                if s[0] == 249:
 | 
					                if s[0] == 249 and block is not None:
 | 
				
			||||||
                    #
 | 
					                    #
 | 
				
			||||||
                    # graphic control extension
 | 
					                    # graphic control extension
 | 
				
			||||||
                    #
 | 
					                    #
 | 
				
			||||||
| 
						 | 
					@ -249,14 +253,14 @@ class GifImageFile(ImageFile.ImageFile):
 | 
				
			||||||
                        info["comment"] = comment
 | 
					                        info["comment"] = comment
 | 
				
			||||||
                    s = None
 | 
					                    s = None
 | 
				
			||||||
                    continue
 | 
					                    continue
 | 
				
			||||||
                elif s[0] == 255 and frame == 0:
 | 
					                elif s[0] == 255 and frame == 0 and block is not None:
 | 
				
			||||||
                    #
 | 
					                    #
 | 
				
			||||||
                    # application extension
 | 
					                    # application extension
 | 
				
			||||||
                    #
 | 
					                    #
 | 
				
			||||||
                    info["extension"] = block, self.fp.tell()
 | 
					                    info["extension"] = block, self.fp.tell()
 | 
				
			||||||
                    if block[:11] == b"NETSCAPE2.0":
 | 
					                    if block[:11] == b"NETSCAPE2.0":
 | 
				
			||||||
                        block = self.data()
 | 
					                        block = self.data()
 | 
				
			||||||
                        if len(block) >= 3 and block[0] == 1:
 | 
					                        if block and len(block) >= 3 and block[0] == 1:
 | 
				
			||||||
                            self.info["loop"] = i16(block, 1)
 | 
					                            self.info["loop"] = i16(block, 1)
 | 
				
			||||||
                while self.data():
 | 
					                while self.data():
 | 
				
			||||||
                    pass
 | 
					                    pass
 | 
				
			||||||
| 
						 | 
					@ -345,51 +349,52 @@ class GifImageFile(ImageFile.ImageFile):
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                return (color, color, color)
 | 
					                return (color, color, color)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.dispose = None
 | 
				
			||||||
        self.dispose_extent = frame_dispose_extent
 | 
					        self.dispose_extent = frame_dispose_extent
 | 
				
			||||||
        try:
 | 
					        if self.dispose_extent and self.disposal_method >= 2:
 | 
				
			||||||
            if self.disposal_method < 2:
 | 
					            try:
 | 
				
			||||||
                # do not dispose or none specified
 | 
					                if self.disposal_method == 2:
 | 
				
			||||||
                self.dispose = None
 | 
					                    # replace with background colour
 | 
				
			||||||
            elif self.disposal_method == 2:
 | 
					 | 
				
			||||||
                # replace with background colour
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                # only dispose the extent in this frame
 | 
					 | 
				
			||||||
                x0, y0, x1, y1 = self.dispose_extent
 | 
					 | 
				
			||||||
                dispose_size = (x1 - x0, y1 - y0)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                Image._decompression_bomb_check(dispose_size)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                # by convention, attempt to use transparency first
 | 
					 | 
				
			||||||
                dispose_mode = "P"
 | 
					 | 
				
			||||||
                color = self.info.get("transparency", frame_transparency)
 | 
					 | 
				
			||||||
                if color is not None:
 | 
					 | 
				
			||||||
                    if self.mode in ("RGB", "RGBA"):
 | 
					 | 
				
			||||||
                        dispose_mode = "RGBA"
 | 
					 | 
				
			||||||
                        color = _rgb(color) + (0,)
 | 
					 | 
				
			||||||
                else:
 | 
					 | 
				
			||||||
                    color = 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)
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                # replace with previous contents
 | 
					 | 
				
			||||||
                if self.im is not None:
 | 
					 | 
				
			||||||
                    # only dispose the extent in this frame
 | 
					                    # only dispose the extent in this frame
 | 
				
			||||||
                    self.dispose = self._crop(self.im, self.dispose_extent)
 | 
					 | 
				
			||||||
                elif frame_transparency is not None:
 | 
					 | 
				
			||||||
                    x0, y0, x1, y1 = self.dispose_extent
 | 
					                    x0, y0, x1, y1 = self.dispose_extent
 | 
				
			||||||
                    dispose_size = (x1 - x0, y1 - y0)
 | 
					                    dispose_size = (x1 - x0, y1 - y0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    Image._decompression_bomb_check(dispose_size)
 | 
					                    Image._decompression_bomb_check(dispose_size)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    # by convention, attempt to use transparency first
 | 
				
			||||||
                    dispose_mode = "P"
 | 
					                    dispose_mode = "P"
 | 
				
			||||||
                    color = frame_transparency
 | 
					                    color = self.info.get("transparency", frame_transparency)
 | 
				
			||||||
                    if self.mode in ("RGB", "RGBA"):
 | 
					                    if color is not None:
 | 
				
			||||||
                        dispose_mode = "RGBA"
 | 
					                        if self.mode in ("RGB", "RGBA"):
 | 
				
			||||||
                        color = _rgb(frame_transparency) + (0,)
 | 
					                            dispose_mode = "RGBA"
 | 
				
			||||||
 | 
					                            color = _rgb(color) + (0,)
 | 
				
			||||||
 | 
					                    else:
 | 
				
			||||||
 | 
					                        color = 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)
 | 
				
			||||||
        except AttributeError:
 | 
					                else:
 | 
				
			||||||
            pass
 | 
					                    # replace with previous contents
 | 
				
			||||||
 | 
					                    if self.im is not None:
 | 
				
			||||||
 | 
					                        # only dispose the extent in this frame
 | 
				
			||||||
 | 
					                        self.dispose = self._crop(self.im, self.dispose_extent)
 | 
				
			||||||
 | 
					                    elif frame_transparency is not None:
 | 
				
			||||||
 | 
					                        x0, y0, x1, y1 = self.dispose_extent
 | 
				
			||||||
 | 
					                        dispose_size = (x1 - x0, y1 - y0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        Image._decompression_bomb_check(dispose_size)
 | 
				
			||||||
 | 
					                        dispose_mode = "P"
 | 
				
			||||||
 | 
					                        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:
 | 
				
			||||||
 | 
					                pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if interlace is not None:
 | 
					        if interlace is not None:
 | 
				
			||||||
            transparency = -1
 | 
					            transparency = -1
 | 
				
			||||||
| 
						 | 
					@ -498,7 +503,12 @@ def _normalize_mode(im: Image.Image) -> Image.Image:
 | 
				
			||||||
    return im.convert("L")
 | 
					    return im.convert("L")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def _normalize_palette(im, palette, info):
 | 
					_Palette = Union[bytes, bytearray, List[int], ImagePalette.ImagePalette]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _normalize_palette(
 | 
				
			||||||
 | 
					    im: Image.Image, palette: _Palette | None, info: dict[str, Any]
 | 
				
			||||||
 | 
					) -> Image.Image:
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    Normalizes the palette for image.
 | 
					    Normalizes the palette for image.
 | 
				
			||||||
      - Sets the palette to the incoming palette, if provided.
 | 
					      - Sets the palette to the incoming palette, if provided.
 | 
				
			||||||
| 
						 | 
					@ -526,8 +536,10 @@ def _normalize_palette(im, palette, info):
 | 
				
			||||||
            source_palette = bytearray(i // 3 for i in range(768))
 | 
					            source_palette = bytearray(i // 3 for i in range(768))
 | 
				
			||||||
        im.palette = ImagePalette.ImagePalette("RGB", palette=source_palette)
 | 
					        im.palette = ImagePalette.ImagePalette("RGB", palette=source_palette)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    used_palette_colors: list[int] | None
 | 
				
			||||||
    if palette:
 | 
					    if palette:
 | 
				
			||||||
        used_palette_colors = []
 | 
					        used_palette_colors = []
 | 
				
			||||||
 | 
					        assert source_palette is not None
 | 
				
			||||||
        for i in range(0, len(source_palette), 3):
 | 
					        for i in range(0, len(source_palette), 3):
 | 
				
			||||||
            source_color = tuple(source_palette[i : i + 3])
 | 
					            source_color = tuple(source_palette[i : i + 3])
 | 
				
			||||||
            index = im.palette.colors.get(source_color)
 | 
					            index = im.palette.colors.get(source_color)
 | 
				
			||||||
| 
						 | 
					@ -561,7 +573,7 @@ def _normalize_palette(im, palette, info):
 | 
				
			||||||
def _write_single_frame(
 | 
					def _write_single_frame(
 | 
				
			||||||
    im: Image.Image,
 | 
					    im: Image.Image,
 | 
				
			||||||
    fp: IO[bytes],
 | 
					    fp: IO[bytes],
 | 
				
			||||||
    palette: bytes | bytearray | list[int] | ImagePalette.ImagePalette,
 | 
					    palette: _Palette | None,
 | 
				
			||||||
) -> None:
 | 
					) -> None:
 | 
				
			||||||
    im_out = _normalize_mode(im)
 | 
					    im_out = _normalize_mode(im)
 | 
				
			||||||
    for k, v in im_out.info.items():
 | 
					    for k, v in im_out.info.items():
 | 
				
			||||||
| 
						 | 
					@ -585,7 +597,7 @@ def _write_single_frame(
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def _getbbox(
 | 
					def _getbbox(
 | 
				
			||||||
    base_im: Image.Image, im_frame: Image.Image
 | 
					    base_im: Image.Image, im_frame: Image.Image
 | 
				
			||||||
) -> tuple[Image.Image, tuple[int, int, int, int]]:
 | 
					) -> tuple[Image.Image, tuple[int, int, int, int] | None]:
 | 
				
			||||||
    if _get_palette_bytes(im_frame) != _get_palette_bytes(base_im):
 | 
					    if _get_palette_bytes(im_frame) != _get_palette_bytes(base_im):
 | 
				
			||||||
        im_frame = im_frame.convert("RGBA")
 | 
					        im_frame = im_frame.convert("RGBA")
 | 
				
			||||||
        base_im = base_im.convert("RGBA")
 | 
					        base_im = base_im.convert("RGBA")
 | 
				
			||||||
| 
						 | 
					@ -593,12 +605,20 @@ def _getbbox(
 | 
				
			||||||
    return delta, delta.getbbox(alpha_only=False)
 | 
					    return delta, delta.getbbox(alpha_only=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def _write_multiple_frames(im, fp, palette):
 | 
					class _Frame(NamedTuple):
 | 
				
			||||||
 | 
					    im: Image.Image
 | 
				
			||||||
 | 
					    bbox: tuple[int, int, int, int] | None
 | 
				
			||||||
 | 
					    encoderinfo: dict[str, Any]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _write_multiple_frames(
 | 
				
			||||||
 | 
					    im: Image.Image, fp: IO[bytes], palette: _Palette | None
 | 
				
			||||||
 | 
					) -> bool:
 | 
				
			||||||
    duration = im.encoderinfo.get("duration")
 | 
					    duration = im.encoderinfo.get("duration")
 | 
				
			||||||
    disposal = im.encoderinfo.get("disposal", im.info.get("disposal"))
 | 
					    disposal = im.encoderinfo.get("disposal", im.info.get("disposal"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    im_frames = []
 | 
					    im_frames: list[_Frame] = []
 | 
				
			||||||
    previous_im = None
 | 
					    previous_im: Image.Image | None = None
 | 
				
			||||||
    frame_count = 0
 | 
					    frame_count = 0
 | 
				
			||||||
    background_im = None
 | 
					    background_im = None
 | 
				
			||||||
    for imSequence in itertools.chain([im], im.encoderinfo.get("append_images", [])):
 | 
					    for imSequence in itertools.chain([im], im.encoderinfo.get("append_images", [])):
 | 
				
			||||||
| 
						 | 
					@ -624,24 +644,22 @@ def _write_multiple_frames(im, fp, palette):
 | 
				
			||||||
            frame_count += 1
 | 
					            frame_count += 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            diff_frame = None
 | 
					            diff_frame = None
 | 
				
			||||||
            if im_frames:
 | 
					            if im_frames and previous_im:
 | 
				
			||||||
                # delta frame
 | 
					                # delta frame
 | 
				
			||||||
                delta, bbox = _getbbox(previous_im, im_frame)
 | 
					                delta, bbox = _getbbox(previous_im, im_frame)
 | 
				
			||||||
                if not bbox:
 | 
					                if not bbox:
 | 
				
			||||||
                    # This frame is identical to the previous frame
 | 
					                    # This frame is identical to the previous frame
 | 
				
			||||||
                    if encoderinfo.get("duration"):
 | 
					                    if encoderinfo.get("duration"):
 | 
				
			||||||
                        im_frames[-1]["encoderinfo"]["duration"] += encoderinfo[
 | 
					                        im_frames[-1].encoderinfo["duration"] += encoderinfo["duration"]
 | 
				
			||||||
                            "duration"
 | 
					 | 
				
			||||||
                        ]
 | 
					 | 
				
			||||||
                    continue
 | 
					                    continue
 | 
				
			||||||
                if im_frames[-1]["encoderinfo"].get("disposal") == 2:
 | 
					                if im_frames[-1].encoderinfo.get("disposal") == 2:
 | 
				
			||||||
                    if background_im is None:
 | 
					                    if background_im is None:
 | 
				
			||||||
                        color = im.encoderinfo.get(
 | 
					                        color = im.encoderinfo.get(
 | 
				
			||||||
                            "transparency", im.info.get("transparency", (0, 0, 0))
 | 
					                            "transparency", im.info.get("transparency", (0, 0, 0))
 | 
				
			||||||
                        )
 | 
					                        )
 | 
				
			||||||
                        background = _get_background(im_frame, color)
 | 
					                        background = _get_background(im_frame, color)
 | 
				
			||||||
                        background_im = Image.new("P", im_frame.size, background)
 | 
					                        background_im = Image.new("P", im_frame.size, background)
 | 
				
			||||||
                        background_im.putpalette(im_frames[0]["im"].palette)
 | 
					                        background_im.putpalette(im_frames[0].im.palette)
 | 
				
			||||||
                    bbox = _getbbox(background_im, im_frame)[1]
 | 
					                    bbox = _getbbox(background_im, im_frame)[1]
 | 
				
			||||||
                elif encoderinfo.get("optimize") and im_frame.mode != "1":
 | 
					                elif encoderinfo.get("optimize") and im_frame.mode != "1":
 | 
				
			||||||
                    if "transparency" not in encoderinfo:
 | 
					                    if "transparency" not in encoderinfo:
 | 
				
			||||||
| 
						 | 
					@ -687,31 +705,29 @@ def _write_multiple_frames(im, fp, palette):
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                bbox = None
 | 
					                bbox = None
 | 
				
			||||||
            previous_im = im_frame
 | 
					            previous_im = im_frame
 | 
				
			||||||
            im_frames.append(
 | 
					            im_frames.append(_Frame(diff_frame or im_frame, bbox, encoderinfo))
 | 
				
			||||||
                {"im": diff_frame or im_frame, "bbox": bbox, "encoderinfo": encoderinfo}
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if len(im_frames) == 1:
 | 
					    if len(im_frames) == 1:
 | 
				
			||||||
        if "duration" in im.encoderinfo:
 | 
					        if "duration" in im.encoderinfo:
 | 
				
			||||||
            # Since multiple frames will not be written, use the combined duration
 | 
					            # Since multiple frames will not be written, use the combined duration
 | 
				
			||||||
            im.encoderinfo["duration"] = im_frames[0]["encoderinfo"]["duration"]
 | 
					            im.encoderinfo["duration"] = im_frames[0].encoderinfo["duration"]
 | 
				
			||||||
        return
 | 
					        return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for frame_data in im_frames:
 | 
					    for frame_data in im_frames:
 | 
				
			||||||
        im_frame = frame_data["im"]
 | 
					        im_frame = frame_data.im
 | 
				
			||||||
        if not frame_data["bbox"]:
 | 
					        if not frame_data.bbox:
 | 
				
			||||||
            # global header
 | 
					            # global header
 | 
				
			||||||
            for s in _get_global_header(im_frame, frame_data["encoderinfo"]):
 | 
					            for s in _get_global_header(im_frame, frame_data.encoderinfo):
 | 
				
			||||||
                fp.write(s)
 | 
					                fp.write(s)
 | 
				
			||||||
            offset = (0, 0)
 | 
					            offset = (0, 0)
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            # compress difference
 | 
					            # compress difference
 | 
				
			||||||
            if not palette:
 | 
					            if not palette:
 | 
				
			||||||
                frame_data["encoderinfo"]["include_color_table"] = True
 | 
					                frame_data.encoderinfo["include_color_table"] = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            im_frame = im_frame.crop(frame_data["bbox"])
 | 
					            im_frame = im_frame.crop(frame_data.bbox)
 | 
				
			||||||
            offset = frame_data["bbox"][:2]
 | 
					            offset = frame_data.bbox[:2]
 | 
				
			||||||
        _write_frame_data(fp, im_frame, offset, frame_data["encoderinfo"])
 | 
					        _write_frame_data(fp, im_frame, offset, frame_data.encoderinfo)
 | 
				
			||||||
    return True
 | 
					    return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -748,7 +764,9 @@ def get_interlace(im: Image.Image) -> int:
 | 
				
			||||||
    return interlace
 | 
					    return interlace
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def _write_local_header(fp, im, offset, flags):
 | 
					def _write_local_header(
 | 
				
			||||||
 | 
					    fp: IO[bytes], im: Image.Image, offset: tuple[int, int], flags: int
 | 
				
			||||||
 | 
					) -> None:
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        transparency = im.encoderinfo["transparency"]
 | 
					        transparency = im.encoderinfo["transparency"]
 | 
				
			||||||
    except KeyError:
 | 
					    except KeyError:
 | 
				
			||||||
| 
						 | 
					@ -849,7 +867,7 @@ def _save_netpbm(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
 | 
				
			||||||
_FORCE_OPTIMIZE = False
 | 
					_FORCE_OPTIMIZE = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def _get_optimize(im, info):
 | 
					def _get_optimize(im: Image.Image, info: dict[str, Any]) -> list[int] | None:
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    Palette optimization is a potentially expensive operation.
 | 
					    Palette optimization is a potentially expensive operation.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -893,6 +911,7 @@ def _get_optimize(im, info):
 | 
				
			||||||
                and current_palette_size > 2
 | 
					                and current_palette_size > 2
 | 
				
			||||||
            ):
 | 
					            ):
 | 
				
			||||||
                return used_palette_colors
 | 
					                return used_palette_colors
 | 
				
			||||||
 | 
					    return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def _get_color_table_size(palette_bytes: bytes) -> int:
 | 
					def _get_color_table_size(palette_bytes: bytes) -> int:
 | 
				
			||||||
| 
						 | 
					@ -933,7 +952,10 @@ def _get_palette_bytes(im: Image.Image) -> bytes:
 | 
				
			||||||
    return im.palette.palette if im.palette else b""
 | 
					    return im.palette.palette if im.palette else b""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def _get_background(im, info_background):
 | 
					def _get_background(
 | 
				
			||||||
 | 
					    im: Image.Image,
 | 
				
			||||||
 | 
					    info_background: int | tuple[int, int, int] | tuple[int, int, int, int] | None,
 | 
				
			||||||
 | 
					) -> int:
 | 
				
			||||||
    background = 0
 | 
					    background = 0
 | 
				
			||||||
    if info_background:
 | 
					    if info_background:
 | 
				
			||||||
        if isinstance(info_background, tuple):
 | 
					        if isinstance(info_background, tuple):
 | 
				
			||||||
| 
						 | 
					@ -956,7 +978,7 @@ def _get_background(im, info_background):
 | 
				
			||||||
    return background
 | 
					    return background
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def _get_global_header(im, info):
 | 
					def _get_global_header(im: Image.Image, info: dict[str, Any]) -> list[bytes]:
 | 
				
			||||||
    """Return a list of strings representing a GIF header"""
 | 
					    """Return a list of strings representing a GIF header"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Header Block
 | 
					    # Header Block
 | 
				
			||||||
| 
						 | 
					@ -1018,7 +1040,12 @@ def _get_global_header(im, info):
 | 
				
			||||||
    return header
 | 
					    return header
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def _write_frame_data(fp, im_frame, offset, params):
 | 
					def _write_frame_data(
 | 
				
			||||||
 | 
					    fp: IO[bytes],
 | 
				
			||||||
 | 
					    im_frame: Image.Image,
 | 
				
			||||||
 | 
					    offset: tuple[int, int],
 | 
				
			||||||
 | 
					    params: dict[str, Any],
 | 
				
			||||||
 | 
					) -> None:
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        im_frame.encoderinfo = params
 | 
					        im_frame.encoderinfo = params
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1038,7 +1065,9 @@ def _write_frame_data(fp, im_frame, offset, params):
 | 
				
			||||||
# Legacy GIF utilities
 | 
					# Legacy GIF utilities
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def getheader(im, palette=None, info=None):
 | 
					def getheader(
 | 
				
			||||||
 | 
					    im: Image.Image, palette: _Palette | None = None, info: dict[str, Any] | None = None
 | 
				
			||||||
 | 
					) -> tuple[list[bytes], list[int] | None]:
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    Legacy Method to get Gif data from image.
 | 
					    Legacy Method to get Gif data from image.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1050,11 +1079,11 @@ def getheader(im, palette=None, info=None):
 | 
				
			||||||
    :returns: tuple of(list of header items, optimized palette)
 | 
					    :returns: tuple of(list of header items, optimized palette)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    used_palette_colors = _get_optimize(im, info)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if info is None:
 | 
					    if info is None:
 | 
				
			||||||
        info = {}
 | 
					        info = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    used_palette_colors = _get_optimize(im, info)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if "background" not in info and "background" in im.info:
 | 
					    if "background" not in info and "background" in im.info:
 | 
				
			||||||
        info["background"] = im.info["background"]
 | 
					        info["background"] = im.info["background"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1066,7 +1095,9 @@ def getheader(im, palette=None, info=None):
 | 
				
			||||||
    return header, used_palette_colors
 | 
					    return header, used_palette_colors
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def getdata(im, offset=(0, 0), **params):
 | 
					def getdata(
 | 
				
			||||||
 | 
					    im: Image.Image, offset: tuple[int, int] = (0, 0), **params: Any
 | 
				
			||||||
 | 
					) -> list[bytes]:
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    Legacy Method
 | 
					    Legacy Method
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1083,12 +1114,23 @@ def getdata(im, offset=(0, 0), **params):
 | 
				
			||||||
    :returns: List of bytes containing GIF encoded frame data
 | 
					    :returns: List of bytes containing GIF encoded frame data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
 | 
					    from io import BytesIO
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    class Collector:
 | 
					    class Collector(BytesIO):
 | 
				
			||||||
        data = []
 | 
					        data = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        def write(self, data: bytes) -> None:
 | 
					        if sys.version_info >= (3, 12):
 | 
				
			||||||
            self.data.append(data)
 | 
					            from collections.abc import Buffer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            def write(self, data: Buffer) -> int:
 | 
				
			||||||
 | 
					                self.data.append(data)
 | 
				
			||||||
 | 
					                return len(data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            def write(self, data: Any) -> int:
 | 
				
			||||||
 | 
					                self.data.append(data)
 | 
				
			||||||
 | 
					                return len(data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    im.load()  # make sure raster data is available
 | 
					    im.load()  # make sure raster data is available
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -41,7 +41,7 @@ import warnings
 | 
				
			||||||
from collections.abc import Callable, MutableMapping
 | 
					from collections.abc import Callable, MutableMapping
 | 
				
			||||||
from enum import IntEnum
 | 
					from enum import IntEnum
 | 
				
			||||||
from types import ModuleType
 | 
					from types import ModuleType
 | 
				
			||||||
from typing import IO, TYPE_CHECKING, Any, Literal, Protocol, Sequence, cast
 | 
					from typing import IO, TYPE_CHECKING, Any, Literal, Protocol, Sequence, Tuple, cast
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# VERSION was removed in Pillow 6.0.0.
 | 
					# VERSION was removed in Pillow 6.0.0.
 | 
				
			||||||
# PILLOW_VERSION was removed in Pillow 9.0.0.
 | 
					# PILLOW_VERSION was removed in Pillow 9.0.0.
 | 
				
			||||||
| 
						 | 
					@ -1367,7 +1367,7 @@ class Image:
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        return ImageMode.getmode(self.mode).bands
 | 
					        return ImageMode.getmode(self.mode).bands
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def getbbox(self, *, alpha_only: bool = True) -> tuple[int, int, int, int]:
 | 
					    def getbbox(self, *, alpha_only: bool = True) -> tuple[int, int, int, int] | None:
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Calculates the bounding box of the non-zero regions in the
 | 
					        Calculates the bounding box of the non-zero regions in the
 | 
				
			||||||
        image.
 | 
					        image.
 | 
				
			||||||
| 
						 | 
					@ -3029,12 +3029,18 @@ def new(
 | 
				
			||||||
        color = ImageColor.getcolor(color, mode)
 | 
					        color = ImageColor.getcolor(color, mode)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    im = Image()
 | 
					    im = Image()
 | 
				
			||||||
    if mode == "P" and isinstance(color, (list, tuple)) and len(color) in [3, 4]:
 | 
					    if (
 | 
				
			||||||
        # RGB or RGBA value for a P image
 | 
					        mode == "P"
 | 
				
			||||||
        from . import ImagePalette
 | 
					        and isinstance(color, (list, tuple))
 | 
				
			||||||
 | 
					        and all(isinstance(i, int) for i in color)
 | 
				
			||||||
 | 
					    ):
 | 
				
			||||||
 | 
					        color_ints: tuple[int, ...] = cast(Tuple[int, ...], tuple(color))
 | 
				
			||||||
 | 
					        if len(color_ints) == 3 or len(color_ints) == 4:
 | 
				
			||||||
 | 
					            # RGB or RGBA value for a P image
 | 
				
			||||||
 | 
					            from . import ImagePalette
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        im.palette = ImagePalette.ImagePalette()
 | 
					            im.palette = ImagePalette.ImagePalette()
 | 
				
			||||||
        color = im.palette.getcolor(color)
 | 
					            color = im.palette.getcolor(color_ints)
 | 
				
			||||||
    return im._new(core.fill(mode, size, color))
 | 
					    return im._new(core.fill(mode, size, color))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -497,7 +497,7 @@ def expand(
 | 
				
			||||||
    color = _color(fill, image.mode)
 | 
					    color = _color(fill, image.mode)
 | 
				
			||||||
    if image.palette:
 | 
					    if image.palette:
 | 
				
			||||||
        palette = ImagePalette.ImagePalette(palette=image.getpalette())
 | 
					        palette = ImagePalette.ImagePalette(palette=image.getpalette())
 | 
				
			||||||
        if isinstance(color, tuple):
 | 
					        if isinstance(color, tuple) and (len(color) == 3 or len(color) == 4):
 | 
				
			||||||
            color = palette.getcolor(color)
 | 
					            color = palette.getcolor(color)
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        palette = None
 | 
					        palette = None
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -18,10 +18,13 @@
 | 
				
			||||||
from __future__ import annotations
 | 
					from __future__ import annotations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import array
 | 
					import array
 | 
				
			||||||
from typing import IO, Sequence
 | 
					from typing import IO, TYPE_CHECKING, Sequence
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from . import GimpGradientFile, GimpPaletteFile, ImageColor, PaletteFile
 | 
					from . import GimpGradientFile, GimpPaletteFile, ImageColor, PaletteFile
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if TYPE_CHECKING:
 | 
				
			||||||
 | 
					    from . import Image
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ImagePalette:
 | 
					class ImagePalette:
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
| 
						 | 
					@ -128,7 +131,11 @@ class ImagePalette:
 | 
				
			||||||
                raise ValueError(msg) from e
 | 
					                raise ValueError(msg) from e
 | 
				
			||||||
        return index
 | 
					        return index
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def getcolor(self, color, image=None) -> int:
 | 
					    def getcolor(
 | 
				
			||||||
 | 
					        self,
 | 
				
			||||||
 | 
					        color: tuple[int, int, int] | tuple[int, int, int, int],
 | 
				
			||||||
 | 
					        image: Image.Image | None = None,
 | 
				
			||||||
 | 
					    ) -> int:
 | 
				
			||||||
        """Given an rgb tuple, allocate palette entry.
 | 
					        """Given an rgb tuple, allocate palette entry.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        .. warning:: This method is experimental.
 | 
					        .. warning:: This method is experimental.
 | 
				
			||||||
| 
						 | 
					@ -163,7 +170,7 @@ class ImagePalette:
 | 
				
			||||||
                self.dirty = 1
 | 
					                self.dirty = 1
 | 
				
			||||||
                return index
 | 
					                return index
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            msg = f"unknown color specifier: {repr(color)}"
 | 
					            msg = f"unknown color specifier: {repr(color)}"  # type: ignore[unreachable]
 | 
				
			||||||
            raise ValueError(msg)
 | 
					            raise ValueError(msg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def save(self, fp: str | IO[str]) -> None:
 | 
					    def save(self, fp: str | IO[str]) -> None:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user