aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/Pillow/py3/PIL
diff options
context:
space:
mode:
authorkickbutt <kickbutt@yandex-team.com>2024-01-23 23:36:43 +0300
committerkickbutt <kickbutt@yandex-team.com>2024-01-23 23:55:22 +0300
commitfe742a0b69a530f86d1ea7aa84978d673256f8b7 (patch)
treea045a5eb8dba770797e84d0b233098605396027d /contrib/python/Pillow/py3/PIL
parentbd7d89b121ae7b9f4427766292c950fcc91c2975 (diff)
downloadydb-fe742a0b69a530f86d1ea7aa84978d673256f8b7.tar.gz
Fix separator in CUDA_ARCHITECTURES
Diffstat (limited to 'contrib/python/Pillow/py3/PIL')
-rw-r--r--contrib/python/Pillow/py3/PIL/BdfFontFile.py21
-rw-r--r--contrib/python/Pillow/py3/PIL/BlpImagePlugin.py1
-rw-r--r--contrib/python/Pillow/py3/PIL/BmpImagePlugin.py14
-rw-r--r--contrib/python/Pillow/py3/PIL/BufrStubImagePlugin.py1
-rw-r--r--contrib/python/Pillow/py3/PIL/ContainerIO.py25
-rw-r--r--contrib/python/Pillow/py3/PIL/CurImagePlugin.py4
-rw-r--r--contrib/python/Pillow/py3/PIL/DcxImagePlugin.py1
-rw-r--r--contrib/python/Pillow/py3/PIL/DdsImagePlugin.py611
-rw-r--r--contrib/python/Pillow/py3/PIL/EpsImagePlugin.py16
-rw-r--r--contrib/python/Pillow/py3/PIL/ExifTags.py1
-rw-r--r--contrib/python/Pillow/py3/PIL/FitsImagePlugin.py3
-rw-r--r--contrib/python/Pillow/py3/PIL/FliImagePlugin.py4
-rw-r--r--contrib/python/Pillow/py3/PIL/FontFile.py58
-rw-r--r--contrib/python/Pillow/py3/PIL/FpxImagePlugin.py12
-rw-r--r--contrib/python/Pillow/py3/PIL/FtexImagePlugin.py1
-rw-r--r--contrib/python/Pillow/py3/PIL/GbrImagePlugin.py1
-rw-r--r--contrib/python/Pillow/py3/PIL/GdImageFile.py2
-rw-r--r--contrib/python/Pillow/py3/PIL/GifImagePlugin.py171
-rw-r--r--contrib/python/Pillow/py3/PIL/GimpGradientFile.py2
-rw-r--r--contrib/python/Pillow/py3/PIL/GimpPaletteFile.py1
-rw-r--r--contrib/python/Pillow/py3/PIL/GribStubImagePlugin.py1
-rw-r--r--contrib/python/Pillow/py3/PIL/Hdf5StubImagePlugin.py1
-rw-r--r--contrib/python/Pillow/py3/PIL/IcnsImagePlugin.py5
-rw-r--r--contrib/python/Pillow/py3/PIL/IcoImagePlugin.py6
-rw-r--r--contrib/python/Pillow/py3/PIL/ImImagePlugin.py2
-rw-r--r--contrib/python/Pillow/py3/PIL/Image.py134
-rw-r--r--contrib/python/Pillow/py3/PIL/ImageChops.py52
-rw-r--r--contrib/python/Pillow/py3/PIL/ImageCms.py10
-rw-r--r--contrib/python/Pillow/py3/PIL/ImageColor.py6
-rw-r--r--contrib/python/Pillow/py3/PIL/ImageDraw.py7
-rw-r--r--contrib/python/Pillow/py3/PIL/ImageDraw2.py2
-rw-r--r--contrib/python/Pillow/py3/PIL/ImageEnhance.py3
-rw-r--r--contrib/python/Pillow/py3/PIL/ImageFile.py60
-rw-r--r--contrib/python/Pillow/py3/PIL/ImageFilter.py6
-rw-r--r--contrib/python/Pillow/py3/PIL/ImageFont.py50
-rw-r--r--contrib/python/Pillow/py3/PIL/ImageGrab.py1
-rw-r--r--contrib/python/Pillow/py3/PIL/ImageMath.py14
-rw-r--r--contrib/python/Pillow/py3/PIL/ImageMode.py124
-rw-r--r--contrib/python/Pillow/py3/PIL/ImageMorph.py1
-rw-r--r--contrib/python/Pillow/py3/PIL/ImageOps.py13
-rw-r--r--contrib/python/Pillow/py3/PIL/ImagePalette.py68
-rw-r--r--contrib/python/Pillow/py3/PIL/ImagePath.py1
-rw-r--r--contrib/python/Pillow/py3/PIL/ImageSequence.py24
-rw-r--r--contrib/python/Pillow/py3/PIL/ImageShow.py5
-rw-r--r--contrib/python/Pillow/py3/PIL/ImageStat.py55
-rw-r--r--contrib/python/Pillow/py3/PIL/ImageTransform.py16
-rw-r--r--contrib/python/Pillow/py3/PIL/ImageWin.py5
-rw-r--r--contrib/python/Pillow/py3/PIL/ImtImagePlugin.py2
-rw-r--r--contrib/python/Pillow/py3/PIL/IptcImagePlugin.py85
-rw-r--r--contrib/python/Pillow/py3/PIL/Jpeg2KImagePlugin.py7
-rw-r--r--contrib/python/Pillow/py3/PIL/JpegImagePlugin.py27
-rw-r--r--contrib/python/Pillow/py3/PIL/JpegPresets.py1
-rw-r--r--contrib/python/Pillow/py3/PIL/McIdasImagePlugin.py1
-rw-r--r--contrib/python/Pillow/py3/PIL/MicImagePlugin.py14
-rw-r--r--contrib/python/Pillow/py3/PIL/MpegImagePlugin.py2
-rw-r--r--contrib/python/Pillow/py3/PIL/MpoImagePlugin.py4
-rw-r--r--contrib/python/Pillow/py3/PIL/MspImagePlugin.py1
-rw-r--r--contrib/python/Pillow/py3/PIL/PSDraw.py3
-rw-r--r--contrib/python/Pillow/py3/PIL/PaletteFile.py1
-rw-r--r--contrib/python/Pillow/py3/PIL/PalmImagePlugin.py3
-rw-r--r--contrib/python/Pillow/py3/PIL/PcdImagePlugin.py2
-rw-r--r--contrib/python/Pillow/py3/PIL/PcfFontFile.py48
-rw-r--r--contrib/python/Pillow/py3/PIL/PcxImagePlugin.py5
-rw-r--r--contrib/python/Pillow/py3/PIL/PdfImagePlugin.py3
-rw-r--r--contrib/python/Pillow/py3/PIL/PdfParser.py6
-rw-r--r--contrib/python/Pillow/py3/PIL/PixarImagePlugin.py1
-rw-r--r--contrib/python/Pillow/py3/PIL/PngImagePlugin.py24
-rw-r--r--contrib/python/Pillow/py3/PIL/PpmImagePlugin.py5
-rw-r--r--contrib/python/Pillow/py3/PIL/PsdImagePlugin.py4
-rw-r--r--contrib/python/Pillow/py3/PIL/PyAccess.py7
-rw-r--r--contrib/python/Pillow/py3/PIL/QoiImagePlugin.py1
-rw-r--r--contrib/python/Pillow/py3/PIL/SgiImagePlugin.py6
-rw-r--r--contrib/python/Pillow/py3/PIL/SpiderImagePlugin.py6
-rw-r--r--contrib/python/Pillow/py3/PIL/SunImagePlugin.py2
-rw-r--r--contrib/python/Pillow/py3/PIL/TarIO.py17
-rw-r--r--contrib/python/Pillow/py3/PIL/TgaImagePlugin.py2
-rw-r--r--contrib/python/Pillow/py3/PIL/TiffImagePlugin.py53
-rw-r--r--contrib/python/Pillow/py3/PIL/TiffTags.py21
-rw-r--r--contrib/python/Pillow/py3/PIL/WalImageFile.py1
-rw-r--r--contrib/python/Pillow/py3/PIL/WebPImagePlugin.py5
-rw-r--r--contrib/python/Pillow/py3/PIL/WmfImagePlugin.py1
-rw-r--r--contrib/python/Pillow/py3/PIL/XVThumbImagePlugin.py1
-rw-r--r--contrib/python/Pillow/py3/PIL/XbmImagePlugin.py1
-rw-r--r--contrib/python/Pillow/py3/PIL/XpmImagePlugin.py2
-rw-r--r--contrib/python/Pillow/py3/PIL/__init__.py1
-rw-r--r--contrib/python/Pillow/py3/PIL/__main__.py2
-rw-r--r--contrib/python/Pillow/py3/PIL/_binary.py30
-rw-r--r--contrib/python/Pillow/py3/PIL/_deprecate.py2
-rw-r--r--contrib/python/Pillow/py3/PIL/_typing.py18
-rw-r--r--contrib/python/Pillow/py3/PIL/_util.py21
-rw-r--r--contrib/python/Pillow/py3/PIL/_version.py4
-rw-r--r--contrib/python/Pillow/py3/PIL/features.py2
92 files changed, 1291 insertions, 786 deletions
diff --git a/contrib/python/Pillow/py3/PIL/BdfFontFile.py b/contrib/python/Pillow/py3/PIL/BdfFontFile.py
index 161954831a..e3eda4fe98 100644
--- a/contrib/python/Pillow/py3/PIL/BdfFontFile.py
+++ b/contrib/python/Pillow/py3/PIL/BdfFontFile.py
@@ -20,7 +20,9 @@
"""
Parse X Bitmap Distribution Format (BDF)
"""
+from __future__ import annotations
+from typing import BinaryIO
from . import FontFile, Image
@@ -36,7 +38,17 @@ bdf_slant = {
bdf_spacing = {"P": "Proportional", "M": "Monospaced", "C": "Cell"}
-def bdf_char(f):
+def bdf_char(
+ f: BinaryIO,
+) -> (
+ tuple[
+ str,
+ int,
+ tuple[tuple[int, int], tuple[int, int, int, int], tuple[int, int, int, int]],
+ Image.Image,
+ ]
+ | None
+):
# skip to STARTCHAR
while True:
s = f.readline()
@@ -56,13 +68,12 @@ def bdf_char(f):
props[s[:i].decode("ascii")] = s[i + 1 : -1].decode("ascii")
# load bitmap
- bitmap = []
+ bitmap = bytearray()
while True:
s = f.readline()
if not s or s[:7] == b"ENDCHAR":
break
- bitmap.append(s[:-1])
- bitmap = b"".join(bitmap)
+ bitmap += s[:-1]
# The word BBX
# followed by the width in x (BBw), height in y (BBh),
@@ -92,7 +103,7 @@ def bdf_char(f):
class BdfFontFile(FontFile.FontFile):
"""Font file plugin for the X11 BDF format."""
- def __init__(self, fp):
+ def __init__(self, fp: BinaryIO):
super().__init__()
s = fp.readline()
diff --git a/contrib/python/Pillow/py3/PIL/BlpImagePlugin.py b/contrib/python/Pillow/py3/PIL/BlpImagePlugin.py
index 398696d5c7..b8f38b78a2 100644
--- a/contrib/python/Pillow/py3/PIL/BlpImagePlugin.py
+++ b/contrib/python/Pillow/py3/PIL/BlpImagePlugin.py
@@ -28,6 +28,7 @@ BLP files come in many different flavours:
- DXT3 compression is used if alpha_encoding == 1.
- DXT5 compression is used if alpha_encoding == 7.
"""
+from __future__ import annotations
import os
import struct
diff --git a/contrib/python/Pillow/py3/PIL/BmpImagePlugin.py b/contrib/python/Pillow/py3/PIL/BmpImagePlugin.py
index 9abfd0b5b8..6f730cfef1 100644
--- a/contrib/python/Pillow/py3/PIL/BmpImagePlugin.py
+++ b/contrib/python/Pillow/py3/PIL/BmpImagePlugin.py
@@ -22,7 +22,7 @@
#
# See the README file for information on usage and redistribution.
#
-
+from __future__ import annotations
import os
@@ -230,21 +230,21 @@ class BmpImageFile(ImageFile.ImageFile):
else:
padding = file_info["palette_padding"]
palette = read(padding * file_info["colors"])
- greyscale = True
+ grayscale = True
indices = (
(0, 255)
if file_info["colors"] == 2
else list(range(file_info["colors"]))
)
- # ----------------- Check if greyscale and ignore palette if so
+ # ----------------- Check if grayscale and ignore palette if so
for ind, val in enumerate(indices):
rgb = palette[ind * padding : ind * padding + 3]
if rgb != o8(val) * 3:
- greyscale = False
+ grayscale = False
- # ------- If all colors are grey, white or black, ditch palette
- if greyscale:
+ # ------- If all colors are gray, white or black, ditch palette
+ if grayscale:
self._mode = "1" if file_info["colors"] == 2 else "L"
raw_mode = self.mode
else:
@@ -396,7 +396,7 @@ def _save(im, fp, filename, bitmap_header=True):
dpi = info.get("dpi", (96, 96))
# 1 meter == 39.3701 inches
- ppm = tuple(map(lambda x: int(x * 39.3701 + 0.5), dpi))
+ ppm = tuple(int(x * 39.3701 + 0.5) for x in dpi)
stride = ((im.size[0] * bits + 7) // 8 + 3) & (~3)
header = 40 # or 64 for OS/2 version 2
diff --git a/contrib/python/Pillow/py3/PIL/BufrStubImagePlugin.py b/contrib/python/Pillow/py3/PIL/BufrStubImagePlugin.py
index eef25aa14c..60f3ec25b3 100644
--- a/contrib/python/Pillow/py3/PIL/BufrStubImagePlugin.py
+++ b/contrib/python/Pillow/py3/PIL/BufrStubImagePlugin.py
@@ -8,6 +8,7 @@
#
# See the README file for information on usage and redistribution.
#
+from __future__ import annotations
from . import Image, ImageFile
diff --git a/contrib/python/Pillow/py3/PIL/ContainerIO.py b/contrib/python/Pillow/py3/PIL/ContainerIO.py
index 45e80b39af..0035296a45 100644
--- a/contrib/python/Pillow/py3/PIL/ContainerIO.py
+++ b/contrib/python/Pillow/py3/PIL/ContainerIO.py
@@ -13,18 +13,19 @@
#
# See the README file for information on usage and redistribution.
#
-
+from __future__ import annotations
import io
+from typing import IO, AnyStr, Generic, Literal
-class ContainerIO:
+class ContainerIO(Generic[AnyStr]):
"""
A file object that provides read access to a part of an existing
file (for example a TAR file).
"""
- def __init__(self, file, offset, length):
+ def __init__(self, file: IO[AnyStr], offset: int, length: int) -> None:
"""
Create file object.
@@ -32,7 +33,7 @@ class ContainerIO:
:param offset: Start of region, in bytes.
:param length: Size of region, in bytes.
"""
- self.fh = file
+ self.fh: IO[AnyStr] = file
self.pos = 0
self.offset = offset
self.length = length
@@ -41,10 +42,10 @@ class ContainerIO:
##
# Always false.
- def isatty(self):
+ def isatty(self) -> bool:
return False
- def seek(self, offset, mode=io.SEEK_SET):
+ def seek(self, offset: int, mode: Literal[0, 1, 2] = io.SEEK_SET) -> None:
"""
Move file pointer.
@@ -63,7 +64,7 @@ class ContainerIO:
self.pos = max(0, min(self.pos, self.length))
self.fh.seek(self.offset + self.pos)
- def tell(self):
+ def tell(self) -> int:
"""
Get current file pointer.
@@ -71,7 +72,7 @@ class ContainerIO:
"""
return self.pos
- def read(self, n=0):
+ def read(self, n: int = 0) -> AnyStr:
"""
Read data.
@@ -84,17 +85,17 @@ class ContainerIO:
else:
n = self.length - self.pos
if not n: # EOF
- return b"" if "b" in self.fh.mode else ""
+ return b"" if "b" in self.fh.mode else "" # type: ignore[return-value]
self.pos = self.pos + n
return self.fh.read(n)
- def readline(self):
+ def readline(self) -> AnyStr:
"""
Read a line of text.
:returns: An 8-bit string.
"""
- s = b"" if "b" in self.fh.mode else ""
+ s: AnyStr = b"" if "b" in self.fh.mode else "" # type: ignore[assignment]
newline_character = b"\n" if "b" in self.fh.mode else "\n"
while True:
c = self.read(1)
@@ -105,7 +106,7 @@ class ContainerIO:
break
return s
- def readlines(self):
+ def readlines(self) -> list[AnyStr]:
"""
Read multiple lines of text.
diff --git a/contrib/python/Pillow/py3/PIL/CurImagePlugin.py b/contrib/python/Pillow/py3/PIL/CurImagePlugin.py
index 94efff3415..5fb2b0193c 100644
--- a/contrib/python/Pillow/py3/PIL/CurImagePlugin.py
+++ b/contrib/python/Pillow/py3/PIL/CurImagePlugin.py
@@ -15,6 +15,8 @@
#
# See the README file for information on usage and redistribution.
#
+from __future__ import annotations
+
from . import BmpImagePlugin, Image
from ._binary import i16le as i16
from ._binary import i32le as i32
@@ -64,8 +66,6 @@ class CurImageFile(BmpImagePlugin.BmpImageFile):
d, e, o, a = self.tile[0]
self.tile[0] = d, (0, 0) + self.size, o, a
- return
-
#
# --------------------------------------------------------------------
diff --git a/contrib/python/Pillow/py3/PIL/DcxImagePlugin.py b/contrib/python/Pillow/py3/PIL/DcxImagePlugin.py
index cde9d42f09..f7344df44a 100644
--- a/contrib/python/Pillow/py3/PIL/DcxImagePlugin.py
+++ b/contrib/python/Pillow/py3/PIL/DcxImagePlugin.py
@@ -20,6 +20,7 @@
#
# See the README file for information on usage and redistribution.
#
+from __future__ import annotations
from . import Image
from ._binary import i32le as i32
diff --git a/contrib/python/Pillow/py3/PIL/DdsImagePlugin.py b/contrib/python/Pillow/py3/PIL/DdsImagePlugin.py
index 54f358c7f9..eb4c8f557a 100644
--- a/contrib/python/Pillow/py3/PIL/DdsImagePlugin.py
+++ b/contrib/python/Pillow/py3/PIL/DdsImagePlugin.py
@@ -3,109 +3,323 @@ A Pillow loader for .dds files (S3TC-compressed aka DXTC)
Jerome Leclanche <jerome@leclan.ch>
Documentation:
- https://web.archive.org/web/20170802060935/http://oss.sgi.com/projects/ogl-sample/registry/EXT/texture_compression_s3tc.txt
+https://web.archive.org/web/20170802060935/http://oss.sgi.com/projects/ogl-sample/registry/EXT/texture_compression_s3tc.txt
The contents of this file are hereby released in the public domain (CC0)
Full text of the CC0 license:
- https://creativecommons.org/publicdomain/zero/1.0/
+https://creativecommons.org/publicdomain/zero/1.0/
"""
+from __future__ import annotations
+import io
import struct
-from io import BytesIO
+import sys
+from enum import IntEnum, IntFlag
from . import Image, ImageFile, ImagePalette
+from ._binary import i32le as i32
+from ._binary import o8
from ._binary import o32le as o32
# Magic ("DDS ")
DDS_MAGIC = 0x20534444
-# DDS flags
-DDSD_CAPS = 0x1
-DDSD_HEIGHT = 0x2
-DDSD_WIDTH = 0x4
-DDSD_PITCH = 0x8
-DDSD_PIXELFORMAT = 0x1000
-DDSD_MIPMAPCOUNT = 0x20000
-DDSD_LINEARSIZE = 0x80000
-DDSD_DEPTH = 0x800000
-
-# DDS caps
-DDSCAPS_COMPLEX = 0x8
-DDSCAPS_TEXTURE = 0x1000
-DDSCAPS_MIPMAP = 0x400000
-
-DDSCAPS2_CUBEMAP = 0x200
-DDSCAPS2_CUBEMAP_POSITIVEX = 0x400
-DDSCAPS2_CUBEMAP_NEGATIVEX = 0x800
-DDSCAPS2_CUBEMAP_POSITIVEY = 0x1000
-DDSCAPS2_CUBEMAP_NEGATIVEY = 0x2000
-DDSCAPS2_CUBEMAP_POSITIVEZ = 0x4000
-DDSCAPS2_CUBEMAP_NEGATIVEZ = 0x8000
-DDSCAPS2_VOLUME = 0x200000
-
-# Pixel Format
-DDPF_ALPHAPIXELS = 0x1
-DDPF_ALPHA = 0x2
-DDPF_FOURCC = 0x4
-DDPF_PALETTEINDEXED8 = 0x20
-DDPF_RGB = 0x40
-DDPF_LUMINANCE = 0x20000
-
-# dds.h
-
-DDS_FOURCC = DDPF_FOURCC
-DDS_RGB = DDPF_RGB
-DDS_RGBA = DDPF_RGB | DDPF_ALPHAPIXELS
-DDS_LUMINANCE = DDPF_LUMINANCE
-DDS_LUMINANCEA = DDPF_LUMINANCE | DDPF_ALPHAPIXELS
-DDS_ALPHA = DDPF_ALPHA
-DDS_PAL8 = DDPF_PALETTEINDEXED8
-
-DDS_HEADER_FLAGS_TEXTURE = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT
-DDS_HEADER_FLAGS_MIPMAP = DDSD_MIPMAPCOUNT
-DDS_HEADER_FLAGS_VOLUME = DDSD_DEPTH
-DDS_HEADER_FLAGS_PITCH = DDSD_PITCH
-DDS_HEADER_FLAGS_LINEARSIZE = DDSD_LINEARSIZE
-
-DDS_HEIGHT = DDSD_HEIGHT
-DDS_WIDTH = DDSD_WIDTH
+# DDS flags
+class DDSD(IntFlag):
+ CAPS = 0x1
+ HEIGHT = 0x2
+ WIDTH = 0x4
+ PITCH = 0x8
+ PIXELFORMAT = 0x1000
+ MIPMAPCOUNT = 0x20000
+ LINEARSIZE = 0x80000
+ DEPTH = 0x800000
-DDS_SURFACE_FLAGS_TEXTURE = DDSCAPS_TEXTURE
-DDS_SURFACE_FLAGS_MIPMAP = DDSCAPS_COMPLEX | DDSCAPS_MIPMAP
-DDS_SURFACE_FLAGS_CUBEMAP = DDSCAPS_COMPLEX
-DDS_CUBEMAP_POSITIVEX = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEX
-DDS_CUBEMAP_NEGATIVEX = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEX
-DDS_CUBEMAP_POSITIVEY = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEY
-DDS_CUBEMAP_NEGATIVEY = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEY
-DDS_CUBEMAP_POSITIVEZ = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEZ
-DDS_CUBEMAP_NEGATIVEZ = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEZ
+# DDS caps
+class DDSCAPS(IntFlag):
+ COMPLEX = 0x8
+ TEXTURE = 0x1000
+ MIPMAP = 0x400000
-# DXT1
-DXT1_FOURCC = 0x31545844
+class DDSCAPS2(IntFlag):
+ CUBEMAP = 0x200
+ CUBEMAP_POSITIVEX = 0x400
+ CUBEMAP_NEGATIVEX = 0x800
+ CUBEMAP_POSITIVEY = 0x1000
+ CUBEMAP_NEGATIVEY = 0x2000
+ CUBEMAP_POSITIVEZ = 0x4000
+ CUBEMAP_NEGATIVEZ = 0x8000
+ VOLUME = 0x200000
-# DXT3
-DXT3_FOURCC = 0x33545844
-# DXT5
-DXT5_FOURCC = 0x35545844
+# Pixel Format
+class DDPF(IntFlag):
+ ALPHAPIXELS = 0x1
+ ALPHA = 0x2
+ FOURCC = 0x4
+ PALETTEINDEXED8 = 0x20
+ RGB = 0x40
+ LUMINANCE = 0x20000
# dxgiformat.h
-
-DXGI_FORMAT_R8G8B8A8_TYPELESS = 27
-DXGI_FORMAT_R8G8B8A8_UNORM = 28
-DXGI_FORMAT_R8G8B8A8_UNORM_SRGB = 29
-DXGI_FORMAT_BC5_TYPELESS = 82
-DXGI_FORMAT_BC5_UNORM = 83
-DXGI_FORMAT_BC5_SNORM = 84
-DXGI_FORMAT_BC6H_UF16 = 95
-DXGI_FORMAT_BC6H_SF16 = 96
-DXGI_FORMAT_BC7_TYPELESS = 97
-DXGI_FORMAT_BC7_UNORM = 98
-DXGI_FORMAT_BC7_UNORM_SRGB = 99
+class DXGI_FORMAT(IntEnum):
+ UNKNOWN = 0
+ R32G32B32A32_TYPELESS = 1
+ R32G32B32A32_FLOAT = 2
+ R32G32B32A32_UINT = 3
+ R32G32B32A32_SINT = 4
+ R32G32B32_TYPELESS = 5
+ R32G32B32_FLOAT = 6
+ R32G32B32_UINT = 7
+ R32G32B32_SINT = 8
+ R16G16B16A16_TYPELESS = 9
+ R16G16B16A16_FLOAT = 10
+ R16G16B16A16_UNORM = 11
+ R16G16B16A16_UINT = 12
+ R16G16B16A16_SNORM = 13
+ R16G16B16A16_SINT = 14
+ R32G32_TYPELESS = 15
+ R32G32_FLOAT = 16
+ R32G32_UINT = 17
+ R32G32_SINT = 18
+ R32G8X24_TYPELESS = 19
+ D32_FLOAT_S8X24_UINT = 20
+ R32_FLOAT_X8X24_TYPELESS = 21
+ X32_TYPELESS_G8X24_UINT = 22
+ R10G10B10A2_TYPELESS = 23
+ R10G10B10A2_UNORM = 24
+ R10G10B10A2_UINT = 25
+ R11G11B10_FLOAT = 26
+ R8G8B8A8_TYPELESS = 27
+ R8G8B8A8_UNORM = 28
+ R8G8B8A8_UNORM_SRGB = 29
+ R8G8B8A8_UINT = 30
+ R8G8B8A8_SNORM = 31
+ R8G8B8A8_SINT = 32
+ R16G16_TYPELESS = 33
+ R16G16_FLOAT = 34
+ R16G16_UNORM = 35
+ R16G16_UINT = 36
+ R16G16_SNORM = 37
+ R16G16_SINT = 38
+ R32_TYPELESS = 39
+ D32_FLOAT = 40
+ R32_FLOAT = 41
+ R32_UINT = 42
+ R32_SINT = 43
+ R24G8_TYPELESS = 44
+ D24_UNORM_S8_UINT = 45
+ R24_UNORM_X8_TYPELESS = 46
+ X24_TYPELESS_G8_UINT = 47
+ R8G8_TYPELESS = 48
+ R8G8_UNORM = 49
+ R8G8_UINT = 50
+ R8G8_SNORM = 51
+ R8G8_SINT = 52
+ R16_TYPELESS = 53
+ R16_FLOAT = 54
+ D16_UNORM = 55
+ R16_UNORM = 56
+ R16_UINT = 57
+ R16_SNORM = 58
+ R16_SINT = 59
+ R8_TYPELESS = 60
+ R8_UNORM = 61
+ R8_UINT = 62
+ R8_SNORM = 63
+ R8_SINT = 64
+ A8_UNORM = 65
+ R1_UNORM = 66
+ R9G9B9E5_SHAREDEXP = 67
+ R8G8_B8G8_UNORM = 68
+ G8R8_G8B8_UNORM = 69
+ BC1_TYPELESS = 70
+ BC1_UNORM = 71
+ BC1_UNORM_SRGB = 72
+ BC2_TYPELESS = 73
+ BC2_UNORM = 74
+ BC2_UNORM_SRGB = 75
+ BC3_TYPELESS = 76
+ BC3_UNORM = 77
+ BC3_UNORM_SRGB = 78
+ BC4_TYPELESS = 79
+ BC4_UNORM = 80
+ BC4_SNORM = 81
+ BC5_TYPELESS = 82
+ BC5_UNORM = 83
+ BC5_SNORM = 84
+ B5G6R5_UNORM = 85
+ B5G5R5A1_UNORM = 86
+ B8G8R8A8_UNORM = 87
+ B8G8R8X8_UNORM = 88
+ R10G10B10_XR_BIAS_A2_UNORM = 89
+ B8G8R8A8_TYPELESS = 90
+ B8G8R8A8_UNORM_SRGB = 91
+ B8G8R8X8_TYPELESS = 92
+ B8G8R8X8_UNORM_SRGB = 93
+ BC6H_TYPELESS = 94
+ BC6H_UF16 = 95
+ BC6H_SF16 = 96
+ BC7_TYPELESS = 97
+ BC7_UNORM = 98
+ BC7_UNORM_SRGB = 99
+ AYUV = 100
+ Y410 = 101
+ Y416 = 102
+ NV12 = 103
+ P010 = 104
+ P016 = 105
+ OPAQUE_420 = 106
+ YUY2 = 107
+ Y210 = 108
+ Y216 = 109
+ NV11 = 110
+ AI44 = 111
+ IA44 = 112
+ P8 = 113
+ A8P8 = 114
+ B4G4R4A4_UNORM = 115
+ P208 = 130
+ V208 = 131
+ V408 = 132
+ SAMPLER_FEEDBACK_MIN_MIP_OPAQUE = 189
+ SAMPLER_FEEDBACK_MIP_REGION_USED_OPAQUE = 190
+
+
+class D3DFMT(IntEnum):
+ UNKNOWN = 0
+ R8G8B8 = 20
+ A8R8G8B8 = 21
+ X8R8G8B8 = 22
+ R5G6B5 = 23
+ X1R5G5B5 = 24
+ A1R5G5B5 = 25
+ A4R4G4B4 = 26
+ R3G3B2 = 27
+ A8 = 28
+ A8R3G3B2 = 29
+ X4R4G4B4 = 30
+ A2B10G10R10 = 31
+ A8B8G8R8 = 32
+ X8B8G8R8 = 33
+ G16R16 = 34
+ A2R10G10B10 = 35
+ A16B16G16R16 = 36
+ A8P8 = 40
+ P8 = 41
+ L8 = 50
+ A8L8 = 51
+ A4L4 = 52
+ V8U8 = 60
+ L6V5U5 = 61
+ X8L8V8U8 = 62
+ Q8W8V8U8 = 63
+ V16U16 = 64
+ A2W10V10U10 = 67
+ D16_LOCKABLE = 70
+ D32 = 71
+ D15S1 = 73
+ D24S8 = 75
+ D24X8 = 77
+ D24X4S4 = 79
+ D16 = 80
+ D32F_LOCKABLE = 82
+ D24FS8 = 83
+ D32_LOCKABLE = 84
+ S8_LOCKABLE = 85
+ L16 = 81
+ VERTEXDATA = 100
+ INDEX16 = 101
+ INDEX32 = 102
+ Q16W16V16U16 = 110
+ R16F = 111
+ G16R16F = 112
+ A16B16G16R16F = 113
+ R32F = 114
+ G32R32F = 115
+ A32B32G32R32F = 116
+ CxV8U8 = 117
+ A1 = 118
+ A2B10G10R10_XR_BIAS = 119
+ BINARYBUFFER = 199
+
+ UYVY = i32(b"UYVY")
+ R8G8_B8G8 = i32(b"RGBG")
+ YUY2 = i32(b"YUY2")
+ G8R8_G8B8 = i32(b"GRGB")
+ DXT1 = i32(b"DXT1")
+ DXT2 = i32(b"DXT2")
+ DXT3 = i32(b"DXT3")
+ DXT4 = i32(b"DXT4")
+ DXT5 = i32(b"DXT5")
+ DX10 = i32(b"DX10")
+ BC4S = i32(b"BC4S")
+ BC4U = i32(b"BC4U")
+ BC5S = i32(b"BC5S")
+ BC5U = i32(b"BC5U")
+ ATI1 = i32(b"ATI1")
+ ATI2 = i32(b"ATI2")
+ MULTI2_ARGB8 = i32(b"MET1")
+
+
+# Backward compatibility layer
+module = sys.modules[__name__]
+for item in DDSD:
+ setattr(module, "DDSD_" + item.name, item.value)
+for item in DDSCAPS:
+ setattr(module, "DDSCAPS_" + item.name, item.value)
+for item in DDSCAPS2:
+ setattr(module, "DDSCAPS2_" + item.name, item.value)
+for item in DDPF:
+ setattr(module, "DDPF_" + item.name, item.value)
+
+DDS_FOURCC = DDPF.FOURCC
+DDS_RGB = DDPF.RGB
+DDS_RGBA = DDPF.RGB | DDPF.ALPHAPIXELS
+DDS_LUMINANCE = DDPF.LUMINANCE
+DDS_LUMINANCEA = DDPF.LUMINANCE | DDPF.ALPHAPIXELS
+DDS_ALPHA = DDPF.ALPHA
+DDS_PAL8 = DDPF.PALETTEINDEXED8
+
+DDS_HEADER_FLAGS_TEXTURE = DDSD.CAPS | DDSD.HEIGHT | DDSD.WIDTH | DDSD.PIXELFORMAT
+DDS_HEADER_FLAGS_MIPMAP = DDSD.MIPMAPCOUNT
+DDS_HEADER_FLAGS_VOLUME = DDSD.DEPTH
+DDS_HEADER_FLAGS_PITCH = DDSD.PITCH
+DDS_HEADER_FLAGS_LINEARSIZE = DDSD.LINEARSIZE
+
+DDS_HEIGHT = DDSD.HEIGHT
+DDS_WIDTH = DDSD.WIDTH
+
+DDS_SURFACE_FLAGS_TEXTURE = DDSCAPS.TEXTURE
+DDS_SURFACE_FLAGS_MIPMAP = DDSCAPS.COMPLEX | DDSCAPS.MIPMAP
+DDS_SURFACE_FLAGS_CUBEMAP = DDSCAPS.COMPLEX
+
+DDS_CUBEMAP_POSITIVEX = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_POSITIVEX
+DDS_CUBEMAP_NEGATIVEX = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_NEGATIVEX
+DDS_CUBEMAP_POSITIVEY = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_POSITIVEY
+DDS_CUBEMAP_NEGATIVEY = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_NEGATIVEY
+DDS_CUBEMAP_POSITIVEZ = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_POSITIVEZ
+DDS_CUBEMAP_NEGATIVEZ = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_NEGATIVEZ
+
+DXT1_FOURCC = D3DFMT.DXT1
+DXT3_FOURCC = D3DFMT.DXT3
+DXT5_FOURCC = D3DFMT.DXT5
+
+DXGI_FORMAT_R8G8B8A8_TYPELESS = DXGI_FORMAT.R8G8B8A8_TYPELESS
+DXGI_FORMAT_R8G8B8A8_UNORM = DXGI_FORMAT.R8G8B8A8_UNORM
+DXGI_FORMAT_R8G8B8A8_UNORM_SRGB = DXGI_FORMAT.R8G8B8A8_UNORM_SRGB
+DXGI_FORMAT_BC5_TYPELESS = DXGI_FORMAT.BC5_TYPELESS
+DXGI_FORMAT_BC5_UNORM = DXGI_FORMAT.BC5_UNORM
+DXGI_FORMAT_BC5_SNORM = DXGI_FORMAT.BC5_SNORM
+DXGI_FORMAT_BC6H_UF16 = DXGI_FORMAT.BC6H_UF16
+DXGI_FORMAT_BC6H_SF16 = DXGI_FORMAT.BC6H_SF16
+DXGI_FORMAT_BC7_TYPELESS = DXGI_FORMAT.BC7_TYPELESS
+DXGI_FORMAT_BC7_UNORM = DXGI_FORMAT.BC7_UNORM
+DXGI_FORMAT_BC7_UNORM_SRGB = DXGI_FORMAT.BC7_UNORM_SRGB
class DdsImageFile(ImageFile.ImageFile):
@@ -124,166 +338,222 @@ class DdsImageFile(ImageFile.ImageFile):
if len(header_bytes) != 120:
msg = f"Incomplete header: {len(header_bytes)} bytes"
raise OSError(msg)
- header = BytesIO(header_bytes)
+ header = io.BytesIO(header_bytes)
flags, height, width = struct.unpack("<3I", header.read(12))
self._size = (width, height)
- self._mode = "RGBA"
+ extents = (0, 0) + self.size
pitch, depth, mipmaps = struct.unpack("<3I", header.read(12))
struct.unpack("<11I", header.read(44)) # reserved
# pixel format
- pfsize, pfflags = struct.unpack("<2I", header.read(8))
- fourcc = header.read(4)
- (bitcount,) = struct.unpack("<I", header.read(4))
- masks = struct.unpack("<4I", header.read(16))
- if pfflags & DDPF_LUMINANCE:
- # Texture contains uncompressed L or LA data
- if pfflags & DDPF_ALPHAPIXELS:
- self._mode = "LA"
- else:
- self._mode = "L"
-
- self.tile = [("raw", (0, 0) + self.size, 0, (self.mode, 0, 1))]
- elif pfflags & DDPF_RGB:
+ pfsize, pfflags, fourcc, bitcount = struct.unpack("<4I", header.read(16))
+ n = 0
+ rawmode = None
+ if pfflags & DDPF.RGB:
# Texture contains uncompressed RGB data
- masks = {mask: ["R", "G", "B", "A"][i] for i, mask in enumerate(masks)}
- rawmode = ""
- if pfflags & DDPF_ALPHAPIXELS:
- rawmode += masks[0xFF000000]
+ if pfflags & DDPF.ALPHAPIXELS:
+ self._mode = "RGBA"
+ mask_count = 4
else:
self._mode = "RGB"
- rawmode += masks[0xFF0000] + masks[0xFF00] + masks[0xFF]
+ mask_count = 3
- self.tile = [("raw", (0, 0) + self.size, 0, (rawmode[::-1], 0, 1))]
- elif pfflags & DDPF_PALETTEINDEXED8:
+ masks = struct.unpack(f"<{mask_count}I", header.read(mask_count * 4))
+ self.tile = [("dds_rgb", extents, 0, (bitcount, masks))]
+ return
+ elif pfflags & DDPF.LUMINANCE:
+ if bitcount == 8:
+ self._mode = "L"
+ elif bitcount == 16 and pfflags & DDPF.ALPHAPIXELS:
+ self._mode = "LA"
+ else:
+ msg = f"Unsupported bitcount {bitcount} for {pfflags}"
+ raise OSError(msg)
+ elif pfflags & DDPF.PALETTEINDEXED8:
self._mode = "P"
self.palette = ImagePalette.raw("RGBA", self.fp.read(1024))
- self.tile = [("raw", (0, 0) + self.size, 0, "L")]
- else:
- data_start = header_size + 4
- n = 0
- if fourcc == b"DXT1":
+ elif pfflags & DDPF.FOURCC:
+ offset = header_size + 4
+ if fourcc == D3DFMT.DXT1:
+ self._mode = "RGBA"
self.pixel_format = "DXT1"
n = 1
- elif fourcc == b"DXT3":
+ elif fourcc == D3DFMT.DXT3:
+ self._mode = "RGBA"
self.pixel_format = "DXT3"
n = 2
- elif fourcc == b"DXT5":
+ elif fourcc == D3DFMT.DXT5:
+ self._mode = "RGBA"
self.pixel_format = "DXT5"
n = 3
- elif fourcc == b"ATI1":
+ elif fourcc in (D3DFMT.BC4U, D3DFMT.ATI1):
+ self._mode = "L"
self.pixel_format = "BC4"
n = 4
- self._mode = "L"
- elif fourcc in (b"ATI2", b"BC5U"):
- self.pixel_format = "BC5"
- n = 5
+ elif fourcc == D3DFMT.BC5S:
self._mode = "RGB"
- elif fourcc == b"BC5S":
self.pixel_format = "BC5S"
n = 5
+ elif fourcc in (D3DFMT.BC5U, D3DFMT.ATI2):
self._mode = "RGB"
- elif fourcc == b"DX10":
- data_start += 20
+ self.pixel_format = "BC5"
+ n = 5
+ elif fourcc == D3DFMT.DX10:
+ offset += 20
# ignoring flags which pertain to volume textures and cubemaps
(dxgi_format,) = struct.unpack("<I", self.fp.read(4))
self.fp.read(16)
- if dxgi_format in (DXGI_FORMAT_BC5_TYPELESS, DXGI_FORMAT_BC5_UNORM):
+ if dxgi_format in (
+ DXGI_FORMAT.BC1_UNORM,
+ DXGI_FORMAT.BC1_TYPELESS,
+ ):
+ self._mode = "RGBA"
+ self.pixel_format = "BC1"
+ n = 1
+ elif dxgi_format in (DXGI_FORMAT.BC4_TYPELESS, DXGI_FORMAT.BC4_UNORM):
+ self._mode = "L"
+ self.pixel_format = "BC4"
+ n = 4
+ elif dxgi_format in (DXGI_FORMAT.BC5_TYPELESS, DXGI_FORMAT.BC5_UNORM):
+ self._mode = "RGB"
self.pixel_format = "BC5"
n = 5
+ elif dxgi_format == DXGI_FORMAT.BC5_SNORM:
self._mode = "RGB"
- elif dxgi_format == DXGI_FORMAT_BC5_SNORM:
self.pixel_format = "BC5S"
n = 5
+ elif dxgi_format == DXGI_FORMAT.BC6H_UF16:
self._mode = "RGB"
- elif dxgi_format == DXGI_FORMAT_BC6H_UF16:
self.pixel_format = "BC6H"
n = 6
+ elif dxgi_format == DXGI_FORMAT.BC6H_SF16:
self._mode = "RGB"
- elif dxgi_format == DXGI_FORMAT_BC6H_SF16:
self.pixel_format = "BC6HS"
n = 6
- self._mode = "RGB"
- elif dxgi_format in (DXGI_FORMAT_BC7_TYPELESS, DXGI_FORMAT_BC7_UNORM):
- self.pixel_format = "BC7"
- n = 7
- elif dxgi_format == DXGI_FORMAT_BC7_UNORM_SRGB:
+ elif dxgi_format in (
+ DXGI_FORMAT.BC7_TYPELESS,
+ DXGI_FORMAT.BC7_UNORM,
+ DXGI_FORMAT.BC7_UNORM_SRGB,
+ ):
+ self._mode = "RGBA"
self.pixel_format = "BC7"
- self.info["gamma"] = 1 / 2.2
n = 7
+ if dxgi_format == DXGI_FORMAT.BC7_UNORM_SRGB:
+ self.info["gamma"] = 1 / 2.2
elif dxgi_format in (
- DXGI_FORMAT_R8G8B8A8_TYPELESS,
- DXGI_FORMAT_R8G8B8A8_UNORM,
- DXGI_FORMAT_R8G8B8A8_UNORM_SRGB,
+ DXGI_FORMAT.R8G8B8A8_TYPELESS,
+ DXGI_FORMAT.R8G8B8A8_UNORM,
+ DXGI_FORMAT.R8G8B8A8_UNORM_SRGB,
):
- self.tile = [("raw", (0, 0) + self.size, 0, ("RGBA", 0, 1))]
- if dxgi_format == DXGI_FORMAT_R8G8B8A8_UNORM_SRGB:
+ self._mode = "RGBA"
+ if dxgi_format == DXGI_FORMAT.R8G8B8A8_UNORM_SRGB:
self.info["gamma"] = 1 / 2.2
- return
else:
msg = f"Unimplemented DXGI format {dxgi_format}"
raise NotImplementedError(msg)
else:
msg = f"Unimplemented pixel format {repr(fourcc)}"
raise NotImplementedError(msg)
+ else:
+ msg = f"Unknown pixel format flags {pfflags}"
+ raise NotImplementedError(msg)
+ if n:
self.tile = [
- ("bcn", (0, 0) + self.size, data_start, (n, self.pixel_format))
+ ImageFile._Tile("bcn", extents, offset, (n, self.pixel_format))
]
+ else:
+ self.tile = [ImageFile._Tile("raw", extents, 0, rawmode or self.mode)]
def load_seek(self, pos):
pass
+class DdsRgbDecoder(ImageFile.PyDecoder):
+ _pulls_fd = True
+
+ def decode(self, buffer):
+ bitcount, masks = self.args
+
+ # Some masks will be padded with zeros, e.g. R 0b11 G 0b1100
+ # Calculate how many zeros each mask is padded with
+ mask_offsets = []
+ # And the maximum value of each channel without the padding
+ mask_totals = []
+ for mask in masks:
+ offset = 0
+ if mask != 0:
+ while mask >> (offset + 1) << (offset + 1) == mask:
+ offset += 1
+ mask_offsets.append(offset)
+ mask_totals.append(mask >> offset)
+
+ data = bytearray()
+ bytecount = bitcount // 8
+ while len(data) < self.state.xsize * self.state.ysize * len(masks):
+ value = int.from_bytes(self.fd.read(bytecount), "little")
+ for i, mask in enumerate(masks):
+ masked_value = value & mask
+ # Remove the zero padding, and scale it to 8 bits
+ data += o8(
+ int(((masked_value >> mask_offsets[i]) / mask_totals[i]) * 255)
+ )
+ self.set_as_raw(bytes(data))
+ return -1, 0
+
+
def _save(im, fp, filename):
if im.mode not in ("RGB", "RGBA", "L", "LA"):
msg = f"cannot write mode {im.mode} as DDS"
raise OSError(msg)
- rawmode = im.mode
- masks = [0xFF0000, 0xFF00, 0xFF]
- if im.mode in ("L", "LA"):
- pixel_flags = DDPF_LUMINANCE
+ alpha = im.mode[-1] == "A"
+ if im.mode[0] == "L":
+ pixel_flags = DDPF.LUMINANCE
+ rawmode = im.mode
+ if alpha:
+ rgba_mask = [0x000000FF, 0x000000FF, 0x000000FF]
+ else:
+ rgba_mask = [0xFF000000, 0xFF000000, 0xFF000000]
else:
- pixel_flags = DDPF_RGB
- rawmode = rawmode[::-1]
- if im.mode in ("LA", "RGBA"):
- pixel_flags |= DDPF_ALPHAPIXELS
- masks.append(0xFF000000)
+ pixel_flags = DDPF.RGB
+ rawmode = im.mode[::-1]
+ rgba_mask = [0x00FF0000, 0x0000FF00, 0x000000FF]
- bitcount = len(masks) * 8
- while len(masks) < 4:
- masks.append(0)
+ if alpha:
+ r, g, b, a = im.split()
+ im = Image.merge("RGBA", (a, r, g, b))
+ if alpha:
+ pixel_flags |= DDPF.ALPHAPIXELS
+ rgba_mask.append(0xFF000000 if alpha else 0)
+
+ flags = DDSD.CAPS | DDSD.HEIGHT | DDSD.WIDTH | DDSD.PITCH | DDSD.PIXELFORMAT
+ bitcount = len(im.getbands()) * 8
+ pitch = (im.width * bitcount + 7) // 8
fp.write(
o32(DDS_MAGIC)
- + o32(124) # header size
- + o32(
- DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PITCH | DDSD_PIXELFORMAT
- ) # flags
- + o32(im.height)
- + o32(im.width)
- + o32((im.width * bitcount + 7) // 8) # pitch
- + o32(0) # depth
- + o32(0) # mipmaps
- + o32(0) * 11 # reserved
- + o32(32) # pfsize
- + o32(pixel_flags) # pfflags
- + o32(0) # fourcc
- + o32(bitcount) # bitcount
- + b"".join(o32(mask) for mask in masks) # rgbabitmask
- + o32(DDSCAPS_TEXTURE) # dwCaps
- + o32(0) # dwCaps2
- + o32(0) # dwCaps3
- + o32(0) # dwCaps4
- + o32(0) # dwReserved2
+ + struct.pack(
+ "<7I",
+ 124, # header size
+ flags, # flags
+ im.height,
+ im.width,
+ pitch,
+ 0, # depth
+ 0, # mipmaps
+ )
+ + struct.pack("11I", *((0,) * 11)) # reserved
+ # pfsize, pfflags, fourcc, bitcount
+ + struct.pack("<4I", 32, pixel_flags, 0, bitcount)
+ + struct.pack("<4I", *rgba_mask) # dwRGBABitMask
+ + struct.pack("<5I", DDSCAPS.TEXTURE, 0, 0, 0, 0)
+ )
+ ImageFile._save(
+ im, fp, [ImageFile._Tile("raw", (0, 0) + im.size, 0, (rawmode, 0, 1))]
)
- if im.mode == "RGBA":
- r, g, b, a = im.split()
- im = Image.merge("RGBA", (a, r, g, b))
- ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, 1))])
def _accept(prefix):
@@ -291,5 +561,6 @@ def _accept(prefix):
Image.register_open(DdsImageFile.format, DdsImageFile, _accept)
+Image.register_decoder("dds_rgb", DdsRgbDecoder)
Image.register_save(DdsImageFile.format, _save)
Image.register_extension(DdsImageFile.format, ".dds")
diff --git a/contrib/python/Pillow/py3/PIL/EpsImagePlugin.py b/contrib/python/Pillow/py3/PIL/EpsImagePlugin.py
index 9b2fce0ac0..d2e60aa075 100644
--- a/contrib/python/Pillow/py3/PIL/EpsImagePlugin.py
+++ b/contrib/python/Pillow/py3/PIL/EpsImagePlugin.py
@@ -19,6 +19,7 @@
#
# See the README file for information on usage and redistribution.
#
+from __future__ import annotations
import io
import os
@@ -77,14 +78,11 @@ def Ghostscript(tile, size, fp, scale=1, transparency=False):
# Hack to support hi-res rendering
scale = int(scale) or 1
- # orig_size = size
- # orig_bbox = bbox
- size = (size[0] * scale, size[1] * scale)
+ width = size[0] * scale
+ height = size[1] * scale
# resolution is dependent on bbox and size
- res = (
- 72.0 * size[0] / (bbox[2] - bbox[0]),
- 72.0 * size[1] / (bbox[3] - bbox[1]),
- )
+ res_x = 72.0 * width / (bbox[2] - bbox[0])
+ res_y = 72.0 * height / (bbox[3] - bbox[1])
out_fd, outfile = tempfile.mkstemp()
os.close(out_fd)
@@ -121,8 +119,8 @@ def Ghostscript(tile, size, fp, scale=1, transparency=False):
command = [
gs_binary,
"-q", # quiet mode
- "-g%dx%d" % size, # set output geometry (pixels)
- "-r%fx%f" % res, # set input DPI (dots per inch)
+ f"-g{width:d}x{height:d}", # set output geometry (pixels)
+ f"-r{res_x:f}x{res_y:f}", # set input DPI (dots per inch)
"-dBATCH", # exit after processing
"-dNOPAUSE", # don't pause between pages
"-dSAFER", # safe mode
diff --git a/contrib/python/Pillow/py3/PIL/ExifTags.py b/contrib/python/Pillow/py3/PIL/ExifTags.py
index 2347c6d4c2..60a4d9774a 100644
--- a/contrib/python/Pillow/py3/PIL/ExifTags.py
+++ b/contrib/python/Pillow/py3/PIL/ExifTags.py
@@ -13,6 +13,7 @@
This module provides constants and clear-text names for various
well-known EXIF tags.
"""
+from __future__ import annotations
from enum import IntEnum
diff --git a/contrib/python/Pillow/py3/PIL/FitsImagePlugin.py b/contrib/python/Pillow/py3/PIL/FitsImagePlugin.py
index e0e51aaac7..7dce2d60f7 100644
--- a/contrib/python/Pillow/py3/PIL/FitsImagePlugin.py
+++ b/contrib/python/Pillow/py3/PIL/FitsImagePlugin.py
@@ -8,6 +8,7 @@
#
# See the README file for information on usage and redistribution.
#
+from __future__ import annotations
import math
@@ -54,12 +55,10 @@ class FitsImageFile(ImageFile.ImageFile):
self._mode = "L"
elif number_of_bits == 16:
self._mode = "I"
- # rawmode = "I;16S"
elif number_of_bits == 32:
self._mode = "I"
elif number_of_bits in (-32, -64):
self._mode = "F"
- # rawmode = "F" if number_of_bits == -32 else "F;64F"
offset = math.ceil(self.fp.tell() / 2880) * 2880
self.tile = [("raw", (0, 0) + self.size, offset, (self.mode, 0, -1))]
diff --git a/contrib/python/Pillow/py3/PIL/FliImagePlugin.py b/contrib/python/Pillow/py3/PIL/FliImagePlugin.py
index 8f641ece99..9769761fc1 100644
--- a/contrib/python/Pillow/py3/PIL/FliImagePlugin.py
+++ b/contrib/python/Pillow/py3/PIL/FliImagePlugin.py
@@ -14,6 +14,7 @@
#
# See the README file for information on usage and redistribution.
#
+from __future__ import annotations
import os
@@ -150,7 +151,8 @@ class FliImageFile(ImageFile.ImageFile):
s = self.fp.read(4)
if not s:
- raise EOFError
+ msg = "missing frame size"
+ raise EOFError(msg)
framesize = i32(s)
diff --git a/contrib/python/Pillow/py3/PIL/FontFile.py b/contrib/python/Pillow/py3/PIL/FontFile.py
index 5ec0a6632e..3ec1ae819f 100644
--- a/contrib/python/Pillow/py3/PIL/FontFile.py
+++ b/contrib/python/Pillow/py3/PIL/FontFile.py
@@ -13,16 +13,19 @@
#
# See the README file for information on usage and redistribution.
#
-
+from __future__ import annotations
import os
+from typing import BinaryIO
from . import Image, _binary
WIDTH = 800
-def puti16(fp, values):
+def puti16(
+ fp: BinaryIO, values: tuple[int, int, int, int, int, int, int, int, int, int]
+) -> None:
"""Write network order (big-endian) 16-bit sequence"""
for v in values:
if v < 0:
@@ -33,16 +36,34 @@ def puti16(fp, values):
class FontFile:
"""Base class for raster font file handlers."""
- bitmap = None
-
- def __init__(self):
- self.info = {}
- self.glyph = [None] * 256
-
- def __getitem__(self, ix):
+ bitmap: Image.Image | None = None
+
+ def __init__(self) -> None:
+ self.info: dict[bytes, bytes | int] = {}
+ self.glyph: list[
+ tuple[
+ tuple[int, int],
+ tuple[int, int, int, int],
+ tuple[int, int, int, int],
+ Image.Image,
+ ]
+ | None
+ ] = [None] * 256
+
+ def __getitem__(
+ self, ix: int
+ ) -> (
+ tuple[
+ tuple[int, int],
+ tuple[int, int, int, int],
+ tuple[int, int, int, int],
+ Image.Image,
+ ]
+ | None
+ ):
return self.glyph[ix]
- def compile(self):
+ def compile(self) -> None:
"""Create metrics and bitmap"""
if self.bitmap:
@@ -51,7 +72,7 @@ class FontFile:
# create bitmap large enough to hold all data
h = w = maxwidth = 0
lines = 1
- for glyph in self:
+ for glyph in self.glyph:
if glyph:
d, dst, src, im = glyph
h = max(h, src[3] - src[1])
@@ -65,20 +86,22 @@ class FontFile:
ysize = lines * h
if xsize == 0 and ysize == 0:
- return ""
+ return
self.ysize = h
# paste glyphs into bitmap
self.bitmap = Image.new("1", (xsize, ysize))
- self.metrics = [None] * 256
+ self.metrics: list[
+ tuple[tuple[int, int], tuple[int, int, int, int], tuple[int, int, int, int]]
+ | None
+ ] = [None] * 256
x = y = 0
for i in range(256):
glyph = self[i]
if glyph:
d, dst, src, im = glyph
xx = src[2] - src[0]
- # yy = src[3] - src[1]
x0, y0 = x, y
x = x + xx
if x > WIDTH:
@@ -89,12 +112,15 @@ class FontFile:
self.bitmap.paste(im.crop(src), s)
self.metrics[i] = d, dst, s
- def save(self, filename):
+ def save(self, filename: str) -> None:
"""Save font"""
self.compile()
# font data
+ if not self.bitmap:
+ msg = "No bitmap created"
+ raise ValueError(msg)
self.bitmap.save(os.path.splitext(filename)[0] + ".pbm", "PNG")
# font metrics
@@ -105,6 +131,6 @@ class FontFile:
for id in range(256):
m = self.metrics[id]
if not m:
- puti16(fp, [0] * 10)
+ puti16(fp, (0,) * 10)
else:
puti16(fp, m[0] + m[1] + m[2])
diff --git a/contrib/python/Pillow/py3/PIL/FpxImagePlugin.py b/contrib/python/Pillow/py3/PIL/FpxImagePlugin.py
index a878cbfd20..75680a94e0 100644
--- a/contrib/python/Pillow/py3/PIL/FpxImagePlugin.py
+++ b/contrib/python/Pillow/py3/PIL/FpxImagePlugin.py
@@ -14,6 +14,8 @@
#
# See the README file for information on usage and redistribution.
#
+from __future__ import annotations
+
import olefile
from . import Image, ImageFile
@@ -97,16 +99,15 @@ class FpxImageFile(ImageFile.ImageFile):
s = prop[0x2000002 | id]
- colors = []
bands = i32(s, 4)
if bands > 4:
msg = "Invalid number of bands"
raise OSError(msg)
- for i in range(bands):
- # note: for now, we ignore the "uncalibrated" flag
- colors.append(i32(s, 8 + i * 4) & 0x7FFFFFFF)
- self._mode, self.rawmode = MODES[tuple(colors)]
+ # note: for now, we ignore the "uncalibrated" flag
+ colors = tuple(i32(s, 8 + i * 4) & 0x7FFFFFFF for i in range(bands))
+
+ self._mode, self.rawmode = MODES[colors]
# load JPEG tables, if any
self.jpeg = {}
@@ -227,6 +228,7 @@ class FpxImageFile(ImageFile.ImageFile):
break # isn't really required
self.stream = stream
+ self._fp = self.fp
self.fp = None
def load(self):
diff --git a/contrib/python/Pillow/py3/PIL/FtexImagePlugin.py b/contrib/python/Pillow/py3/PIL/FtexImagePlugin.py
index c2e4ead717..d5513a56a1 100644
--- a/contrib/python/Pillow/py3/PIL/FtexImagePlugin.py
+++ b/contrib/python/Pillow/py3/PIL/FtexImagePlugin.py
@@ -50,6 +50,7 @@ bytes for that mipmap level.
Note: All data is stored in little-Endian (Intel) byte order.
"""
+from __future__ import annotations
import struct
from enum import IntEnum
diff --git a/contrib/python/Pillow/py3/PIL/GbrImagePlugin.py b/contrib/python/Pillow/py3/PIL/GbrImagePlugin.py
index ec6e9de6e7..6722fa2b14 100644
--- a/contrib/python/Pillow/py3/PIL/GbrImagePlugin.py
+++ b/contrib/python/Pillow/py3/PIL/GbrImagePlugin.py
@@ -23,6 +23,7 @@
# Version 2 files are saved by GIMP v2.8 (at least)
# Version 3 files have a format specifier of 18 for 16bit floats in
# the color depth field. This is currently unsupported by Pillow.
+from __future__ import annotations
from . import Image, ImageFile
from ._binary import i32be as i32
diff --git a/contrib/python/Pillow/py3/PIL/GdImageFile.py b/contrib/python/Pillow/py3/PIL/GdImageFile.py
index 3599994a8e..d84876eb6b 100644
--- a/contrib/python/Pillow/py3/PIL/GdImageFile.py
+++ b/contrib/python/Pillow/py3/PIL/GdImageFile.py
@@ -25,7 +25,7 @@
implementation is provided for convenience and demonstrational
purposes only.
"""
-
+from __future__ import annotations
from . import ImageFile, ImagePalette, UnidentifiedImageError
from ._binary import i16be as i16
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
diff --git a/contrib/python/Pillow/py3/PIL/GimpGradientFile.py b/contrib/python/Pillow/py3/PIL/GimpGradientFile.py
index 8e801be0b8..2d8c78ea91 100644
--- a/contrib/python/Pillow/py3/PIL/GimpGradientFile.py
+++ b/contrib/python/Pillow/py3/PIL/GimpGradientFile.py
@@ -18,7 +18,7 @@ Stuff to translate curve segments to palette values (derived from
the corresponding code in GIMP, written by Federico Mena Quintero.
See the GIMP distribution for more information.)
"""
-
+from __future__ import annotations
from math import log, pi, sin, sqrt
diff --git a/contrib/python/Pillow/py3/PIL/GimpPaletteFile.py b/contrib/python/Pillow/py3/PIL/GimpPaletteFile.py
index d388928945..a3109ebaa1 100644
--- a/contrib/python/Pillow/py3/PIL/GimpPaletteFile.py
+++ b/contrib/python/Pillow/py3/PIL/GimpPaletteFile.py
@@ -13,6 +13,7 @@
#
# See the README file for information on usage and redistribution.
#
+from __future__ import annotations
import re
diff --git a/contrib/python/Pillow/py3/PIL/GribStubImagePlugin.py b/contrib/python/Pillow/py3/PIL/GribStubImagePlugin.py
index c1c71da08c..f8106800c4 100644
--- a/contrib/python/Pillow/py3/PIL/GribStubImagePlugin.py
+++ b/contrib/python/Pillow/py3/PIL/GribStubImagePlugin.py
@@ -8,6 +8,7 @@
#
# See the README file for information on usage and redistribution.
#
+from __future__ import annotations
from . import Image, ImageFile
diff --git a/contrib/python/Pillow/py3/PIL/Hdf5StubImagePlugin.py b/contrib/python/Pillow/py3/PIL/Hdf5StubImagePlugin.py
index c26b480acf..65409e269f 100644
--- a/contrib/python/Pillow/py3/PIL/Hdf5StubImagePlugin.py
+++ b/contrib/python/Pillow/py3/PIL/Hdf5StubImagePlugin.py
@@ -8,6 +8,7 @@
#
# See the README file for information on usage and redistribution.
#
+from __future__ import annotations
from . import Image, ImageFile
diff --git a/contrib/python/Pillow/py3/PIL/IcnsImagePlugin.py b/contrib/python/Pillow/py3/PIL/IcnsImagePlugin.py
index 0aa4f7a845..d877b4ecba 100644
--- a/contrib/python/Pillow/py3/PIL/IcnsImagePlugin.py
+++ b/contrib/python/Pillow/py3/PIL/IcnsImagePlugin.py
@@ -16,6 +16,7 @@
#
# See the README file for information on usage and redistribution.
#
+from __future__ import annotations
import io
import os
@@ -391,8 +392,8 @@ if __name__ == "__main__":
with open(sys.argv[1], "rb") as fp:
imf = IcnsImageFile(fp)
for size in imf.info["sizes"]:
- imf.size = size
- imf.save("out-%s-%s-%s.png" % size)
+ width, height, scale = imf.size = size
+ imf.save(f"out-{width}-{height}-{scale}.png")
with Image.open(sys.argv[1]) as im:
im.save("out.png")
if sys.platform == "windows":
diff --git a/contrib/python/Pillow/py3/PIL/IcoImagePlugin.py b/contrib/python/Pillow/py3/PIL/IcoImagePlugin.py
index 0445a2ab22..1b22f8645d 100644
--- a/contrib/python/Pillow/py3/PIL/IcoImagePlugin.py
+++ b/contrib/python/Pillow/py3/PIL/IcoImagePlugin.py
@@ -20,7 +20,7 @@
# Icon format references:
# * https://en.wikipedia.org/wiki/ICO_(file_format)
# * https://msdn.microsoft.com/en-us/library/ms997538.aspx
-
+from __future__ import annotations
import warnings
from io import BytesIO
@@ -174,9 +174,7 @@ class IcoFile:
self.entry = sorted(self.entry, key=lambda x: x["color_depth"])
# ICO images are usually squares
- # self.entry = sorted(self.entry, key=lambda x: x['width'])
- self.entry = sorted(self.entry, key=lambda x: x["square"])
- self.entry.reverse()
+ self.entry = sorted(self.entry, key=lambda x: x["square"], reverse=True)
def sizes(self):
"""
diff --git a/contrib/python/Pillow/py3/PIL/ImImagePlugin.py b/contrib/python/Pillow/py3/PIL/ImImagePlugin.py
index b42ba7cac7..97d726a8a6 100644
--- a/contrib/python/Pillow/py3/PIL/ImImagePlugin.py
+++ b/contrib/python/Pillow/py3/PIL/ImImagePlugin.py
@@ -24,7 +24,7 @@
#
# See the README file for information on usage and redistribution.
#
-
+from __future__ import annotations
import os
import re
diff --git a/contrib/python/Pillow/py3/PIL/Image.py b/contrib/python/Pillow/py3/PIL/Image.py
index 1adca9ad5b..1bba9aad2c 100644
--- a/contrib/python/Pillow/py3/PIL/Image.py
+++ b/contrib/python/Pillow/py3/PIL/Image.py
@@ -24,6 +24,8 @@
# See the README file for information on usage and redistribution.
#
+from __future__ import annotations
+
import atexit
import builtins
import io
@@ -40,7 +42,7 @@ from enum import IntEnum
from pathlib import Path
try:
- import defusedxml.ElementTree as ElementTree
+ from defusedxml import ElementTree
except ImportError:
ElementTree = None
@@ -90,7 +92,7 @@ try:
raise ImportError(msg)
except ImportError as v:
- core = DeferredError(ImportError("The _imaging C module is not installed."))
+ core = DeferredError.new(ImportError("The _imaging C module is not installed."))
# Explanations for ways that we know we might have an import error
if str(v).startswith("Module use of python"):
# The _imaging C module is present, but not compiled for
@@ -355,10 +357,11 @@ def init():
if _initialized >= 2:
return 0
+ parent_name = __name__.rpartition(".")[0]
for plugin in _plugins:
try:
logger.debug("Importing %s", plugin)
- __import__(f"PIL.{plugin}", globals(), locals(), [])
+ __import__(f"{parent_name}.{plugin}", globals(), locals(), [])
except ImportError as e:
logger.debug("Image: failed to import %s: %s", plugin, e)
@@ -476,8 +479,8 @@ class Image:
* :py:func:`~PIL.Image.frombytes`
"""
- format = None
- format_description = None
+ format: str | None = None
+ format_description: str | None = None
_close_exclusive_fp_after_loading = True
def __init__(self):
@@ -527,15 +530,19 @@ class Image:
def __enter__(self):
return self
+ def _close_fp(self):
+ if getattr(self, "_fp", False):
+ if self._fp != self.fp:
+ self._fp.close()
+ self._fp = DeferredError(ValueError("Operation on closed image"))
+ if self.fp:
+ self.fp.close()
+
def __exit__(self, *args):
- if hasattr(self, "fp") and getattr(self, "_exclusive_fp", False):
- if getattr(self, "_fp", False):
- if self._fp != self.fp:
- self._fp.close()
- self._fp = DeferredError(ValueError("Operation on closed image"))
- if self.fp:
- self.fp.close()
- self.fp = None
+ if hasattr(self, "fp"):
+ if getattr(self, "_exclusive_fp", False):
+ self._close_fp()
+ self.fp = None
def close(self):
"""
@@ -549,16 +556,12 @@ class Image:
:py:meth:`~PIL.Image.Image.load` method. See :ref:`file-handling` for
more information.
"""
- try:
- if getattr(self, "_fp", False):
- if self._fp != self.fp:
- self._fp.close()
- self._fp = DeferredError(ValueError("Operation on closed image"))
- if self.fp:
- self.fp.close()
- self.fp = None
- except Exception as msg:
- logger.debug("Error closing: %s", msg)
+ if hasattr(self, "fp"):
+ try:
+ self._close_fp()
+ self.fp = None
+ except Exception as msg:
+ logger.debug("Error closing: %s", msg)
if getattr(self, "map", None):
self.map = None
@@ -791,6 +794,9 @@ class Image:
but loads data into this image instead of creating a new image object.
"""
+ if self.width == 0 or self.height == 0:
+ return
+
# may pass tuple instead of argument list
if len(args) == 1 and isinstance(args[0], tuple):
args = args[0]
@@ -878,12 +884,12 @@ class Image:
"L", "RGB" and "CMYK". The ``matrix`` argument only supports "L"
and "RGB".
- When translating a color image to greyscale (mode "L"),
+ When translating a color image to grayscale (mode "L"),
the library uses the ITU-R 601-2 luma transform::
L = R * 299/1000 + G * 587/1000 + B * 114/1000
- The default method of converting a greyscale ("L") or "RGB"
+ The default method of converting a grayscale ("L") or "RGB"
image into a bilevel (mode "1") image uses Floyd-Steinberg
dither to approximate the original image luminosity levels. If
dither is ``None``, all values larger than 127 are set to 255 (white),
@@ -1156,7 +1162,7 @@ class Image:
if palette.mode != "P":
msg = "bad mode for palette image"
raise ValueError(msg)
- if self.mode != "RGB" and self.mode != "L":
+ if self.mode not in {"RGB", "L"}:
msg = "only RGB or L mode images can be quantized to a palette"
raise ValueError(msg)
im = self.im.convert("P", dither, palette.im)
@@ -1174,7 +1180,7 @@ class Image:
return im
- def copy(self):
+ def copy(self) -> Image:
"""
Copies this image. Use this method if you wish to paste things
into an image, but still retain the original.
@@ -1187,7 +1193,7 @@ class Image:
__copy__ = copy
- def crop(self, box=None):
+ def crop(self, box=None) -> Image:
"""
Returns a rectangular region from this image. The box is a
4-tuple defining the left, upper, right, and lower pixel
@@ -1238,7 +1244,7 @@ class Image:
Configures the image file loader so it returns a version of the
image that as closely as possible matches the given mode and
size. For example, you can use this method to convert a color
- JPEG to greyscale while loading it.
+ JPEG to grayscale while loading it.
If any changes are made, returns a tuple with the chosen ``mode`` and
``box`` with coordinates of the original image within the altered one.
@@ -1284,9 +1290,9 @@ class Image:
if self.im.bands == 1 or multiband:
return self._new(filter.filter(self.im))
- ims = []
- for c in range(self.im.bands):
- ims.append(self._new(filter.filter(self.im.getband(c))))
+ ims = [
+ self._new(filter.filter(self.im.getband(c))) for c in range(self.im.bands)
+ ]
return merge(self.mode, ims)
def getbands(self):
@@ -1335,10 +1341,7 @@ class Image:
self.load()
if self.mode in ("1", "L", "P"):
h = self.im.histogram()
- out = []
- for i in range(256):
- if h[i]:
- out.append((h[i], i))
+ out = [(h[i], i) for i in range(256) if h[i]]
if len(out) > maxcolors:
return None
return out
@@ -1379,10 +1382,7 @@ class Image:
self.load()
if self.im.bands > 1:
- extrema = []
- for i in range(self.im.bands):
- extrema.append(self.im.getband(i).getextrema())
- return tuple(extrema)
+ return tuple(self.im.getband(i).getextrema() for i in range(self.im.bands))
return self.im.getextrema()
def _getxmp(self, xmp_tags):
@@ -1610,13 +1610,13 @@ class Image:
than one band, the histograms for all bands are concatenated (for
example, the histogram for an "RGB" image contains 768 values).
- A bilevel image (mode "1") is treated as a greyscale ("L") image
+ A bilevel image (mode "1") is treated as a grayscale ("L") image
by this method.
If a mask is provided, the method returns a histogram for those
parts of the image where the mask image is non-zero. The mask
image must have the same size as the image, and be either a
- bi-level image (mode "1") or a greyscale image ("L").
+ bi-level image (mode "1") or a grayscale image ("L").
:param mask: An optional mask.
:param extrema: An optional tuple of manually-specified extrema.
@@ -1636,13 +1636,13 @@ class Image:
"""
Calculates and returns the entropy for the image.
- A bilevel image (mode "1") is treated as a greyscale ("L")
+ A bilevel image (mode "1") is treated as a grayscale ("L")
image by this method.
If a mask is provided, the method employs the histogram for
those parts of the image where the mask image is non-zero.
The mask image must have the same size as the image, and be
- either a bi-level image (mode "1") or a greyscale image ("L").
+ either a bi-level image (mode "1") or a grayscale image ("L").
:param mask: An optional mask.
:param extrema: An optional tuple of manually-specified extrema.
@@ -1658,7 +1658,7 @@ class Image:
return self.im.entropy(extrema)
return self.im.entropy()
- def paste(self, im, box=None, mask=None):
+ def paste(self, im, box=None, mask=None) -> None:
"""
Pastes another image into this image. The box argument is either
a 2-tuple giving the upper left corner, a 4-tuple defining the
@@ -1862,7 +1862,8 @@ class Image:
# do things the hard way
im = self.im.convert(mode)
if im.mode not in ("LA", "PA", "RGBA"):
- raise ValueError from e # sanity check
+ msg = "alpha channel could not be added"
+ raise ValueError(msg) from e # sanity check
self.im = im
self.pyaccess = None
self._mode = self.im.mode
@@ -2350,7 +2351,7 @@ class Image:
(w, h), Transform.AFFINE, matrix, resample, fillcolor=fillcolor
)
- def save(self, fp, format=None, **params):
+ def save(self, fp, format=None, **params) -> None:
"""
Saves this image under the given filename. If no format is
specified, the format to use is determined from the filename
@@ -2448,7 +2449,7 @@ class Image:
if open_fp:
fp.close()
- def seek(self, frame):
+ def seek(self, frame) -> Image:
"""
Seeks to the given frame in this sequence file. If you seek
beyond the end of the sequence, the method raises an
@@ -2467,7 +2468,8 @@ class Image:
# overridden by file handlers
if frame != 0:
- raise EOFError
+ msg = "no more images in file"
+ raise EOFError(msg)
def show(self, title=None):
"""
@@ -2534,7 +2536,7 @@ class Image:
return self._new(self.im.getband(channel))
- def tell(self):
+ def tell(self) -> int:
"""
Returns the current frame number. See :py:meth:`~PIL.Image.Image.seek`.
@@ -2641,7 +2643,7 @@ class Image:
resample=Resampling.NEAREST,
fill=1,
fillcolor=None,
- ):
+ ) -> Image:
"""
Transforms this image. This method creates a new image with the
given size, and the same mode as the original, and copies data
@@ -2874,7 +2876,7 @@ class ImageTransformHandler:
def _wedge():
- """Create greyscale wedge (for debugging only)"""
+ """Create grayscale wedge (for debugging only)"""
return Image()._new(core.wedge("L"))
@@ -2900,7 +2902,7 @@ def _check_size(size):
return True
-def new(mode, size, color=0):
+def new(mode, size, color=0) -> Image:
"""
Creates a new image with the given mode and size.
@@ -2939,7 +2941,7 @@ def new(mode, size, color=0):
return im._new(core.fill(mode, size, color))
-def frombytes(mode, size, data, decoder_name="raw", *args):
+def frombytes(mode, size, data, decoder_name="raw", *args) -> Image:
"""
Creates a copy of an image memory from pixel data in a buffer.
@@ -2965,15 +2967,16 @@ def frombytes(mode, size, data, decoder_name="raw", *args):
_check_size(size)
- # may pass tuple instead of argument list
- if len(args) == 1 and isinstance(args[0], tuple):
- args = args[0]
+ im = new(mode, size)
+ if im.width != 0 and im.height != 0:
+ # may pass tuple instead of argument list
+ if len(args) == 1 and isinstance(args[0], tuple):
+ args = args[0]
- if decoder_name == "raw" and args == ():
- args = mode
+ if decoder_name == "raw" and args == ():
+ args = mode
- im = new(mode, size)
- im.frombytes(data, decoder_name, args)
+ im.frombytes(data, decoder_name, args)
return im
@@ -3094,7 +3097,8 @@ def fromarray(obj, mode=None):
try:
mode, rawmode = _fromarray_typemap[typekey]
except KeyError as e:
- msg = "Cannot handle this data type: %s, %s" % typekey
+ typekey_shape, typestr = typekey
+ msg = f"Cannot handle this data type: {typekey_shape}, {typestr}"
raise TypeError(msg) from e
else:
rawmode = mode
@@ -3186,7 +3190,7 @@ def _decompression_bomb_check(size):
)
-def open(fp, mode="r", formats=None):
+def open(fp, mode="r", formats=None) -> Image:
"""
Opens and identifies the given image file.
@@ -3411,7 +3415,7 @@ def merge(mode, bands):
# Plugin registry
-def register_open(id, factory, accept=None):
+def register_open(id, factory, accept=None) -> None:
"""
Register an image file plugin. This function should not be used
in application code.
@@ -3465,7 +3469,7 @@ def register_save_all(id, driver):
SAVE_ALL[id.upper()] = driver
-def register_extension(id, extension):
+def register_extension(id, extension) -> None:
"""
Registers an image extension. This function should not be
used in application code.
diff --git a/contrib/python/Pillow/py3/PIL/ImageChops.py b/contrib/python/Pillow/py3/PIL/ImageChops.py
index 7012003179..29a5c995fd 100644
--- a/contrib/python/Pillow/py3/PIL/ImageChops.py
+++ b/contrib/python/Pillow/py3/PIL/ImageChops.py
@@ -15,11 +15,13 @@
# See the README file for information on usage and redistribution.
#
+from __future__ import annotations
+
from . import Image
-def constant(image, value):
- """Fill a channel with a given grey level.
+def constant(image: Image.Image, value: int) -> Image.Image:
+ """Fill a channel with a given gray level.
:rtype: :py:class:`~PIL.Image.Image`
"""
@@ -27,7 +29,7 @@ def constant(image, value):
return Image.new("L", image.size, value)
-def duplicate(image):
+def duplicate(image: Image.Image) -> Image.Image:
"""Copy a channel. Alias for :py:meth:`PIL.Image.Image.copy`.
:rtype: :py:class:`~PIL.Image.Image`
@@ -36,7 +38,7 @@ def duplicate(image):
return image.copy()
-def invert(image):
+def invert(image: Image.Image) -> Image.Image:
"""
Invert an image (channel). ::
@@ -49,7 +51,7 @@ def invert(image):
return image._new(image.im.chop_invert())
-def lighter(image1, image2):
+def lighter(image1: Image.Image, image2: Image.Image) -> Image.Image:
"""
Compares the two images, pixel by pixel, and returns a new image containing
the lighter values. ::
@@ -64,7 +66,7 @@ def lighter(image1, image2):
return image1._new(image1.im.chop_lighter(image2.im))
-def darker(image1, image2):
+def darker(image1: Image.Image, image2: Image.Image) -> Image.Image:
"""
Compares the two images, pixel by pixel, and returns a new image containing
the darker values. ::
@@ -79,7 +81,7 @@ def darker(image1, image2):
return image1._new(image1.im.chop_darker(image2.im))
-def difference(image1, image2):
+def difference(image1: Image.Image, image2: Image.Image) -> Image.Image:
"""
Returns the absolute value of the pixel-by-pixel difference between the two
images. ::
@@ -94,7 +96,7 @@ def difference(image1, image2):
return image1._new(image1.im.chop_difference(image2.im))
-def multiply(image1, image2):
+def multiply(image1: Image.Image, image2: Image.Image) -> Image.Image:
"""
Superimposes two images on top of each other.
@@ -111,7 +113,7 @@ def multiply(image1, image2):
return image1._new(image1.im.chop_multiply(image2.im))
-def screen(image1, image2):
+def screen(image1: Image.Image, image2: Image.Image) -> Image.Image:
"""
Superimposes two inverted images on top of each other. ::
@@ -125,7 +127,7 @@ def screen(image1, image2):
return image1._new(image1.im.chop_screen(image2.im))
-def soft_light(image1, image2):
+def soft_light(image1: Image.Image, image2: Image.Image) -> Image.Image:
"""
Superimposes two images on top of each other using the Soft Light algorithm
@@ -137,7 +139,7 @@ def soft_light(image1, image2):
return image1._new(image1.im.chop_soft_light(image2.im))
-def hard_light(image1, image2):
+def hard_light(image1: Image.Image, image2: Image.Image) -> Image.Image:
"""
Superimposes two images on top of each other using the Hard Light algorithm
@@ -149,7 +151,7 @@ def hard_light(image1, image2):
return image1._new(image1.im.chop_hard_light(image2.im))
-def overlay(image1, image2):
+def overlay(image1: Image.Image, image2: Image.Image) -> Image.Image:
"""
Superimposes two images on top of each other using the Overlay algorithm
@@ -161,7 +163,9 @@ def overlay(image1, image2):
return image1._new(image1.im.chop_overlay(image2.im))
-def add(image1, image2, scale=1.0, offset=0):
+def add(
+ image1: Image.Image, image2: Image.Image, scale: float = 1.0, offset: float = 0
+) -> Image.Image:
"""
Adds two images, dividing the result by scale and adding the
offset. If omitted, scale defaults to 1.0, and offset to 0.0. ::
@@ -176,7 +180,9 @@ def add(image1, image2, scale=1.0, offset=0):
return image1._new(image1.im.chop_add(image2.im, scale, offset))
-def subtract(image1, image2, scale=1.0, offset=0):
+def subtract(
+ image1: Image.Image, image2: Image.Image, scale: float = 1.0, offset: float = 0
+) -> Image.Image:
"""
Subtracts two images, dividing the result by scale and adding the offset.
If omitted, scale defaults to 1.0, and offset to 0.0. ::
@@ -191,7 +197,7 @@ def subtract(image1, image2, scale=1.0, offset=0):
return image1._new(image1.im.chop_subtract(image2.im, scale, offset))
-def add_modulo(image1, image2):
+def add_modulo(image1: Image.Image, image2: Image.Image) -> Image.Image:
"""Add two images, without clipping the result. ::
out = ((image1 + image2) % MAX)
@@ -204,7 +210,7 @@ def add_modulo(image1, image2):
return image1._new(image1.im.chop_add_modulo(image2.im))
-def subtract_modulo(image1, image2):
+def subtract_modulo(image1: Image.Image, image2: Image.Image) -> Image.Image:
"""Subtract two images, without clipping the result. ::
out = ((image1 - image2) % MAX)
@@ -217,7 +223,7 @@ def subtract_modulo(image1, image2):
return image1._new(image1.im.chop_subtract_modulo(image2.im))
-def logical_and(image1, image2):
+def logical_and(image1: Image.Image, image2: Image.Image) -> Image.Image:
"""Logical AND between two images.
Both of the images must have mode "1". If you would like to perform a
@@ -235,7 +241,7 @@ def logical_and(image1, image2):
return image1._new(image1.im.chop_and(image2.im))
-def logical_or(image1, image2):
+def logical_or(image1: Image.Image, image2: Image.Image) -> Image.Image:
"""Logical OR between two images.
Both of the images must have mode "1". ::
@@ -250,7 +256,7 @@ def logical_or(image1, image2):
return image1._new(image1.im.chop_or(image2.im))
-def logical_xor(image1, image2):
+def logical_xor(image1: Image.Image, image2: Image.Image) -> Image.Image:
"""Logical XOR between two images.
Both of the images must have mode "1". ::
@@ -265,7 +271,7 @@ def logical_xor(image1, image2):
return image1._new(image1.im.chop_xor(image2.im))
-def blend(image1, image2, alpha):
+def blend(image1: Image.Image, image2: Image.Image, alpha: float) -> Image.Image:
"""Blend images using constant transparency weight. Alias for
:py:func:`PIL.Image.blend`.
@@ -275,7 +281,9 @@ def blend(image1, image2, alpha):
return Image.blend(image1, image2, alpha)
-def composite(image1, image2, mask):
+def composite(
+ image1: Image.Image, image2: Image.Image, mask: Image.Image
+) -> Image.Image:
"""Create composite using transparency mask. Alias for
:py:func:`PIL.Image.composite`.
@@ -285,7 +293,7 @@ def composite(image1, image2, mask):
return Image.composite(image1, image2, mask)
-def offset(image, xoffset, yoffset=None):
+def offset(image: Image.Image, xoffset: int, yoffset: int | None = None) -> Image.Image:
"""Returns a copy of the image where data has been offset by the given
distances. Data wraps around the edges. If ``yoffset`` is omitted, it
is assumed to be equal to ``xoffset``.
diff --git a/contrib/python/Pillow/py3/PIL/ImageCms.py b/contrib/python/Pillow/py3/PIL/ImageCms.py
index 3a337f9f20..643fce830b 100644
--- a/contrib/python/Pillow/py3/PIL/ImageCms.py
+++ b/contrib/python/Pillow/py3/PIL/ImageCms.py
@@ -14,6 +14,7 @@
# See the README file for information on usage and redistribution. See
# below for the original description.
+from __future__ import annotations
import sys
from enum import IntEnum
@@ -27,7 +28,7 @@ except ImportError as ex:
# anything in core.
from ._util import DeferredError
- _imagingcms = DeferredError(ex)
+ _imagingcms = DeferredError.new(ex)
DESCRIPTION = """
pyCMS
@@ -787,11 +788,8 @@ def getProfileInfo(profile):
# info was description \r\n\r\n copyright \r\n\r\n K007 tag \r\n\r\n whitepoint
description = profile.profile.profile_description
cpright = profile.profile.copyright
- arr = []
- for elt in (description, cpright):
- if elt:
- arr.append(elt)
- return "\r\n\r\n".join(arr) + "\r\n\r\n"
+ elements = [element for element in (description, cpright) if element]
+ return "\r\n\r\n".join(elements) + "\r\n\r\n"
except (AttributeError, OSError, TypeError, ValueError) as v:
raise PyCMSError(v) from v
diff --git a/contrib/python/Pillow/py3/PIL/ImageColor.py b/contrib/python/Pillow/py3/PIL/ImageColor.py
index befc1fd1d8..ad59b06677 100644
--- a/contrib/python/Pillow/py3/PIL/ImageColor.py
+++ b/contrib/python/Pillow/py3/PIL/ImageColor.py
@@ -16,12 +16,15 @@
#
# See the README file for information on usage and redistribution.
#
+from __future__ import annotations
import re
+from functools import lru_cache
from . import Image
+@lru_cache
def getrgb(color):
"""
Convert a color string to an RGB or RGBA tuple. If the string cannot be
@@ -120,11 +123,12 @@ def getrgb(color):
raise ValueError(msg)
+@lru_cache
def getcolor(color, mode):
"""
Same as :py:func:`~PIL.ImageColor.getrgb` for most modes. However, if
``mode`` is HSV, converts the RGB value to a HSV value, or if ``mode`` is
- not color or a palette image, converts the RGB value to a greyscale value.
+ not color or a palette image, converts the RGB value to a grayscale value.
If the string cannot be parsed, this function raises a :py:exc:`ValueError`
exception.
diff --git a/contrib/python/Pillow/py3/PIL/ImageDraw.py b/contrib/python/Pillow/py3/PIL/ImageDraw.py
index fbf320d72a..84665f54ff 100644
--- a/contrib/python/Pillow/py3/PIL/ImageDraw.py
+++ b/contrib/python/Pillow/py3/PIL/ImageDraw.py
@@ -29,9 +29,11 @@
#
# See the README file for information on usage and redistribution.
#
+from __future__ import annotations
import math
import numbers
+import struct
from . import Image, ImageColor
@@ -542,7 +544,8 @@ class ImageDraw:
# font.getmask2(mode="RGBA") returns color in RGB bands and mask in A
# extract mask and set text alpha
color, mask = mask, mask.getband(3)
- color.fillband(3, (ink >> 24) & 0xFF)
+ ink_alpha = struct.pack("i", ink)[3]
+ color.fillband(3, ink_alpha)
x, y = coord
self.im.paste(color, (x, y, x + mask.size[0], y + mask.size[1]), mask)
else:
@@ -921,7 +924,7 @@ def floodfill(image, xy, value, border=None, thresh=0):
if border is None:
fill = _color_diff(p, background) <= thresh
else:
- fill = p != value and p != border
+ fill = p not in (value, border)
if fill:
pixel[s, t] = value
new_edge.add((s, t))
diff --git a/contrib/python/Pillow/py3/PIL/ImageDraw2.py b/contrib/python/Pillow/py3/PIL/ImageDraw2.py
index 7ce0224a67..35ee5834e3 100644
--- a/contrib/python/Pillow/py3/PIL/ImageDraw2.py
+++ b/contrib/python/Pillow/py3/PIL/ImageDraw2.py
@@ -22,7 +22,7 @@
.. seealso:: :py:mod:`PIL.ImageDraw`
"""
-
+from __future__ import annotations
from . import Image, ImageColor, ImageDraw, ImageFont, ImagePath
diff --git a/contrib/python/Pillow/py3/PIL/ImageEnhance.py b/contrib/python/Pillow/py3/PIL/ImageEnhance.py
index 3b79d5c46a..93a50d2a2b 100644
--- a/contrib/python/Pillow/py3/PIL/ImageEnhance.py
+++ b/contrib/python/Pillow/py3/PIL/ImageEnhance.py
@@ -17,6 +17,7 @@
#
# See the README file for information on usage and redistribution.
#
+from __future__ import annotations
from . import Image, ImageFilter, ImageStat
@@ -59,7 +60,7 @@ class Contrast(_Enhance):
This class can be used to control the contrast of an image, similar
to the contrast control on a TV set. An enhancement factor of 0.0
- gives a solid grey image. A factor of 1.0 gives the original image.
+ gives a solid gray image. A factor of 1.0 gives the original image.
"""
def __init__(self, image):
diff --git a/contrib/python/Pillow/py3/PIL/ImageFile.py b/contrib/python/Pillow/py3/PIL/ImageFile.py
index 8e4f7dfb2c..0923979af8 100644
--- a/contrib/python/Pillow/py3/PIL/ImageFile.py
+++ b/contrib/python/Pillow/py3/PIL/ImageFile.py
@@ -26,13 +26,16 @@
#
# See the README file for information on usage and redistribution.
#
+from __future__ import annotations
import io
import itertools
import struct
import sys
+from typing import Any, NamedTuple
from . import Image
+from ._deprecate import deprecate
from ._util import is_path
MAXBLOCK = 65536
@@ -61,15 +64,25 @@ Dict of known error codes returned from :meth:`.PyDecoder.decode`,
# Helpers
-def raise_oserror(error):
+def _get_oserror(error, *, encoder):
try:
msg = Image.core.getcodecstatus(error)
except AttributeError:
msg = ERRORS.get(error)
if not msg:
- msg = f"decoder error {error}"
- msg += " when reading image file"
- raise OSError(msg)
+ msg = f"{'encoder' if encoder else 'decoder'} error {error}"
+ msg += f" when {'writing' if encoder else 'reading'} image file"
+ return OSError(msg)
+
+
+def raise_oserror(error):
+ deprecate(
+ "raise_oserror",
+ 12,
+ action="It is only useful for translating error codes returned by a codec's "
+ "decode() method, which ImageFile already does automatically.",
+ )
+ raise _get_oserror(error, encoder=False)
def _tilesort(t):
@@ -77,6 +90,13 @@ def _tilesort(t):
return t[2]
+class _Tile(NamedTuple):
+ encoder_name: str
+ extents: tuple[int, int, int, int]
+ offset: int
+ args: tuple[Any, ...] | str | None
+
+
#
# --------------------------------------------------------------------
# ImageFile base class
@@ -187,6 +207,8 @@ class ImageFile(Image.Image):
if use_mmap:
# try memory mapping
decoder_name, extents, offset, args = self.tile[0]
+ if isinstance(args, str):
+ args = (args, 0, 1)
if (
decoder_name == "raw"
and len(args) >= 3
@@ -200,8 +222,8 @@ class ImageFile(Image.Image):
with open(self.filename) as fp:
self.map = mmap.mmap(fp.fileno(), 0, access=mmap.ACCESS_READ)
if offset + self.size[1] * args[1] > self.map.size():
- # buffer is not large enough
- raise OSError
+ msg = "buffer is not large enough"
+ raise OSError(msg)
self.im = Image.core.map_buffer(
self.map, self.size, decoder_name, offset, args
)
@@ -285,7 +307,7 @@ class ImageFile(Image.Image):
if not self.map and not LOAD_TRUNCATED_IMAGES and err_code < 0:
# still raised if decoder fails to return anything
- raise_oserror(err_code)
+ raise _get_oserror(err_code, encoder=False)
return Image.Image.load(self)
@@ -412,7 +434,7 @@ class Parser:
if e < 0:
# decoding error
self.image = None
- raise_oserror(e)
+ raise _get_oserror(e, encoder=False)
else:
# end of image
return
@@ -430,7 +452,6 @@ class Parser:
with io.BytesIO(self.data) as fp:
im = Image.open(fp)
except OSError:
- # traceback.print_exc()
pass # not enough data
else:
flag = hasattr(im, "load_seek") or hasattr(im, "load_read")
@@ -521,13 +542,13 @@ def _save(im, fp, tile, bufsize=0):
fp.flush()
-def _encode_tile(im, fp, tile, bufsize, fh, exc=None):
- for e, b, o, a in tile:
- if o > 0:
- fp.seek(o)
- encoder = Image._getencoder(im.mode, e, a, im.encoderconfig)
+def _encode_tile(im, fp, tile: list[_Tile], bufsize, fh, exc=None):
+ for encoder_name, extents, offset, args in tile:
+ if offset > 0:
+ fp.seek(offset)
+ encoder = Image._getencoder(im.mode, encoder_name, args, im.encoderconfig)
try:
- encoder.setimage(im.im, b)
+ encoder.setimage(im.im, extents)
if encoder.pushes_fd:
encoder.setfd(fp)
errcode = encoder.encode_to_pyfd()[1]
@@ -543,8 +564,7 @@ def _encode_tile(im, fp, tile, bufsize, fh, exc=None):
# slight speedup: compress to real file object
errcode = encoder.encode_to_file(fh, bufsize)
if errcode < 0:
- msg = f"encoder error {errcode} when writing image file"
- raise OSError(msg) from exc
+ raise _get_oserror(errcode, encoder=True) from exc
finally:
encoder.cleanup()
@@ -690,7 +710,8 @@ class PyDecoder(PyCodec):
If finished with decoding return -1 for the bytes consumed.
Err codes are from :data:`.ImageFile.ERRORS`.
"""
- raise NotImplementedError()
+ msg = "unavailable in base decoder"
+ raise NotImplementedError(msg)
def set_as_raw(self, data, rawmode=None):
"""
@@ -739,7 +760,8 @@ class PyEncoder(PyCodec):
If finished with encoding return 1 for the error code.
Err codes are from :data:`.ImageFile.ERRORS`.
"""
- raise NotImplementedError()
+ msg = "unavailable in base encoder"
+ raise NotImplementedError(msg)
def encode_to_pyfd(self):
"""
diff --git a/contrib/python/Pillow/py3/PIL/ImageFilter.py b/contrib/python/Pillow/py3/PIL/ImageFilter.py
index 57268b8f53..035b83c4d7 100644
--- a/contrib/python/Pillow/py3/PIL/ImageFilter.py
+++ b/contrib/python/Pillow/py3/PIL/ImageFilter.py
@@ -14,6 +14,8 @@
#
# See the README file for information on usage and redistribution.
#
+from __future__ import annotations
+
import functools
@@ -222,7 +224,7 @@ class UnsharpMask(MultibandFilter):
.. _digital unsharp masking: https://en.wikipedia.org/wiki/Unsharp_masking#Digital_unsharp_masking
- """ # noqa: E501
+ """
name = "UnsharpMask"
@@ -394,7 +396,7 @@ class Color3DLUT(MultibandFilter):
if hasattr(table, "shape"):
try:
import numpy
- except ImportError: # pragma: no cover
+ except ImportError:
pass
if numpy and isinstance(table, numpy.ndarray):
diff --git a/contrib/python/Pillow/py3/PIL/ImageFont.py b/contrib/python/Pillow/py3/PIL/ImageFont.py
index c295621351..8213d030a4 100644
--- a/contrib/python/Pillow/py3/PIL/ImageFont.py
+++ b/contrib/python/Pillow/py3/PIL/ImageFont.py
@@ -25,12 +25,16 @@
# See the README file for information on usage and redistribution.
#
+from __future__ import annotations
+
import base64
import os
import sys
import warnings
from enum import IntEnum
from io import BytesIO
+from pathlib import Path
+from typing import BinaryIO
from . import Image
from ._util import is_directory, is_path
@@ -49,7 +53,7 @@ try:
except ImportError as ex:
from ._util import DeferredError
- core = DeferredError(ex)
+ core = DeferredError.new(ex)
def _string_length_check(text):
@@ -145,6 +149,8 @@ class ImageFont:
:return: An internal PIL storage memory instance as defined by the
:py:mod:`PIL.Image.core` interface module.
"""
+ _string_length_check(text)
+ Image._decompression_bomb_check(self.font.getsize(text))
return self.font.getmask(text, mode)
def getbbox(self, text, *args, **kwargs):
@@ -185,9 +191,20 @@ class ImageFont:
class FreeTypeFont:
"""FreeType font wrapper (requires _imagingft service)"""
- def __init__(self, font=None, size=10, index=0, encoding="", layout_engine=None):
+ def __init__(
+ self,
+ font: bytes | str | Path | BinaryIO | None = None,
+ size: float = 10,
+ index: int = 0,
+ encoding: str = "",
+ layout_engine: Layout | None = None,
+ ) -> None:
# FIXME: use service provider instead
+ if size <= 0:
+ msg = "font size must be greater than 0"
+ raise ValueError(msg)
+
self.path = font
self.size = size
self.index = index
@@ -213,6 +230,8 @@ class FreeTypeFont:
)
if is_path(font):
+ if isinstance(font, Path):
+ font = str(font)
if sys.platform == "win32":
font_bytes_path = font if isinstance(font, bytes) else font.encode()
try:
@@ -375,8 +394,9 @@ class FreeTypeFont:
:param stroke_width: The width of the text stroke.
:param anchor: The text anchor alignment. Determines the relative location of
- the anchor to the text. The default alignment is top left.
- See :ref:`text-anchors` for valid values.
+ the anchor to the text. The default alignment is top left,
+ specifically ``la`` for horizontal text and ``lt`` for
+ vertical text. See :ref:`text-anchors` for details.
:return: ``(left, top, right, bottom)`` bounding box
"""
@@ -449,8 +469,9 @@ class FreeTypeFont:
.. versionadded:: 6.2.0
:param anchor: The text anchor alignment. Determines the relative location of
- the anchor to the text. The default alignment is top left.
- See :ref:`text-anchors` for valid values.
+ the anchor to the text. The default alignment is top left,
+ specifically ``la`` for horizontal text and ``lt`` for
+ vertical text. See :ref:`text-anchors` for details.
.. versionadded:: 8.0.0
@@ -541,8 +562,9 @@ class FreeTypeFont:
.. versionadded:: 6.2.0
:param anchor: The text anchor alignment. Determines the relative location of
- the anchor to the text. The default alignment is top left.
- See :ref:`text-anchors` for valid values.
+ the anchor to the text. The default alignment is top left,
+ specifically ``la`` for horizontal text and ``lt`` for
+ vertical text. See :ref:`text-anchors` for details.
.. versionadded:: 8.0.0
@@ -565,16 +587,16 @@ class FreeTypeFont:
im = None
size = None
- def fill(mode, im_size):
+ def fill(width, height):
nonlocal im, size
- size = im_size
+ size = (width, height)
if Image.MAX_IMAGE_PIXELS is not None:
- pixels = max(1, size[0]) * max(1, size[1])
+ pixels = max(1, width) * max(1, height)
if pixels > 2 * Image.MAX_IMAGE_PIXELS:
return
- im = Image.core.fill(mode, size)
+ im = Image.core.fill("RGBA" if mode == "RGBA" else "L", size)
return im
offset = self.font.render(
@@ -712,7 +734,6 @@ class TransposedFont:
if self.orientation in (Image.Transpose.ROTATE_90, Image.Transpose.ROTATE_270):
msg = "text length is undefined for text rotated by 90 or 270 degrees"
raise ValueError(msg)
- _string_length_check(text)
return self.font.getlength(text, *args, **kwargs)
@@ -775,7 +796,7 @@ def truetype(font=None, size=10, index=0, encoding="", layout_engine=None):
This specifies the character set to use. It does not alter the
encoding of any text provided in subsequent operations.
:param layout_engine: Which layout engine to use, if available:
- :data:`.ImageFont.Layout.BASIC` or :data:`.ImageFont.Layout.RAQM`.
+ :attr:`.ImageFont.Layout.BASIC` or :attr:`.ImageFont.Layout.RAQM`.
If it is available, Raqm layout will be used by default.
Otherwise, basic layout will be used.
@@ -788,6 +809,7 @@ def truetype(font=None, size=10, index=0, encoding="", layout_engine=None):
.. versionadded:: 4.2.0
:return: A font object.
:exception OSError: If the file could not be read.
+ :exception ValueError: If the font size is not greater than zero.
"""
def freetype(font):
diff --git a/contrib/python/Pillow/py3/PIL/ImageGrab.py b/contrib/python/Pillow/py3/PIL/ImageGrab.py
index bcfffc3dc1..a4993d3d4b 100644
--- a/contrib/python/Pillow/py3/PIL/ImageGrab.py
+++ b/contrib/python/Pillow/py3/PIL/ImageGrab.py
@@ -14,6 +14,7 @@
#
# See the README file for information on usage and redistribution.
#
+from __future__ import annotations
import io
import os
diff --git a/contrib/python/Pillow/py3/PIL/ImageMath.py b/contrib/python/Pillow/py3/PIL/ImageMath.py
index eb6bbe6c62..b77f4bce56 100644
--- a/contrib/python/Pillow/py3/PIL/ImageMath.py
+++ b/contrib/python/Pillow/py3/PIL/ImageMath.py
@@ -14,16 +14,13 @@
#
# See the README file for information on usage and redistribution.
#
+from __future__ import annotations
import builtins
from . import Image, _imagingmath
-def _isconstant(v):
- return isinstance(v, (int, float))
-
-
class _Operand:
"""Wraps an image operand, providing standard operators"""
@@ -43,7 +40,7 @@ class _Operand:
raise ValueError(msg)
else:
# argument was a constant
- if _isconstant(im1) and self.im.mode in ("1", "L", "I"):
+ if isinstance(im1, (int, float)) and self.im.mode in ("1", "L", "I"):
return Image.new("I", self.im.size, im1)
else:
return Image.new("F", self.im.size, im1)
@@ -237,9 +234,14 @@ def eval(expression, _dict={}, **kw):
# build execution namespace
args = ops.copy()
+ for k in list(_dict.keys()) + list(kw.keys()):
+ if "__" in k or hasattr(builtins, k):
+ msg = f"'{k}' not allowed"
+ raise ValueError(msg)
+
args.update(_dict)
args.update(kw)
- for k, v in list(args.items()):
+ for k, v in args.items():
if hasattr(v, "im"):
args[k] = _Operand(v)
diff --git a/contrib/python/Pillow/py3/PIL/ImageMode.py b/contrib/python/Pillow/py3/PIL/ImageMode.py
index a0b3351429..0b31f60817 100644
--- a/contrib/python/Pillow/py3/PIL/ImageMode.py
+++ b/contrib/python/Pillow/py3/PIL/ImageMode.py
@@ -12,79 +12,85 @@
#
# See the README file for information on usage and redistribution.
#
+from __future__ import annotations
import sys
-
-# mode descriptor cache
-_modes = None
+from functools import lru_cache
class ModeDescriptor:
"""Wrapper for mode strings."""
- def __init__(self, mode, bands, basemode, basetype, typestr):
+ def __init__(
+ self,
+ mode: str,
+ bands: tuple[str, ...],
+ basemode: str,
+ basetype: str,
+ typestr: str,
+ ) -> None:
self.mode = mode
self.bands = bands
self.basemode = basemode
self.basetype = basetype
self.typestr = typestr
- def __str__(self):
+ def __str__(self) -> str:
return self.mode
-def getmode(mode):
+@lru_cache
+def getmode(mode: str) -> ModeDescriptor:
"""Gets a mode descriptor for the given mode."""
- global _modes
- if not _modes:
- # initialize mode cache
- modes = {}
- endian = "<" if sys.byteorder == "little" else ">"
- for m, (basemode, basetype, bands, typestr) in {
- # core modes
- # Bits need to be extended to bytes
- "1": ("L", "L", ("1",), "|b1"),
- "L": ("L", "L", ("L",), "|u1"),
- "I": ("L", "I", ("I",), endian + "i4"),
- "F": ("L", "F", ("F",), endian + "f4"),
- "P": ("P", "L", ("P",), "|u1"),
- "RGB": ("RGB", "L", ("R", "G", "B"), "|u1"),
- "RGBX": ("RGB", "L", ("R", "G", "B", "X"), "|u1"),
- "RGBA": ("RGB", "L", ("R", "G", "B", "A"), "|u1"),
- "CMYK": ("RGB", "L", ("C", "M", "Y", "K"), "|u1"),
- "YCbCr": ("RGB", "L", ("Y", "Cb", "Cr"), "|u1"),
- # UNDONE - unsigned |u1i1i1
- "LAB": ("RGB", "L", ("L", "A", "B"), "|u1"),
- "HSV": ("RGB", "L", ("H", "S", "V"), "|u1"),
- # extra experimental modes
- "RGBa": ("RGB", "L", ("R", "G", "B", "a"), "|u1"),
- "BGR;15": ("RGB", "L", ("B", "G", "R"), "|u1"),
- "BGR;16": ("RGB", "L", ("B", "G", "R"), "|u1"),
- "BGR;24": ("RGB", "L", ("B", "G", "R"), "|u1"),
- "LA": ("L", "L", ("L", "A"), "|u1"),
- "La": ("L", "L", ("L", "a"), "|u1"),
- "PA": ("RGB", "L", ("P", "A"), "|u1"),
- }.items():
- modes[m] = ModeDescriptor(m, bands, basemode, basetype, typestr)
- # mapping modes
- for i16mode, typestr in {
- # I;16 == I;16L, and I;32 == I;32L
- "I;16": "<u2",
- "I;16S": "<i2",
- "I;16L": "<u2",
- "I;16LS": "<i2",
- "I;16B": ">u2",
- "I;16BS": ">i2",
- "I;16N": endian + "u2",
- "I;16NS": endian + "i2",
- "I;32": "<u4",
- "I;32B": ">u4",
- "I;32L": "<u4",
- "I;32S": "<i4",
- "I;32BS": ">i4",
- "I;32LS": "<i4",
- }.items():
- modes[i16mode] = ModeDescriptor(i16mode, ("I",), "L", "L", typestr)
- # set global mode cache atomically
- _modes = modes
- return _modes[mode]
+ # initialize mode cache
+ endian = "<" if sys.byteorder == "little" else ">"
+
+ modes = {
+ # core modes
+ # Bits need to be extended to bytes
+ "1": ("L", "L", ("1",), "|b1"),
+ "L": ("L", "L", ("L",), "|u1"),
+ "I": ("L", "I", ("I",), endian + "i4"),
+ "F": ("L", "F", ("F",), endian + "f4"),
+ "P": ("P", "L", ("P",), "|u1"),
+ "RGB": ("RGB", "L", ("R", "G", "B"), "|u1"),
+ "RGBX": ("RGB", "L", ("R", "G", "B", "X"), "|u1"),
+ "RGBA": ("RGB", "L", ("R", "G", "B", "A"), "|u1"),
+ "CMYK": ("RGB", "L", ("C", "M", "Y", "K"), "|u1"),
+ "YCbCr": ("RGB", "L", ("Y", "Cb", "Cr"), "|u1"),
+ # UNDONE - unsigned |u1i1i1
+ "LAB": ("RGB", "L", ("L", "A", "B"), "|u1"),
+ "HSV": ("RGB", "L", ("H", "S", "V"), "|u1"),
+ # extra experimental modes
+ "RGBa": ("RGB", "L", ("R", "G", "B", "a"), "|u1"),
+ "BGR;15": ("RGB", "L", ("B", "G", "R"), "|u1"),
+ "BGR;16": ("RGB", "L", ("B", "G", "R"), "|u1"),
+ "BGR;24": ("RGB", "L", ("B", "G", "R"), "|u1"),
+ "LA": ("L", "L", ("L", "A"), "|u1"),
+ "La": ("L", "L", ("L", "a"), "|u1"),
+ "PA": ("RGB", "L", ("P", "A"), "|u1"),
+ }
+ if mode in modes:
+ base_mode, base_type, bands, type_str = modes[mode]
+ return ModeDescriptor(mode, bands, base_mode, base_type, type_str)
+
+ mapping_modes = {
+ # I;16 == I;16L, and I;32 == I;32L
+ "I;16": "<u2",
+ "I;16S": "<i2",
+ "I;16L": "<u2",
+ "I;16LS": "<i2",
+ "I;16B": ">u2",
+ "I;16BS": ">i2",
+ "I;16N": endian + "u2",
+ "I;16NS": endian + "i2",
+ "I;32": "<u4",
+ "I;32B": ">u4",
+ "I;32L": "<u4",
+ "I;32S": "<i4",
+ "I;32BS": ">i4",
+ "I;32LS": "<i4",
+ }
+
+ type_str = mapping_modes[mode]
+ return ModeDescriptor(mode, ("I",), "L", "L", type_str)
diff --git a/contrib/python/Pillow/py3/PIL/ImageMorph.py b/contrib/python/Pillow/py3/PIL/ImageMorph.py
index 6fccc315b3..282e7d2a54 100644
--- a/contrib/python/Pillow/py3/PIL/ImageMorph.py
+++ b/contrib/python/Pillow/py3/PIL/ImageMorph.py
@@ -4,6 +4,7 @@
# 2014-06-04 Initial version.
#
# Copyright (c) 2014 Dov Grobgeld <dov.grobgeld@gmail.com>
+from __future__ import annotations
import re
diff --git a/contrib/python/Pillow/py3/PIL/ImageOps.py b/contrib/python/Pillow/py3/PIL/ImageOps.py
index 42f2152b39..a9e626b2b2 100644
--- a/contrib/python/Pillow/py3/PIL/ImageOps.py
+++ b/contrib/python/Pillow/py3/PIL/ImageOps.py
@@ -16,6 +16,7 @@
#
# See the README file for information on usage and redistribution.
#
+from __future__ import annotations
import functools
import operator
@@ -56,7 +57,7 @@ def _lut(image, lut):
lut = lut + lut + lut
return image.point(lut)
else:
- msg = "not supported for this image mode"
+ msg = f"not supported for mode {image.mode}"
raise OSError(msg)
@@ -557,9 +558,7 @@ def invert(image):
:param image: The image to invert.
:return: An image.
"""
- lut = []
- for i in range(256):
- lut.append(255 - i)
+ lut = list(range(255, -1, -1))
return image.point(lut) if image.mode == "1" else _lut(image, lut)
@@ -581,10 +580,8 @@ def posterize(image, bits):
:param bits: The number of bits to keep for each channel (1-8).
:return: An image.
"""
- lut = []
mask = ~(2 ** (8 - bits) - 1)
- for i in range(256):
- lut.append(i & mask)
+ lut = [i & mask for i in range(256)]
return _lut(image, lut)
@@ -593,7 +590,7 @@ def solarize(image, threshold=128):
Invert all pixel values above a threshold.
:param image: The image to solarize.
- :param threshold: All pixels above this greyscale level are inverted.
+ :param threshold: All pixels above this grayscale level are inverted.
:return: An image.
"""
lut = []
diff --git a/contrib/python/Pillow/py3/PIL/ImagePalette.py b/contrib/python/Pillow/py3/PIL/ImagePalette.py
index f0c0947086..fbcfa309d2 100644
--- a/contrib/python/Pillow/py3/PIL/ImagePalette.py
+++ b/contrib/python/Pillow/py3/PIL/ImagePalette.py
@@ -15,6 +15,7 @@
#
# See the README file for information on usage and redistribution.
#
+from __future__ import annotations
import array
@@ -102,6 +103,30 @@ class ImagePalette:
# Declare tostring as an alias for tobytes
tostring = tobytes
+ def _new_color_index(self, image=None, e=None):
+ if not isinstance(self.palette, bytearray):
+ self._palette = bytearray(self.palette)
+ index = len(self.palette) // 3
+ special_colors = ()
+ if image:
+ special_colors = (
+ image.info.get("background"),
+ image.info.get("transparency"),
+ )
+ while index in special_colors:
+ index += 1
+ if index >= 256:
+ if image:
+ # Search for an unused index
+ for i, count in reversed(list(enumerate(image.histogram()))):
+ if count == 0 and i not in special_colors:
+ index = i
+ break
+ if index >= 256:
+ msg = "cannot allocate more than 256 colors"
+ raise ValueError(msg) from e
+ return index
+
def getcolor(self, color, image=None):
"""Given an rgb tuple, allocate palette entry.
@@ -124,27 +149,7 @@ class ImagePalette:
return self.colors[color]
except KeyError as e:
# allocate new color slot
- if not isinstance(self.palette, bytearray):
- self._palette = bytearray(self.palette)
- index = len(self.palette) // 3
- special_colors = ()
- if image:
- special_colors = (
- image.info.get("background"),
- image.info.get("transparency"),
- )
- while index in special_colors:
- index += 1
- if index >= 256:
- if image:
- # Search for an unused index
- for i, count in reversed(list(enumerate(image.histogram()))):
- if count == 0 and i not in special_colors:
- index = i
- break
- if index >= 256:
- msg = "cannot allocate more than 256 colors"
- raise ValueError(msg) from e
+ index = self._new_color_index(image, e)
self.colors[color] = index
if index * 3 < len(self.palette):
self._palette = (
@@ -200,20 +205,15 @@ def raw(rawmode, data):
def make_linear_lut(black, white):
- lut = []
if black == 0:
- for i in range(256):
- lut.append(white * i // 255)
- else:
- raise NotImplementedError # FIXME
- return lut
+ return [white * i // 255 for i in range(256)]
+
+ msg = "unavailable when black is non-zero"
+ raise NotImplementedError(msg) # FIXME
def make_gamma_lut(exp):
- lut = []
- for i in range(256):
- lut.append(int(((i / 255.0) ** exp) * 255.0 + 0.5))
- return lut
+ return [int(((i / 255.0) ** exp) * 255.0 + 0.5) for i in range(256)]
def negative(mode="RGB"):
@@ -225,9 +225,7 @@ def negative(mode="RGB"):
def random(mode="RGB"):
from random import randint
- palette = []
- for i in range(256 * len(mode)):
- palette.append(randint(0, 255))
+ palette = [randint(0, 255) for _ in range(256 * len(mode))]
return ImagePalette(mode, palette)
@@ -256,8 +254,6 @@ def load(filename):
if lut:
break
except (SyntaxError, ValueError):
- # import traceback
- # traceback.print_exc()
pass
else:
msg = "cannot load palette"
diff --git a/contrib/python/Pillow/py3/PIL/ImagePath.py b/contrib/python/Pillow/py3/PIL/ImagePath.py
index 3d3538c97b..77e8a609a5 100644
--- a/contrib/python/Pillow/py3/PIL/ImagePath.py
+++ b/contrib/python/Pillow/py3/PIL/ImagePath.py
@@ -13,6 +13,7 @@
#
# See the README file for information on usage and redistribution.
#
+from __future__ import annotations
from . import Image
diff --git a/contrib/python/Pillow/py3/PIL/ImageSequence.py b/contrib/python/Pillow/py3/PIL/ImageSequence.py
index c4bb6334ac..2c18502763 100644
--- a/contrib/python/Pillow/py3/PIL/ImageSequence.py
+++ b/contrib/python/Pillow/py3/PIL/ImageSequence.py
@@ -14,6 +14,11 @@
#
##
+from __future__ import annotations
+
+from typing import Callable
+
+from . import Image
class Iterator:
@@ -28,33 +33,38 @@ class Iterator:
:param im: An image object.
"""
- def __init__(self, im):
+ def __init__(self, im: Image.Image):
if not hasattr(im, "seek"):
msg = "im must have seek method"
raise AttributeError(msg)
self.im = im
self.position = getattr(self.im, "_min_frame", 0)
- def __getitem__(self, ix):
+ def __getitem__(self, ix: int) -> Image.Image:
try:
self.im.seek(ix)
return self.im
except EOFError as e:
- raise IndexError from e # end of sequence
+ msg = "end of sequence"
+ raise IndexError(msg) from e
- def __iter__(self):
+ def __iter__(self) -> Iterator:
return self
- def __next__(self):
+ def __next__(self) -> Image.Image:
try:
self.im.seek(self.position)
self.position += 1
return self.im
except EOFError as e:
- raise StopIteration from e
+ msg = "end of sequence"
+ raise StopIteration(msg) from e
-def all_frames(im, func=None):
+def all_frames(
+ im: Image.Image | list[Image.Image],
+ func: Callable[[Image.Image], Image.Image] | None = None,
+) -> list[Image.Image]:
"""
Applies a given function to all frames in an image or a list of images.
The frames are returned as a list of separate images.
diff --git a/contrib/python/Pillow/py3/PIL/ImageShow.py b/contrib/python/Pillow/py3/PIL/ImageShow.py
index 8b1c3f8bb6..fad3e09800 100644
--- a/contrib/python/Pillow/py3/PIL/ImageShow.py
+++ b/contrib/python/Pillow/py3/PIL/ImageShow.py
@@ -11,6 +11,8 @@
#
# See the README file for information on usage and redistribution.
#
+from __future__ import annotations
+
import os
import shutil
import subprocess
@@ -99,7 +101,8 @@ class Viewer:
Returns the command used to display the file.
Not implemented in the base class.
"""
- raise NotImplementedError
+ msg = "unavailable in base viewer"
+ raise NotImplementedError(msg)
def save_image(self, image):
"""Save to temporary file and return filename."""
diff --git a/contrib/python/Pillow/py3/PIL/ImageStat.py b/contrib/python/Pillow/py3/PIL/ImageStat.py
index b7ebddf066..13864e59cf 100644
--- a/contrib/python/Pillow/py3/PIL/ImageStat.py
+++ b/contrib/python/Pillow/py3/PIL/ImageStat.py
@@ -20,10 +20,9 @@
#
# See the README file for information on usage and redistribution.
#
+from __future__ import annotations
-import functools
import math
-import operator
class Stat:
@@ -53,26 +52,22 @@ class Stat:
"""Get min/max values for each band in the image"""
def minmax(histogram):
- n = 255
- x = 0
+ res_min, res_max = 255, 0
for i in range(256):
if histogram[i]:
- n = min(n, i)
- x = max(x, i)
- return n, x # returns (255, 0) if there's no data in the histogram
+ res_min = i
+ break
+ for i in range(255, -1, -1):
+ if histogram[i]:
+ res_max = i
+ break
+ return res_min, res_max
- v = []
- for i in range(0, len(self.h), 256):
- v.append(minmax(self.h[i:]))
- return v
+ return [minmax(self.h[i:]) for i in range(0, len(self.h), 256)]
def _getcount(self):
"""Get total number of pixels in each layer"""
-
- v = []
- for i in range(0, len(self.h), 256):
- v.append(functools.reduce(operator.add, self.h[i : i + 256]))
- return v
+ return [sum(self.h[i : i + 256]) for i in range(0, len(self.h), 256)]
def _getsum(self):
"""Get sum of all pixels in each layer"""
@@ -98,11 +93,7 @@ class Stat:
def _getmean(self):
"""Get average pixel level for each layer"""
-
- v = []
- for i in self.bands:
- v.append(self.sum[i] / self.count[i])
- return v
+ return [self.sum[i] / self.count[i] for i in self.bands]
def _getmedian(self):
"""Get median pixel level for each layer"""
@@ -121,28 +112,18 @@ class Stat:
def _getrms(self):
"""Get RMS for each layer"""
-
- v = []
- for i in self.bands:
- v.append(math.sqrt(self.sum2[i] / self.count[i]))
- return v
+ return [math.sqrt(self.sum2[i] / self.count[i]) for i in self.bands]
def _getvar(self):
"""Get variance for each layer"""
-
- v = []
- for i in self.bands:
- n = self.count[i]
- v.append((self.sum2[i] - (self.sum[i] ** 2.0) / n) / n)
- return v
+ return [
+ (self.sum2[i] - (self.sum[i] ** 2.0) / self.count[i]) / self.count[i]
+ for i in self.bands
+ ]
def _getstddev(self):
"""Get standard deviation for each layer"""
-
- v = []
- for i in self.bands:
- v.append(math.sqrt(self.var[i]))
- return v
+ return [math.sqrt(self.var[i]) for i in self.bands]
Global = Stat # compatibility
diff --git a/contrib/python/Pillow/py3/PIL/ImageTransform.py b/contrib/python/Pillow/py3/PIL/ImageTransform.py
index 7881f0d262..84c81f1844 100644
--- a/contrib/python/Pillow/py3/PIL/ImageTransform.py
+++ b/contrib/python/Pillow/py3/PIL/ImageTransform.py
@@ -12,18 +12,28 @@
#
# See the README file for information on usage and redistribution.
#
+from __future__ import annotations
+
+from typing import Sequence
from . import Image
class Transform(Image.ImageTransformHandler):
- def __init__(self, data):
+ method: Image.Transform
+
+ def __init__(self, data: Sequence[int]) -> None:
self.data = data
- def getdata(self):
+ def getdata(self) -> tuple[int, Sequence[int]]:
return self.method, self.data
- def transform(self, size, image, **options):
+ def transform(
+ self,
+ size: tuple[int, int],
+ image: Image.Image,
+ **options: dict[str, str | int | tuple[int, ...] | list[int]],
+ ) -> Image.Image:
# can be overridden
method, data = self.getdata()
return image.transform(size, method, data, **options)
diff --git a/contrib/python/Pillow/py3/PIL/ImageWin.py b/contrib/python/Pillow/py3/PIL/ImageWin.py
index ca9b14c8ad..75910d2d9a 100644
--- a/contrib/python/Pillow/py3/PIL/ImageWin.py
+++ b/contrib/python/Pillow/py3/PIL/ImageWin.py
@@ -16,6 +16,7 @@
#
# See the README file for information on usage and redistribution.
#
+from __future__ import annotations
from . import Image
@@ -54,9 +55,9 @@ class Dib:
"L", "P", or "RGB".
If the display requires a palette, this constructor creates a suitable
- palette and associates it with the image. For an "L" image, 128 greylevels
+ palette and associates it with the image. For an "L" image, 128 graylevels
are allocated. For an "RGB" image, a 6x6x6 colour cube is used, together
- with 20 greylevels.
+ with 20 graylevels.
To make sure that palettes work properly under Windows, you must call the
``palette`` method upon certain events from Windows.
diff --git a/contrib/python/Pillow/py3/PIL/ImtImagePlugin.py b/contrib/python/Pillow/py3/PIL/ImtImagePlugin.py
index d409fcd59d..7469c592dd 100644
--- a/contrib/python/Pillow/py3/PIL/ImtImagePlugin.py
+++ b/contrib/python/Pillow/py3/PIL/ImtImagePlugin.py
@@ -13,7 +13,7 @@
#
# See the README file for information on usage and redistribution.
#
-
+from __future__ import annotations
import re
diff --git a/contrib/python/Pillow/py3/PIL/IptcImagePlugin.py b/contrib/python/Pillow/py3/PIL/IptcImagePlugin.py
index 316cd17c73..4096094348 100644
--- a/contrib/python/Pillow/py3/PIL/IptcImagePlugin.py
+++ b/contrib/python/Pillow/py3/PIL/IptcImagePlugin.py
@@ -14,31 +14,50 @@
#
# See the README file for information on usage and redistribution.
#
-import os
-import tempfile
+from __future__ import annotations
+
+from io import BytesIO
+from typing import Sequence
from . import Image, ImageFile
-from ._binary import i8
from ._binary import i16be as i16
from ._binary import i32be as i32
-from ._binary import o8
+from ._deprecate import deprecate
COMPRESSION = {1: "raw", 5: "jpeg"}
-PAD = o8(0) * 4
+
+def __getattr__(name: str) -> bytes:
+ if name == "PAD":
+ deprecate("IptcImagePlugin.PAD", 12)
+ return b"\0\0\0\0"
+ msg = f"module '{__name__}' has no attribute '{name}'"
+ raise AttributeError(msg)
#
# Helpers
-def i(c):
- return i32((PAD + c)[-4:])
+def _i(c: bytes) -> int:
+ return i32((b"\0\0\0\0" + c)[-4:])
+
+
+def _i8(c: int | bytes) -> int:
+ return c if isinstance(c, int) else c[0]
+
+def i(c: bytes) -> int:
+ """.. deprecated:: 10.2.0"""
+ deprecate("IptcImagePlugin.i", 12)
+ return _i(c)
-def dump(c):
+
+def dump(c: Sequence[int | bytes]) -> None:
+ """.. deprecated:: 10.2.0"""
+ deprecate("IptcImagePlugin.dump", 12)
for i in c:
- print("%02x" % i8(i), end=" ")
+ print("%02x" % _i8(i), end=" ")
print()
@@ -51,10 +70,10 @@ class IptcImageFile(ImageFile.ImageFile):
format = "IPTC"
format_description = "IPTC/NAA"
- def getint(self, key):
- return i(self.info[key])
+ def getint(self, key: tuple[int, int]) -> int:
+ return _i(self.info[key])
- def field(self):
+ def field(self) -> tuple[tuple[int, int] | None, int]:
#
# get a IPTC field header
s = self.fp.read(5)
@@ -76,13 +95,13 @@ class IptcImageFile(ImageFile.ImageFile):
elif size == 128:
size = 0
elif size > 128:
- size = i(self.fp.read(size - 128))
+ size = _i(self.fp.read(size - 128))
else:
size = i16(s, 3)
return tag, size
- def _open(self):
+ def _open(self) -> None:
# load descriptive fields
while True:
offset = self.fp.tell()
@@ -102,10 +121,10 @@ class IptcImageFile(ImageFile.ImageFile):
self.info[tag] = tagdata
# mode
- layers = i8(self.info[(3, 60)][0])
- component = i8(self.info[(3, 60)][1])
+ layers = self.info[(3, 60)][0]
+ component = self.info[(3, 60)][1]
if (3, 65) in self.info:
- id = i8(self.info[(3, 65)][0]) - 1
+ id = self.info[(3, 65)][0] - 1
else:
id = 0
if layers == 1 and not component:
@@ -127,27 +146,22 @@ class IptcImageFile(ImageFile.ImageFile):
# tile
if tag == (8, 10):
- self.tile = [
- ("iptc", (compression, offset), (0, 0, self.size[0], self.size[1]))
- ]
+ self.tile = [("iptc", (0, 0) + self.size, offset, compression)]
def load(self):
if len(self.tile) != 1 or self.tile[0][0] != "iptc":
return ImageFile.ImageFile.load(self)
- type, tile, box = self.tile[0]
-
- encoding, offset = tile
+ offset, compression = self.tile[0][2:]
self.fp.seek(offset)
# Copy image data to temporary file
- o_fd, outfile = tempfile.mkstemp(text=False)
- o = os.fdopen(o_fd)
- if encoding == "raw":
+ o = BytesIO()
+ if compression == "raw":
# To simplify access to the extracted file,
# prepend a PPM header
- o.write("P5\n%d %d\n255\n" % self.size)
+ o.write(b"P5\n%d %d\n255\n" % self.size)
while True:
type, size = self.field()
if type != (8, 10):
@@ -158,17 +172,10 @@ class IptcImageFile(ImageFile.ImageFile):
break
o.write(s)
size -= len(s)
- o.close()
- try:
- with Image.open(outfile) as _im:
- _im.load()
- self.im = _im.im
- finally:
- try:
- os.unlink(outfile)
- except OSError:
- pass
+ with Image.open(o) as _im:
+ _im.load()
+ self.im = _im.im
Image.register_open(IptcImageFile.format, IptcImageFile)
@@ -184,8 +191,6 @@ def getiptcinfo(im):
:returns: A dictionary containing IPTC information, or None if
no IPTC information block was found.
"""
- import io
-
from . import JpegImagePlugin, TiffImagePlugin
data = None
@@ -220,7 +225,7 @@ def getiptcinfo(im):
# parse the IPTC information chunk
im.info = {}
- im.fp = io.BytesIO(data)
+ im.fp = BytesIO(data)
try:
im._open()
diff --git a/contrib/python/Pillow/py3/PIL/Jpeg2KImagePlugin.py b/contrib/python/Pillow/py3/PIL/Jpeg2KImagePlugin.py
index 963d6c1a31..4b778a0d33 100644
--- a/contrib/python/Pillow/py3/PIL/Jpeg2KImagePlugin.py
+++ b/contrib/python/Pillow/py3/PIL/Jpeg2KImagePlugin.py
@@ -13,6 +13,8 @@
#
# See the README file for information on usage and redistribution.
#
+from __future__ import annotations
+
import io
import os
import struct
@@ -334,10 +336,7 @@ def _save(im, fp, filename):
if quality_layers is not None and not (
isinstance(quality_layers, (list, tuple))
and all(
- [
- isinstance(quality_layer, (int, float))
- for quality_layer in quality_layers
- ]
+ isinstance(quality_layer, (int, float)) for quality_layer in quality_layers
)
):
msg = "quality_layers must be a sequence of numbers"
diff --git a/contrib/python/Pillow/py3/PIL/JpegImagePlugin.py b/contrib/python/Pillow/py3/PIL/JpegImagePlugin.py
index 917bbf39fb..81b8749a33 100644
--- a/contrib/python/Pillow/py3/PIL/JpegImagePlugin.py
+++ b/contrib/python/Pillow/py3/PIL/JpegImagePlugin.py
@@ -31,6 +31,8 @@
#
# See the README file for information on usage and redistribution.
#
+from __future__ import annotations
+
import array
import io
import math
@@ -85,10 +87,12 @@ def APP(self, marker):
self.info["dpi"] = jfif_density
self.info["jfif_unit"] = jfif_unit
self.info["jfif_density"] = jfif_density
- elif marker == 0xFFE1 and s[:5] == b"Exif\0":
- if "exif" not in self.info:
- # extract EXIF information (incomplete)
- self.info["exif"] = s # FIXME: value will change
+ elif marker == 0xFFE1 and s[:6] == b"Exif\0\0":
+ # extract EXIF information
+ if "exif" in self.info:
+ self.info["exif"] += s[6:]
+ else:
+ self.info["exif"] = s
self._exif_offset = self.fp.tell() - n + 6
elif marker == 0xFFE2 and s[:5] == b"FPXR\0":
# extract FlashPix information (incomplete)
@@ -165,7 +169,8 @@ def APP(self, marker):
except TypeError:
dpi = x_resolution
if math.isnan(dpi):
- raise ValueError
+ msg = "DPI is not a number"
+ raise ValueError(msg)
if resolution_unit == 3: # cm
# 1 dpcm = 2.54 dpi
dpi *= 2.54
@@ -232,9 +237,7 @@ def SOF(self, marker):
# fixup icc profile
self.icclist.sort() # sort by sequence number
if self.icclist[0][13] == len(self.icclist):
- profile = []
- for p in self.icclist:
- profile.append(p[14:])
+ profile = [p[14:] for p in self.icclist]
icc_profile = b"".join(profile)
else:
icc_profile = None # wrong number of fragments
@@ -396,7 +399,7 @@ class JpegImageFile(ImageFile.ImageFile):
# self.__offset = self.fp.tell()
break
s = self.fp.read(1)
- elif i == 0 or i == 0xFFFF:
+ elif i in {0, 0xFFFF}:
# padded marker or junk; move on
s = b"\xff"
elif i == 0xFF00: # Skip extraneous data (escaped 0xFF)
@@ -719,7 +722,8 @@ def _save(im, fp, filename):
for idx, table in enumerate(qtables):
try:
if len(table) != 64:
- raise TypeError
+ msg = "Invalid quantization table"
+ raise TypeError(msg)
table = array.array("H", table)
except TypeError as e:
msg = "Invalid quantization table"
@@ -781,10 +785,13 @@ def _save(im, fp, filename):
progressive,
info.get("smooth", 0),
optimize,
+ info.get("keep_rgb", False),
info.get("streamtype", 0),
dpi[0],
dpi[1],
subsampling,
+ info.get("restart_marker_blocks", 0),
+ info.get("restart_marker_rows", 0),
qtables,
comment,
extra,
diff --git a/contrib/python/Pillow/py3/PIL/JpegPresets.py b/contrib/python/Pillow/py3/PIL/JpegPresets.py
index a678e248e9..9ecfdb2599 100644
--- a/contrib/python/Pillow/py3/PIL/JpegPresets.py
+++ b/contrib/python/Pillow/py3/PIL/JpegPresets.py
@@ -62,6 +62,7 @@ Libjpeg ref.:
https://web.archive.org/web/20120328125543/http://www.jpegcameras.com/libjpeg/libjpeg-3.html
"""
+from __future__ import annotations
# fmt: off
presets = {
diff --git a/contrib/python/Pillow/py3/PIL/McIdasImagePlugin.py b/contrib/python/Pillow/py3/PIL/McIdasImagePlugin.py
index bb79e71de5..9a85c0d15b 100644
--- a/contrib/python/Pillow/py3/PIL/McIdasImagePlugin.py
+++ b/contrib/python/Pillow/py3/PIL/McIdasImagePlugin.py
@@ -15,6 +15,7 @@
#
# See the README file for information on usage and redistribution.
#
+from __future__ import annotations
import struct
diff --git a/contrib/python/Pillow/py3/PIL/MicImagePlugin.py b/contrib/python/Pillow/py3/PIL/MicImagePlugin.py
index 801318930d..f4529d9ae7 100644
--- a/contrib/python/Pillow/py3/PIL/MicImagePlugin.py
+++ b/contrib/python/Pillow/py3/PIL/MicImagePlugin.py
@@ -15,7 +15,7 @@
#
# See the README file for information on usage and redistribution.
#
-
+from __future__ import annotations
import olefile
@@ -51,10 +51,11 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
# find ACI subfiles with Image members (maybe not the
# best way to identify MIC files, but what the... ;-)
- self.images = []
- for path in self.ole.listdir():
- if path[1:] and path[0][-4:] == ".ACI" and path[1] == "Image":
- self.images.append(path)
+ self.images = [
+ path
+ for path in self.ole.listdir()
+ if path[1:] and path[0][-4:] == ".ACI" and path[1] == "Image"
+ ]
# if we didn't find any images, this is probably not
# an MIC file.
@@ -66,6 +67,7 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
self._n_frames = len(self.images)
self.is_animated = self._n_frames > 1
+ self.__fp = self.fp
self.seek(0)
def seek(self, frame):
@@ -87,10 +89,12 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
return self.frame
def close(self):
+ self.__fp.close()
self.ole.close()
super().close()
def __exit__(self, *args):
+ self.__fp.close()
self.ole.close()
super().__exit__()
diff --git a/contrib/python/Pillow/py3/PIL/MpegImagePlugin.py b/contrib/python/Pillow/py3/PIL/MpegImagePlugin.py
index bfa88fe99c..f4e598ca3a 100644
--- a/contrib/python/Pillow/py3/PIL/MpegImagePlugin.py
+++ b/contrib/python/Pillow/py3/PIL/MpegImagePlugin.py
@@ -12,7 +12,7 @@
#
# See the README file for information on usage and redistribution.
#
-
+from __future__ import annotations
from . import Image, ImageFile
from ._binary import i8
diff --git a/contrib/python/Pillow/py3/PIL/MpoImagePlugin.py b/contrib/python/Pillow/py3/PIL/MpoImagePlugin.py
index f9261c77d6..199a100904 100644
--- a/contrib/python/Pillow/py3/PIL/MpoImagePlugin.py
+++ b/contrib/python/Pillow/py3/PIL/MpoImagePlugin.py
@@ -17,6 +17,7 @@
#
# See the README file for information on usage and redistribution.
#
+from __future__ import annotations
import itertools
import os
@@ -33,9 +34,6 @@ from . import (
from ._binary import i16be as i16
from ._binary import o32le
-# def _accept(prefix):
-# return JpegImagePlugin._accept(prefix)
-
def _save(im, fp, filename):
JpegImagePlugin._save(im, fp, filename)
diff --git a/contrib/python/Pillow/py3/PIL/MspImagePlugin.py b/contrib/python/Pillow/py3/PIL/MspImagePlugin.py
index 3f3609f1c2..77dac65b6b 100644
--- a/contrib/python/Pillow/py3/PIL/MspImagePlugin.py
+++ b/contrib/python/Pillow/py3/PIL/MspImagePlugin.py
@@ -22,6 +22,7 @@
# Figure 206. Windows Paint Version 2: "LinS" Format. Used in Windows V2.03
#
# See also: https://www.fileformat.info/format/mspaint/egff.htm
+from __future__ import annotations
import io
import struct
diff --git a/contrib/python/Pillow/py3/PIL/PSDraw.py b/contrib/python/Pillow/py3/PIL/PSDraw.py
index 13b3048f67..848fc2f716 100644
--- a/contrib/python/Pillow/py3/PIL/PSDraw.py
+++ b/contrib/python/Pillow/py3/PIL/PSDraw.py
@@ -14,6 +14,7 @@
#
# See the README file for information on usage and redistribution.
#
+from __future__ import annotations
import sys
@@ -109,7 +110,7 @@ class PSDraw:
if im.mode == "1":
dpi = 200 # fax
else:
- dpi = 100 # greyscale
+ dpi = 100 # grayscale
# image size (on paper)
x = im.size[0] * 72 / dpi
y = im.size[1] * 72 / dpi
diff --git a/contrib/python/Pillow/py3/PIL/PaletteFile.py b/contrib/python/Pillow/py3/PIL/PaletteFile.py
index 4a2c497fc4..dc31754020 100644
--- a/contrib/python/Pillow/py3/PIL/PaletteFile.py
+++ b/contrib/python/Pillow/py3/PIL/PaletteFile.py
@@ -12,6 +12,7 @@
#
# See the README file for information on usage and redistribution.
#
+from __future__ import annotations
from ._binary import o8
diff --git a/contrib/python/Pillow/py3/PIL/PalmImagePlugin.py b/contrib/python/Pillow/py3/PIL/PalmImagePlugin.py
index a88a907917..65be7fef7b 100644
--- a/contrib/python/Pillow/py3/PIL/PalmImagePlugin.py
+++ b/contrib/python/Pillow/py3/PIL/PalmImagePlugin.py
@@ -6,6 +6,7 @@
##
# Image plugin for Palm pixmap images (output only).
##
+from __future__ import annotations
from . import Image, ImageFile
from ._binary import o8
@@ -124,7 +125,7 @@ def _save(im, fp, filename):
if im.encoderinfo.get("bpp") in (1, 2, 4):
# this is 8-bit grayscale, so we shift it to get the high-order bits,
# and invert it because
- # Palm does greyscale from white (0) to black (1)
+ # Palm does grayscale from white (0) to black (1)
bpp = im.encoderinfo["bpp"]
im = im.point(
lambda x, shift=8 - bpp, maxval=(1 << bpp) - 1: maxval - (x >> shift)
diff --git a/contrib/python/Pillow/py3/PIL/PcdImagePlugin.py b/contrib/python/Pillow/py3/PIL/PcdImagePlugin.py
index c7cbca8c5d..a0515b302e 100644
--- a/contrib/python/Pillow/py3/PIL/PcdImagePlugin.py
+++ b/contrib/python/Pillow/py3/PIL/PcdImagePlugin.py
@@ -13,7 +13,7 @@
#
# See the README file for information on usage and redistribution.
#
-
+from __future__ import annotations
from . import Image, ImageFile
diff --git a/contrib/python/Pillow/py3/PIL/PcfFontFile.py b/contrib/python/Pillow/py3/PIL/PcfFontFile.py
index 8db5822fe7..0d1968b140 100644
--- a/contrib/python/Pillow/py3/PIL/PcfFontFile.py
+++ b/contrib/python/Pillow/py3/PIL/PcfFontFile.py
@@ -15,8 +15,10 @@
#
# See the README file for information on usage and redistribution.
#
+from __future__ import annotations
import io
+from typing import BinaryIO, Callable
from . import FontFile, Image
from ._binary import i8
@@ -40,7 +42,7 @@ PCF_SWIDTHS = 1 << 6
PCF_GLYPH_NAMES = 1 << 7
PCF_BDF_ACCELERATORS = 1 << 8
-BYTES_PER_ROW = [
+BYTES_PER_ROW: list[Callable[[int], int]] = [
lambda bits: ((bits + 7) >> 3),
lambda bits: ((bits + 15) >> 3) & ~1,
lambda bits: ((bits + 31) >> 3) & ~3,
@@ -48,7 +50,7 @@ BYTES_PER_ROW = [
]
-def sz(s, o):
+def sz(s: bytes, o: int) -> bytes:
return s[o : s.index(b"\0", o)]
@@ -57,7 +59,7 @@ class PcfFontFile(FontFile.FontFile):
name = "name"
- def __init__(self, fp, charset_encoding="iso8859-1"):
+ def __init__(self, fp: BinaryIO, charset_encoding: str = "iso8859-1"):
self.charset_encoding = charset_encoding
magic = l32(fp.read(4))
@@ -103,7 +105,9 @@ class PcfFontFile(FontFile.FontFile):
bitmaps[ix],
)
- def _getformat(self, tag):
+ def _getformat(
+ self, tag: int
+ ) -> tuple[BinaryIO, int, Callable[[bytes], int], Callable[[bytes], int]]:
format, size, offset = self.toc[tag]
fp = self.fp
@@ -118,7 +122,7 @@ class PcfFontFile(FontFile.FontFile):
return fp, format, i16, i32
- def _load_properties(self):
+ def _load_properties(self) -> dict[bytes, bytes | int]:
#
# font properties
@@ -129,27 +133,24 @@ class PcfFontFile(FontFile.FontFile):
nprops = i32(fp.read(4))
# read property description
- p = []
- for i in range(nprops):
- p.append((i32(fp.read(4)), i8(fp.read(1)), i32(fp.read(4))))
+ p = [(i32(fp.read(4)), i8(fp.read(1)), i32(fp.read(4))) for _ in range(nprops)]
+
if nprops & 3:
fp.seek(4 - (nprops & 3), io.SEEK_CUR) # pad
data = fp.read(i32(fp.read(4)))
for k, s, v in p:
- k = sz(data, k)
- if s:
- v = sz(data, v)
- properties[k] = v
+ property_value: bytes | int = sz(data, v) if s else v
+ properties[sz(data, k)] = property_value
return properties
- def _load_metrics(self):
+ def _load_metrics(self) -> list[tuple[int, int, int, int, int, int, int, int]]:
#
# font metrics
- metrics = []
+ metrics: list[tuple[int, int, int, int, int, int, int, int]] = []
fp, format, i16, i32 = self._getformat(PCF_METRICS)
@@ -182,12 +183,12 @@ class PcfFontFile(FontFile.FontFile):
return metrics
- def _load_bitmaps(self, metrics):
+ def _load_bitmaps(
+ self, metrics: list[tuple[int, int, int, int, int, int, int, int]]
+ ) -> list[Image.Image]:
#
# bitmap data
- bitmaps = []
-
fp, format, i16, i32 = self._getformat(PCF_BITMAPS)
nbitmaps = i32(fp.read(4))
@@ -196,13 +197,9 @@ class PcfFontFile(FontFile.FontFile):
msg = "Wrong number of bitmaps"
raise OSError(msg)
- offsets = []
- for i in range(nbitmaps):
- offsets.append(i32(fp.read(4)))
+ offsets = [i32(fp.read(4)) for _ in range(nbitmaps)]
- bitmap_sizes = []
- for i in range(4):
- bitmap_sizes.append(i32(fp.read(4)))
+ bitmap_sizes = [i32(fp.read(4)) for _ in range(4)]
# byteorder = format & 4 # non-zero => MSB
bitorder = format & 8 # non-zero => MSB
@@ -218,6 +215,7 @@ class PcfFontFile(FontFile.FontFile):
if bitorder:
mode = "1"
+ bitmaps = []
for i in range(nbitmaps):
xsize, ysize = metrics[i][:2]
b, e = offsets[i : i + 2]
@@ -227,7 +225,7 @@ class PcfFontFile(FontFile.FontFile):
return bitmaps
- def _load_encoding(self):
+ def _load_encoding(self) -> list[int | None]:
fp, format, i16, i32 = self._getformat(PCF_BDF_ENCODINGS)
first_col, last_col = i16(fp.read(2)), i16(fp.read(2))
@@ -238,7 +236,7 @@ class PcfFontFile(FontFile.FontFile):
nencoding = (last_col - first_col + 1) * (last_row - first_row + 1)
# map character code to bitmap index
- encoding = [None] * min(256, nencoding)
+ encoding: list[int | None] = [None] * min(256, nencoding)
encoding_offsets = [i16(fp.read(2)) for _ in range(nencoding)]
diff --git a/contrib/python/Pillow/py3/PIL/PcxImagePlugin.py b/contrib/python/Pillow/py3/PIL/PcxImagePlugin.py
index 854d9e83ee..98ecefd051 100644
--- a/contrib/python/Pillow/py3/PIL/PcxImagePlugin.py
+++ b/contrib/python/Pillow/py3/PIL/PcxImagePlugin.py
@@ -24,6 +24,7 @@
#
# See the README file for information on usage and redistribution.
#
+from __future__ import annotations
import io
import logging
@@ -91,7 +92,7 @@ class PcxImageFile(ImageFile.ImageFile):
self.fp.seek(-769, io.SEEK_END)
s = self.fp.read(769)
if len(s) == 769 and s[0] == 12:
- # check if the palette is linear greyscale
+ # check if the palette is linear grayscale
for i in range(256):
if s[i * 3 + 1 : i * 3 + 4] != o8(i) * 3:
mode = rawmode = "P"
@@ -203,7 +204,7 @@ def _save(im, fp, filename):
palette += b"\x00" * (768 - len(palette))
fp.write(palette) # 768 bytes
elif im.mode == "L":
- # greyscale palette
+ # grayscale palette
fp.write(o8(12))
for i in range(256):
fp.write(o8(i) * 3)
diff --git a/contrib/python/Pillow/py3/PIL/PdfImagePlugin.py b/contrib/python/Pillow/py3/PIL/PdfImagePlugin.py
index 09fc0c7e6c..3506aadce8 100644
--- a/contrib/python/Pillow/py3/PIL/PdfImagePlugin.py
+++ b/contrib/python/Pillow/py3/PIL/PdfImagePlugin.py
@@ -19,6 +19,7 @@
##
# Image plugin for PDF images (output only).
##
+from __future__ import annotations
import io
import math
@@ -96,7 +97,7 @@ def _write_image(im, filename, existing_pdf, image_refs):
dict_obj["ColorSpace"] = [
PdfParser.PdfName("Indexed"),
PdfParser.PdfName("DeviceRGB"),
- 255,
+ len(palette) // 3 - 1,
PdfParser.PdfBinary(palette),
]
procset = "ImageI" # indexed color
diff --git a/contrib/python/Pillow/py3/PIL/PdfParser.py b/contrib/python/Pillow/py3/PIL/PdfParser.py
index dc1012f54d..0144600066 100644
--- a/contrib/python/Pillow/py3/PIL/PdfParser.py
+++ b/contrib/python/Pillow/py3/PIL/PdfParser.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import calendar
import codecs
import collections
@@ -82,7 +84,7 @@ class IndirectReference(
collections.namedtuple("IndirectReferenceTuple", ["object_id", "generation"])
):
def __str__(self):
- return "%s %s R" % self
+ return f"{self.object_id} {self.generation} R"
def __bytes__(self):
return self.__str__().encode("us-ascii")
@@ -103,7 +105,7 @@ class IndirectReference(
class IndirectObjectDef(IndirectReference):
def __str__(self):
- return "%s %s obj" % self
+ return f"{self.object_id} {self.generation} obj"
class XrefTable:
diff --git a/contrib/python/Pillow/py3/PIL/PixarImagePlugin.py b/contrib/python/Pillow/py3/PIL/PixarImagePlugin.py
index 850272311d..af866feb36 100644
--- a/contrib/python/Pillow/py3/PIL/PixarImagePlugin.py
+++ b/contrib/python/Pillow/py3/PIL/PixarImagePlugin.py
@@ -18,6 +18,7 @@
#
# See the README file for information on usage and redistribution.
#
+from __future__ import annotations
from . import Image, ImageFile
from ._binary import i16le as i16
diff --git a/contrib/python/Pillow/py3/PIL/PngImagePlugin.py b/contrib/python/Pillow/py3/PIL/PngImagePlugin.py
index 5e5a8cf6a2..e4ed938801 100644
--- a/contrib/python/Pillow/py3/PIL/PngImagePlugin.py
+++ b/contrib/python/Pillow/py3/PIL/PngImagePlugin.py
@@ -30,6 +30,7 @@
#
# See the README file for information on usage and redistribution.
#
+from __future__ import annotations
import itertools
import logging
@@ -56,7 +57,7 @@ _MAGIC = b"\211PNG\r\n\032\n"
_MODES = {
# supported bits/color combinations, and corresponding modes/rawmodes
- # Greyscale
+ # Grayscale
(1, 0): ("1", "1"),
(2, 0): ("L", "L;2"),
(4, 0): ("L", "L;4"),
@@ -70,7 +71,7 @@ _MODES = {
(2, 3): ("P", "P;2"),
(4, 3): ("P", "P;4"),
(8, 3): ("P", "P"),
- # Greyscale with alpha
+ # Grayscale with alpha
(8, 4): ("LA", "LA"),
(16, 4): ("RGBA", "LA;16B"), # LA;16B->LA not yet available
# Truecolour with alpha
@@ -438,11 +439,12 @@ class PngStream(ChunkStream):
tile = [("zip", (0, 0) + self.im_size, pos, self.im_rawmode)]
self.im_tile = tile
self.im_idat = length
- raise EOFError
+ msg = "image data found"
+ raise EOFError(msg)
def chunk_IEND(self, pos, length):
- # end of PNG image
- raise EOFError
+ msg = "end of PNG image"
+ raise EOFError(msg)
def chunk_PLTE(self, pos, length):
# palette
@@ -891,7 +893,8 @@ class PngImageFile(ImageFile.ImageFile):
self.dispose_extent = self.info.get("bbox")
if not self.tile:
- raise EOFError
+ msg = "image not found in APNG frame"
+ raise EOFError(msg)
# setup frame disposal (actual disposal done when needed in the next _seek())
if self._prev_im is None and self.dispose_op == Disposal.OP_PREVIOUS:
@@ -1154,6 +1157,9 @@ def _write_multiple_frames(im, fp, chunk, rawmode, default_image, append_images)
encoderinfo["duration"] = duration
im_frames.append({"im": im_frame, "bbox": bbox, "encoderinfo": encoderinfo})
+ if len(im_frames) == 1 and not default_image:
+ return im_frames[0]["im"]
+
# animation control
chunk(
fp,
@@ -1389,8 +1395,10 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False):
chunk(fp, b"eXIf", exif)
if save_all:
- _write_multiple_frames(im, fp, chunk, rawmode, default_image, append_images)
- else:
+ im = _write_multiple_frames(
+ im, fp, chunk, rawmode, default_image, append_images
+ )
+ if im:
ImageFile._save(im, _idat(fp, chunk), [("zip", (0, 0) + im.size, 0, rawmode)])
if info:
diff --git a/contrib/python/Pillow/py3/PIL/PpmImagePlugin.py b/contrib/python/Pillow/py3/PIL/PpmImagePlugin.py
index e480ab0558..25dbfa5b0b 100644
--- a/contrib/python/Pillow/py3/PIL/PpmImagePlugin.py
+++ b/contrib/python/Pillow/py3/PIL/PpmImagePlugin.py
@@ -13,7 +13,7 @@
#
# See the README file for information on usage and redistribution.
#
-
+from __future__ import annotations
from . import Image, ImageFile
from ._binary import i16be as i16
@@ -328,9 +328,6 @@ def _save(im, fp, filename):
fp.write(b"65535\n")
ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, 1))])
- # ALTERNATIVE: save via builtin debug function
- # im._dump(filename)
-
#
# --------------------------------------------------------------------
diff --git a/contrib/python/Pillow/py3/PIL/PsdImagePlugin.py b/contrib/python/Pillow/py3/PIL/PsdImagePlugin.py
index 2f019bb8c3..5cff564137 100644
--- a/contrib/python/Pillow/py3/PIL/PsdImagePlugin.py
+++ b/contrib/python/Pillow/py3/PIL/PsdImagePlugin.py
@@ -15,6 +15,7 @@
#
# See the README file for information on usage and redistribution.
#
+from __future__ import annotations
import io
@@ -186,6 +187,9 @@ def _layerinfo(fp, ct_bytes):
ct_types = i16(read(2))
types = list(range(ct_types))
if len(types) > 4:
+ fp.seek(len(types) * 6 + 12, io.SEEK_CUR)
+ size = i32(read(4))
+ fp.seek(size, io.SEEK_CUR)
continue
for _ in types:
diff --git a/contrib/python/Pillow/py3/PIL/PyAccess.py b/contrib/python/Pillow/py3/PIL/PyAccess.py
index 99b46a4a66..07bb712d83 100644
--- a/contrib/python/Pillow/py3/PIL/PyAccess.py
+++ b/contrib/python/Pillow/py3/PIL/PyAccess.py
@@ -18,6 +18,7 @@
# * Fill.c uses the integer form, but it's still going to use the old
# Access.c implementation.
#
+from __future__ import annotations
import logging
import sys
@@ -42,7 +43,7 @@ except ImportError as ex:
# anything in core.
from ._util import DeferredError
- FFI = ffi = DeferredError(ex)
+ FFI = ffi = DeferredError.new(ex)
logger = logging.getLogger(__name__)
@@ -244,7 +245,7 @@ class _PyAccessI16_L(PyAccess):
except TypeError:
color = min(color[0], 65535)
- pixel.l = color & 0xFF # noqa: E741
+ pixel.l = color & 0xFF
pixel.r = color >> 8
@@ -265,7 +266,7 @@ class _PyAccessI16_B(PyAccess):
except Exception:
color = min(color[0], 65535)
- pixel.l = color >> 8 # noqa: E741
+ pixel.l = color >> 8
pixel.r = color & 0xFF
diff --git a/contrib/python/Pillow/py3/PIL/QoiImagePlugin.py b/contrib/python/Pillow/py3/PIL/QoiImagePlugin.py
index 66344faac5..a7b9d4a9e3 100644
--- a/contrib/python/Pillow/py3/PIL/QoiImagePlugin.py
+++ b/contrib/python/Pillow/py3/PIL/QoiImagePlugin.py
@@ -5,6 +5,7 @@
#
# See the README file for information on usage and redistribution.
#
+from __future__ import annotations
import os
diff --git a/contrib/python/Pillow/py3/PIL/SgiImagePlugin.py b/contrib/python/Pillow/py3/PIL/SgiImagePlugin.py
index acb9ce5a38..f9a10f6109 100644
--- a/contrib/python/Pillow/py3/PIL/SgiImagePlugin.py
+++ b/contrib/python/Pillow/py3/PIL/SgiImagePlugin.py
@@ -20,7 +20,7 @@
#
# See the README file for information on usage and redistribution.
#
-
+from __future__ import annotations
import os
import struct
@@ -123,7 +123,7 @@ class SgiImageFile(ImageFile.ImageFile):
def _save(im, fp, filename):
- if im.mode != "RGB" and im.mode != "RGBA" and im.mode != "L":
+ if im.mode not in {"RGB", "RGBA", "L"}:
msg = "Unsupported SGI image mode"
raise ValueError(msg)
@@ -155,7 +155,7 @@ def _save(im, fp, filename):
# Z Dimension: Number of channels
z = len(im.mode)
- if dim == 1 or dim == 2:
+ if dim in {1, 2}:
z = 1
# assert we've got the right number of bands.
diff --git a/contrib/python/Pillow/py3/PIL/SpiderImagePlugin.py b/contrib/python/Pillow/py3/PIL/SpiderImagePlugin.py
index 408b982b51..86582fb128 100644
--- a/contrib/python/Pillow/py3/PIL/SpiderImagePlugin.py
+++ b/contrib/python/Pillow/py3/PIL/SpiderImagePlugin.py
@@ -32,6 +32,8 @@
# Details about the Spider image format:
# https://spider.wadsworth.org/spider_doc/spider/docs/image_doc.html
#
+from __future__ import annotations
+
import os
import struct
import sys
@@ -238,9 +240,7 @@ def makeSpiderHeader(im):
if nvalues < 23:
return []
- hdr = []
- for i in range(nvalues):
- hdr.append(0.0)
+ hdr = [0.0] * nvalues
# NB these are Fortran indices
hdr[1] = 1.0 # nslice (=1 for an image)
diff --git a/contrib/python/Pillow/py3/PIL/SunImagePlugin.py b/contrib/python/Pillow/py3/PIL/SunImagePlugin.py
index 6a8d5d86b7..11ce3dfefd 100644
--- a/contrib/python/Pillow/py3/PIL/SunImagePlugin.py
+++ b/contrib/python/Pillow/py3/PIL/SunImagePlugin.py
@@ -15,7 +15,7 @@
#
# See the README file for information on usage and redistribution.
#
-
+from __future__ import annotations
from . import Image, ImageFile, ImagePalette
from ._binary import i32be as i32
diff --git a/contrib/python/Pillow/py3/PIL/TarIO.py b/contrib/python/Pillow/py3/PIL/TarIO.py
index 32928f6af3..7470663b4a 100644
--- a/contrib/python/Pillow/py3/PIL/TarIO.py
+++ b/contrib/python/Pillow/py3/PIL/TarIO.py
@@ -13,16 +13,18 @@
#
# See the README file for information on usage and redistribution.
#
+from __future__ import annotations
import io
+from types import TracebackType
from . import ContainerIO
-class TarIO(ContainerIO.ContainerIO):
+class TarIO(ContainerIO.ContainerIO[bytes]):
"""A file object that provides read access to a given member of a TAR file."""
- def __init__(self, tarfile, file):
+ def __init__(self, tarfile: str, file: str) -> None:
"""
Create file object.
@@ -56,11 +58,16 @@ class TarIO(ContainerIO.ContainerIO):
super().__init__(self.fh, self.fh.tell(), size)
# Context manager support
- def __enter__(self):
+ def __enter__(self) -> TarIO:
return self
- def __exit__(self, *args):
+ def __exit__(
+ self,
+ exc_type: type[BaseException] | None,
+ exc_val: BaseException | None,
+ exc_tb: TracebackType | None,
+ ) -> None:
self.close()
- def close(self):
+ def close(self) -> None:
self.fh.close()
diff --git a/contrib/python/Pillow/py3/PIL/TgaImagePlugin.py b/contrib/python/Pillow/py3/PIL/TgaImagePlugin.py
index f24ee4f5c3..65c7484f75 100644
--- a/contrib/python/Pillow/py3/PIL/TgaImagePlugin.py
+++ b/contrib/python/Pillow/py3/PIL/TgaImagePlugin.py
@@ -15,7 +15,7 @@
#
# See the README file for information on usage and redistribution.
#
-
+from __future__ import annotations
import warnings
diff --git a/contrib/python/Pillow/py3/PIL/TiffImagePlugin.py b/contrib/python/Pillow/py3/PIL/TiffImagePlugin.py
index dabf8dbfb5..e20d4d5ea8 100644
--- a/contrib/python/Pillow/py3/PIL/TiffImagePlugin.py
+++ b/contrib/python/Pillow/py3/PIL/TiffImagePlugin.py
@@ -38,6 +38,8 @@
#
# See the README file for information on usage and redistribution.
#
+from __future__ import annotations
+
import io
import itertools
import logging
@@ -1702,25 +1704,27 @@ def _save(im, fp, filename):
colormap += [0] * (256 - colors)
ifd[COLORMAP] = colormap
# data orientation
- stride = len(bits) * ((im.size[0] * bits[0] + 7) // 8)
- # aim for given strip size (64 KB by default) when using libtiff writer
- if libtiff:
- im_strip_size = encoderinfo.get("strip_size", STRIP_SIZE)
- rows_per_strip = 1 if stride == 0 else min(im_strip_size // stride, im.size[1])
- # JPEG encoder expects multiple of 8 rows
- if compression == "jpeg":
- rows_per_strip = min(((rows_per_strip + 7) // 8) * 8, im.size[1])
- else:
- rows_per_strip = im.size[1]
- if rows_per_strip == 0:
- rows_per_strip = 1
- strip_byte_counts = 1 if stride == 0 else stride * rows_per_strip
- strips_per_image = (im.size[1] + rows_per_strip - 1) // rows_per_strip
- ifd[ROWSPERSTRIP] = rows_per_strip
+ w, h = ifd[IMAGEWIDTH], ifd[IMAGELENGTH]
+ stride = len(bits) * ((w * bits[0] + 7) // 8)
+ if ROWSPERSTRIP not in ifd:
+ # aim for given strip size (64 KB by default) when using libtiff writer
+ if libtiff:
+ im_strip_size = encoderinfo.get("strip_size", STRIP_SIZE)
+ rows_per_strip = 1 if stride == 0 else min(im_strip_size // stride, h)
+ # JPEG encoder expects multiple of 8 rows
+ if compression == "jpeg":
+ rows_per_strip = min(((rows_per_strip + 7) // 8) * 8, h)
+ else:
+ rows_per_strip = h
+ if rows_per_strip == 0:
+ rows_per_strip = 1
+ ifd[ROWSPERSTRIP] = rows_per_strip
+ strip_byte_counts = 1 if stride == 0 else stride * ifd[ROWSPERSTRIP]
+ strips_per_image = (h + ifd[ROWSPERSTRIP] - 1) // ifd[ROWSPERSTRIP]
if strip_byte_counts >= 2**16:
ifd.tagtype[STRIPBYTECOUNTS] = TiffTags.LONG
ifd[STRIPBYTECOUNTS] = (strip_byte_counts,) * (strips_per_image - 1) + (
- stride * im.size[1] - strip_byte_counts * (strips_per_image - 1),
+ stride * h - strip_byte_counts * (strips_per_image - 1),
)
ifd[STRIPOFFSETS] = tuple(
range(0, strip_byte_counts * strips_per_image, strip_byte_counts)
@@ -1885,13 +1889,14 @@ class AppendingTiffWriter:
8, # long8
]
- # StripOffsets = 273
- # FreeOffsets = 288
- # TileOffsets = 324
- # JPEGQTables = 519
- # JPEGDCTables = 520
- # JPEGACTables = 521
- Tags = {273, 288, 324, 519, 520, 521}
+ Tags = {
+ 273, # StripOffsets
+ 288, # FreeOffsets
+ 324, # TileOffsets
+ 519, # JPEGQTables
+ 520, # JPEGDCTables
+ 521, # JPEGACTables
+ }
def __init__(self, fn, new=False):
if hasattr(fn, "read"):
@@ -1941,8 +1946,6 @@ class AppendingTiffWriter:
iimm = self.f.read(4)
if not iimm:
- # msg = "nothing written into new page"
- # raise RuntimeError(msg)
# Make it easy to finish a frame without committing to a new one.
return
diff --git a/contrib/python/Pillow/py3/PIL/TiffTags.py b/contrib/python/Pillow/py3/PIL/TiffTags.py
index 30b05e4e1d..88ff2f4fcd 100644
--- a/contrib/python/Pillow/py3/PIL/TiffTags.py
+++ b/contrib/python/Pillow/py3/PIL/TiffTags.py
@@ -16,6 +16,7 @@
# This module provides constants and clear-text names for various
# well-known TIFF tags.
##
+from __future__ import annotations
from collections import namedtuple
@@ -56,7 +57,7 @@ def lookup(tag, group=None):
##
# Map tag numbers to tag info.
#
-# id: (Name, Type, Length, enum_values)
+# id: (Name, Type, Length[, enum_values])
#
# The length here differs from the length in the tiff spec. For
# numbers, the tiff spec is for the number of fields returned. We
@@ -427,7 +428,7 @@ def _populate():
TAGS_V2[k] = TagInfo(k, *v)
- for group, tags in TAGS_V2_GROUPS.items():
+ for tags in TAGS_V2_GROUPS.values():
for k, v in tags.items():
tags[k] = TagInfo(k, *v)
@@ -438,22 +439,6 @@ _populate()
TYPES = {}
-# was:
-# TYPES = {
-# 1: "byte",
-# 2: "ascii",
-# 3: "short",
-# 4: "long",
-# 5: "rational",
-# 6: "signed byte",
-# 7: "undefined",
-# 8: "signed short",
-# 9: "signed long",
-# 10: "signed rational",
-# 11: "float",
-# 12: "double",
-# }
-
#
# These tags are handled by default in libtiff, without
# adding to the custom dictionary. From tif_dir.c, searching for
diff --git a/contrib/python/Pillow/py3/PIL/WalImageFile.py b/contrib/python/Pillow/py3/PIL/WalImageFile.py
index 3d9f97f848..c5bf3e04cf 100644
--- a/contrib/python/Pillow/py3/PIL/WalImageFile.py
+++ b/contrib/python/Pillow/py3/PIL/WalImageFile.py
@@ -22,6 +22,7 @@ and has been tested with a few sample files found using google.
is not registered for use with :py:func:`PIL.Image.open()`.
To open a WAL file, use the :py:func:`PIL.WalImageFile.open()` function instead.
"""
+from __future__ import annotations
from . import Image, ImageFile
from ._binary import i32le as i32
diff --git a/contrib/python/Pillow/py3/PIL/WebPImagePlugin.py b/contrib/python/Pillow/py3/PIL/WebPImagePlugin.py
index 612fc09467..59556206a3 100644
--- a/contrib/python/Pillow/py3/PIL/WebPImagePlugin.py
+++ b/contrib/python/Pillow/py3/PIL/WebPImagePlugin.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from io import BytesIO
from . import Image, ImageFile
@@ -168,6 +170,9 @@ class WebPImageFile(ImageFile.ImageFile):
return super().load()
+ def load_seek(self, pos):
+ pass
+
def tell(self):
if not _webp.HAVE_WEBPANIM:
return super().tell()
diff --git a/contrib/python/Pillow/py3/PIL/WmfImagePlugin.py b/contrib/python/Pillow/py3/PIL/WmfImagePlugin.py
index 3e5fb01512..b5b8c69b17 100644
--- a/contrib/python/Pillow/py3/PIL/WmfImagePlugin.py
+++ b/contrib/python/Pillow/py3/PIL/WmfImagePlugin.py
@@ -18,6 +18,7 @@
# https://winprotocoldoc.blob.core.windows.net/productionwindowsarchives/MS-WMF/[MS-WMF].pdf
# http://wvware.sourceforge.net/caolan/index.html
# http://wvware.sourceforge.net/caolan/ora-wmf.html
+from __future__ import annotations
from . import Image, ImageFile
from ._binary import i16le as word
diff --git a/contrib/python/Pillow/py3/PIL/XVThumbImagePlugin.py b/contrib/python/Pillow/py3/PIL/XVThumbImagePlugin.py
index eda60c5c5c..47ba1c5480 100644
--- a/contrib/python/Pillow/py3/PIL/XVThumbImagePlugin.py
+++ b/contrib/python/Pillow/py3/PIL/XVThumbImagePlugin.py
@@ -16,6 +16,7 @@
# To do:
# FIXME: make save work (this requires quantization support)
#
+from __future__ import annotations
from . import Image, ImageFile, ImagePalette
from ._binary import o8
diff --git a/contrib/python/Pillow/py3/PIL/XbmImagePlugin.py b/contrib/python/Pillow/py3/PIL/XbmImagePlugin.py
index 71cd57d74d..566acbfe5a 100644
--- a/contrib/python/Pillow/py3/PIL/XbmImagePlugin.py
+++ b/contrib/python/Pillow/py3/PIL/XbmImagePlugin.py
@@ -18,6 +18,7 @@
#
# See the README file for information on usage and redistribution.
#
+from __future__ import annotations
import re
diff --git a/contrib/python/Pillow/py3/PIL/XpmImagePlugin.py b/contrib/python/Pillow/py3/PIL/XpmImagePlugin.py
index 8491d3b7e9..bf73c9bef0 100644
--- a/contrib/python/Pillow/py3/PIL/XpmImagePlugin.py
+++ b/contrib/python/Pillow/py3/PIL/XpmImagePlugin.py
@@ -13,7 +13,7 @@
#
# See the README file for information on usage and redistribution.
#
-
+from __future__ import annotations
import re
diff --git a/contrib/python/Pillow/py3/PIL/__init__.py b/contrib/python/Pillow/py3/PIL/__init__.py
index 2bb8f6d7f1..3fcac8643c 100644
--- a/contrib/python/Pillow/py3/PIL/__init__.py
+++ b/contrib/python/Pillow/py3/PIL/__init__.py
@@ -12,6 +12,7 @@ Use PIL.__version__ for this Pillow version.
;-)
"""
+from __future__ import annotations
from . import _version
diff --git a/contrib/python/Pillow/py3/PIL/__main__.py b/contrib/python/Pillow/py3/PIL/__main__.py
index d249991d05..c49139f9ed 100644
--- a/contrib/python/Pillow/py3/PIL/__main__.py
+++ b/contrib/python/Pillow/py3/PIL/__main__.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from .features import pilinfo
diff --git a/contrib/python/Pillow/py3/PIL/_binary.py b/contrib/python/Pillow/py3/PIL/_binary.py
index a74ee9eb6f..0a07e8d0e1 100644
--- a/contrib/python/Pillow/py3/PIL/_binary.py
+++ b/contrib/python/Pillow/py3/PIL/_binary.py
@@ -13,21 +13,21 @@
"""Binary input/output support routines."""
-
+from __future__ import annotations
from struct import pack, unpack_from
-def i8(c):
- return c if c.__class__ is int else c[0]
+def i8(c: bytes) -> int:
+ return c[0]
-def o8(i):
+def o8(i: int) -> bytes:
return bytes((i & 255,))
# Input, le = little endian, be = big endian
-def i16le(c, o=0):
+def i16le(c: bytes, o: int = 0) -> int:
"""
Converts a 2-bytes (16 bits) string to an unsigned integer.
@@ -37,7 +37,7 @@ def i16le(c, o=0):
return unpack_from("<H", c, o)[0]
-def si16le(c, o=0):
+def si16le(c: bytes, o: int = 0) -> int:
"""
Converts a 2-bytes (16 bits) string to a signed integer.
@@ -47,7 +47,7 @@ def si16le(c, o=0):
return unpack_from("<h", c, o)[0]
-def si16be(c, o=0):
+def si16be(c: bytes, o: int = 0) -> int:
"""
Converts a 2-bytes (16 bits) string to a signed integer, big endian.
@@ -57,7 +57,7 @@ def si16be(c, o=0):
return unpack_from(">h", c, o)[0]
-def i32le(c, o=0):
+def i32le(c: bytes, o: int = 0) -> int:
"""
Converts a 4-bytes (32 bits) string to an unsigned integer.
@@ -67,7 +67,7 @@ def i32le(c, o=0):
return unpack_from("<I", c, o)[0]
-def si32le(c, o=0):
+def si32le(c: bytes, o: int = 0) -> int:
"""
Converts a 4-bytes (32 bits) string to a signed integer.
@@ -77,26 +77,26 @@ def si32le(c, o=0):
return unpack_from("<i", c, o)[0]
-def i16be(c, o=0):
+def i16be(c: bytes, o: int = 0) -> int:
return unpack_from(">H", c, o)[0]
-def i32be(c, o=0):
+def i32be(c: bytes, o: int = 0) -> int:
return unpack_from(">I", c, o)[0]
# Output, le = little endian, be = big endian
-def o16le(i):
+def o16le(i: int) -> bytes:
return pack("<H", i)
-def o32le(i):
+def o32le(i: int) -> bytes:
return pack("<I", i)
-def o16be(i):
+def o16be(i: int) -> bytes:
return pack(">H", i)
-def o32be(i):
+def o32be(i: int) -> bytes:
return pack(">I", i)
diff --git a/contrib/python/Pillow/py3/PIL/_deprecate.py b/contrib/python/Pillow/py3/PIL/_deprecate.py
index 2f2a3df13e..33a0e07b3f 100644
--- a/contrib/python/Pillow/py3/PIL/_deprecate.py
+++ b/contrib/python/Pillow/py3/PIL/_deprecate.py
@@ -47,6 +47,8 @@ def deprecate(
raise RuntimeError(msg)
elif when == 11:
removed = "Pillow 11 (2024-10-15)"
+ elif when == 12:
+ removed = "Pillow 12 (2025-10-15)"
else:
msg = f"Unknown removal version: {when}. Update {__name__}?"
raise ValueError(msg)
diff --git a/contrib/python/Pillow/py3/PIL/_typing.py b/contrib/python/Pillow/py3/PIL/_typing.py
new file mode 100644
index 0000000000..608b2b41fa
--- /dev/null
+++ b/contrib/python/Pillow/py3/PIL/_typing.py
@@ -0,0 +1,18 @@
+from __future__ import annotations
+
+import sys
+
+if sys.version_info >= (3, 10):
+ from typing import TypeGuard
+else:
+ try:
+ from typing_extensions import TypeGuard
+ except ImportError:
+ from typing import Any
+
+ class TypeGuard: # type: ignore[no-redef]
+ def __class_getitem__(cls, item: Any) -> type[bool]:
+ return bool
+
+
+__all__ = ["TypeGuard"]
diff --git a/contrib/python/Pillow/py3/PIL/_util.py b/contrib/python/Pillow/py3/PIL/_util.py
index ba27b7e49e..13f369cca1 100644
--- a/contrib/python/Pillow/py3/PIL/_util.py
+++ b/contrib/python/Pillow/py3/PIL/_util.py
@@ -1,19 +1,32 @@
+from __future__ import annotations
+
import os
from pathlib import Path
+from typing import Any, NoReturn
+
+from ._typing import TypeGuard
-def is_path(f):
+def is_path(f: Any) -> TypeGuard[bytes | str | Path]:
return isinstance(f, (bytes, str, Path))
-def is_directory(f):
+def is_directory(f: Any) -> TypeGuard[bytes | str | Path]:
"""Checks if an object is a string, and that it points to a directory."""
return is_path(f) and os.path.isdir(f)
class DeferredError:
- def __init__(self, ex):
+ def __init__(self, ex: BaseException):
self.ex = ex
- def __getattr__(self, elt):
+ def __getattr__(self, elt: str) -> NoReturn:
raise self.ex
+
+ @staticmethod
+ def new(ex: BaseException) -> Any:
+ """
+ Creates an object that raises the wrapped exception ``ex`` when used,
+ and casts it to :py:obj:`~typing.Any` type.
+ """
+ return DeferredError(ex)
diff --git a/contrib/python/Pillow/py3/PIL/_version.py b/contrib/python/Pillow/py3/PIL/_version.py
index 0936d1a7f3..1018b96b52 100644
--- a/contrib/python/Pillow/py3/PIL/_version.py
+++ b/contrib/python/Pillow/py3/PIL/_version.py
@@ -1,2 +1,4 @@
# Master version for Pillow
-__version__ = "10.1.0"
+from __future__ import annotations
+
+__version__ = "10.2.0"
diff --git a/contrib/python/Pillow/py3/PIL/features.py b/contrib/python/Pillow/py3/PIL/features.py
index 0936499fb8..9db159cdf4 100644
--- a/contrib/python/Pillow/py3/PIL/features.py
+++ b/contrib/python/Pillow/py3/PIL/features.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import collections
import os
import sys