tuple[int, int]:
try:
self._read_blp_header()
self._load()
@@ -285,6 +288,10 @@ class _BLPBaseDecoder(ImageFile.PyDecoder):
raise OSError(msg) from e
return -1, 0
+ @abc.abstractmethod
+ def _load(self) -> None:
+ pass
+
def _read_blp_header(self) -> None:
assert self.fd is not None
self.fd.seek(4)
@@ -306,6 +313,7 @@ class _BLPBaseDecoder(ImageFile.PyDecoder):
self._blp_lengths = struct.unpack("<16I", self._safe_read(16 * 4))
def _safe_read(self, length: int) -> bytes:
+ assert self.fd is not None
return ImageFile._safe_read(self.fd, length)
def _read_palette(self) -> list[tuple[int, int, int, int]]:
@@ -318,7 +326,7 @@ class _BLPBaseDecoder(ImageFile.PyDecoder):
ret.append((b, g, r, a))
return ret
- def _read_bgra(self, palette):
+ def _read_bgra(self, palette: list[tuple[int, int, int, int]]) -> bytearray:
data = bytearray()
_data = BytesIO(self._safe_read(self._blp_lengths[0]))
while True:
@@ -327,7 +335,7 @@ class _BLPBaseDecoder(ImageFile.PyDecoder):
except struct.error:
break
b, g, r, a = palette[offset]
- d = (r, g, b)
+ d: tuple[int, ...] = (r, g, b)
if self._blp_alpha_depth:
d += (a,)
data.extend(d)
@@ -364,7 +372,10 @@ class BLP1Decoder(_BLPBaseDecoder):
Image._decompression_bomb_check(image.size)
if image.mode == "CMYK":
decoder_name, extents, offset, args = image.tile[0]
- image.tile = [(decoder_name, extents, offset, (args[0], "CMYK"))]
+ assert isinstance(args, tuple)
+ image.tile = [
+ ImageFile._Tile(decoder_name, extents, offset, (args[0], "CMYK"))
+ ]
r, g, b = image.convert("RGB").split()
reversed_image = Image.merge("RGB", (b, g, r))
self.set_as_raw(reversed_image.tobytes())
@@ -423,6 +434,7 @@ class BLPEncoder(ImageFile.PyEncoder):
def _write_palette(self) -> bytes:
data = b""
+ assert self.im is not None
palette = self.im.getpalette("RGBA", "RGBA")
for i in range(len(palette) // 4):
r, g, b, a = palette[i * 4 : (i + 1) * 4]
@@ -431,12 +443,13 @@ class BLPEncoder(ImageFile.PyEncoder):
data += b"\x00" * 4
return data
- def encode(self, bufsize):
+ def encode(self, bufsize: int) -> tuple[int, int, bytes]:
palette_data = self._write_palette()
offset = 20 + 16 * 4 * 2 + len(palette_data)
data = struct.pack("<16I", offset, *((0,) * 15))
+ assert self.im is not None
w, h = self.im.size
data += struct.pack("<16I", w * h, *((0,) * 15))
@@ -449,7 +462,7 @@ class BLPEncoder(ImageFile.PyEncoder):
return len(data), 0, data
-def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None:
+def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
if im.mode != "P":
msg = "Unsupported BLP image mode"
raise ValueError(msg)
@@ -457,6 +470,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None:
magic = b"BLP1" if im.encoderinfo.get("blp_version") == "BLP1" else b"BLP2"
fp.write(magic)
+ assert im.palette is not None
fp.write(struct.pack(" None:
fp.write(struct.pack(" None:
"""Read relevant info about the BMP"""
read, seek = self.fp.read, self.fp.seek
if header:
seek(header)
# read bmp header size @offset 14 (this is part of the header size)
- file_info = {"header_size": i32(read(4)), "direction": -1}
+ file_info: dict[str, bool | int | tuple[int, ...]] = {
+ "header_size": i32(read(4)),
+ "direction": -1,
+ }
# -------------------- If requested, read header at a specific position
# read the rest of the bmp header, without its size
+ assert isinstance(file_info["header_size"], int)
header_data = ImageFile._safe_read(self.fp, file_info["header_size"] - 4)
# ------------------------------- Windows Bitmap v2, IBM OS/2 Bitmap v1
@@ -92,7 +96,7 @@ class BmpImageFile(ImageFile.ImageFile):
file_info["height"] = i16(header_data, 2)
file_info["planes"] = i16(header_data, 4)
file_info["bits"] = i16(header_data, 6)
- file_info["compression"] = self.RAW
+ file_info["compression"] = self.COMPRESSIONS["RAW"]
file_info["palette_padding"] = 3
# --------------------------------------------- Windows Bitmap v3 to v5
@@ -122,8 +126,9 @@ class BmpImageFile(ImageFile.ImageFile):
)
file_info["colors"] = i32(header_data, 28)
file_info["palette_padding"] = 4
+ assert isinstance(file_info["pixels_per_meter"], tuple)
self.info["dpi"] = tuple(x / 39.3701 for x in file_info["pixels_per_meter"])
- if file_info["compression"] == self.BITFIELDS:
+ if file_info["compression"] == self.COMPRESSIONS["BITFIELDS"]:
masks = ["r_mask", "g_mask", "b_mask"]
if len(header_data) >= 48:
if len(header_data) >= 52:
@@ -144,6 +149,10 @@ class BmpImageFile(ImageFile.ImageFile):
file_info["a_mask"] = 0x0
for mask in masks:
file_info[mask] = i32(read(4))
+ assert isinstance(file_info["r_mask"], int)
+ assert isinstance(file_info["g_mask"], int)
+ assert isinstance(file_info["b_mask"], int)
+ assert isinstance(file_info["a_mask"], int)
file_info["rgb_mask"] = (
file_info["r_mask"],
file_info["g_mask"],
@@ -161,27 +170,31 @@ class BmpImageFile(ImageFile.ImageFile):
# ------------------ Special case : header is reported 40, which
# ---------------------- is shorter than real size for bpp >= 16
+ assert isinstance(file_info["width"], int)
+ assert isinstance(file_info["height"], int)
self._size = file_info["width"], file_info["height"]
# ------- If color count was not found in the header, compute from bits
+ assert isinstance(file_info["bits"], int)
file_info["colors"] = (
file_info["colors"]
if file_info.get("colors", 0)
else (1 << file_info["bits"])
)
+ assert isinstance(file_info["colors"], int)
if offset == 14 + file_info["header_size"] and file_info["bits"] <= 8:
offset += 4 * file_info["colors"]
# ---------------------- Check bit depth for unusual unsupported values
- self._mode, raw_mode = BIT2MODE.get(file_info["bits"], (None, None))
- if self.mode is None:
+ self._mode, raw_mode = BIT2MODE.get(file_info["bits"], ("", ""))
+ if not self.mode:
msg = f"Unsupported BMP pixel depth ({file_info['bits']})"
raise OSError(msg)
# ---------------- Process BMP with Bitfields compression (not palette)
decoder_name = "raw"
- if file_info["compression"] == self.BITFIELDS:
- SUPPORTED = {
+ if file_info["compression"] == self.COMPRESSIONS["BITFIELDS"]:
+ SUPPORTED: dict[int, list[tuple[int, ...]]] = {
32: [
(0xFF0000, 0xFF00, 0xFF, 0x0),
(0xFF000000, 0xFF0000, 0xFF00, 0x0),
@@ -213,12 +226,14 @@ class BmpImageFile(ImageFile.ImageFile):
file_info["bits"] == 32
and file_info["rgba_mask"] in SUPPORTED[file_info["bits"]]
):
+ assert isinstance(file_info["rgba_mask"], tuple)
raw_mode = MASK_MODES[(file_info["bits"], file_info["rgba_mask"])]
self._mode = "RGBA" if "A" in raw_mode else self.mode
elif (
file_info["bits"] in (24, 16)
and file_info["rgb_mask"] in SUPPORTED[file_info["bits"]]
):
+ assert isinstance(file_info["rgb_mask"], tuple)
raw_mode = MASK_MODES[(file_info["bits"], file_info["rgb_mask"])]
else:
msg = "Unsupported BMP bitfields layout"
@@ -226,10 +241,13 @@ class BmpImageFile(ImageFile.ImageFile):
else:
msg = "Unsupported BMP bitfields layout"
raise OSError(msg)
- elif file_info["compression"] == self.RAW:
+ elif file_info["compression"] == self.COMPRESSIONS["RAW"]:
if file_info["bits"] == 32 and header == 22: # 32-bit .cur offset
raw_mode, self._mode = "BGRA", "RGBA"
- elif file_info["compression"] in (self.RLE8, self.RLE4):
+ elif file_info["compression"] in (
+ self.COMPRESSIONS["RLE8"],
+ self.COMPRESSIONS["RLE4"],
+ ):
decoder_name = "bmp_rle"
else:
msg = f"Unsupported BMP compression ({file_info['compression']})"
@@ -242,6 +260,7 @@ class BmpImageFile(ImageFile.ImageFile):
msg = f"Unsupported BMP Palette size ({file_info['colors']})"
raise OSError(msg)
else:
+ assert isinstance(file_info["palette_padding"], int)
padding = file_info["palette_padding"]
palette = read(padding * file_info["colors"])
grayscale = True
@@ -269,14 +288,15 @@ class BmpImageFile(ImageFile.ImageFile):
# ---------------------------- Finally set the tile data for the plugin
self.info["compression"] = file_info["compression"]
- args = [raw_mode]
+ args: list[Any] = [raw_mode]
if decoder_name == "bmp_rle":
- args.append(file_info["compression"] == self.RLE4)
+ args.append(file_info["compression"] == self.COMPRESSIONS["RLE4"])
else:
+ assert isinstance(file_info["width"], int)
args.append(((file_info["width"] * file_info["bits"] + 31) >> 3) & (~3))
args.append(file_info["direction"])
self.tile = [
- (
+ ImageFile._Tile(
decoder_name,
(0, 0, file_info["width"], file_info["height"]),
offset or self.fp.tell(),
@@ -301,7 +321,8 @@ class BmpImageFile(ImageFile.ImageFile):
class BmpRleDecoder(ImageFile.PyDecoder):
_pulls_fd = True
- def decode(self, buffer):
+ def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int]:
+ assert self.fd is not None
rle4 = self.args[1]
data = bytearray()
x = 0
@@ -366,7 +387,7 @@ class BmpRleDecoder(ImageFile.PyDecoder):
if self.fd.tell() % 2 != 0:
self.fd.seek(1, os.SEEK_CUR)
rawmode = "L" if self.mode == "L" else "P"
- self.set_as_raw(bytes(data), (rawmode, 0, self.args[-1]))
+ self.set_as_raw(bytes(data), rawmode, (0, self.args[-1]))
return -1, 0
@@ -395,12 +416,12 @@ SAVE = {
}
-def _dib_save(im: Image.Image, fp: IO[bytes], filename: str) -> None:
+def _dib_save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
_save(im, fp, filename, False)
def _save(
- im: Image.Image, fp: IO[bytes], filename: str, bitmap_header: bool = True
+ im: Image.Image, fp: IO[bytes], filename: str | bytes, bitmap_header: bool = True
) -> None:
try:
rawmode, bits, colors = SAVE[im.mode]
@@ -463,7 +484,9 @@ def _save(
if palette:
fp.write(palette)
- ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, stride, -1))])
+ ImageFile._save(
+ im, fp, [ImageFile._Tile("raw", (0, 0) + im.size, 0, (rawmode, stride, -1))]
+ )
#
diff --git a/src/PIL/BufrStubImagePlugin.py b/src/PIL/BufrStubImagePlugin.py
index 7388a2b8a..0ee2f653b 100644
--- a/src/PIL/BufrStubImagePlugin.py
+++ b/src/PIL/BufrStubImagePlugin.py
@@ -60,7 +60,7 @@ class BufrStubImageFile(ImageFile.StubImageFile):
return _handler
-def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None:
+def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
if _handler is None or not hasattr(_handler, "save"):
msg = "BUFR save handler not installed"
raise OSError(msg)
diff --git a/src/PIL/ContainerIO.py b/src/PIL/ContainerIO.py
index 0035296a4..ec9e66c71 100644
--- a/src/PIL/ContainerIO.py
+++ b/src/PIL/ContainerIO.py
@@ -16,10 +16,11 @@
from __future__ import annotations
import io
-from typing import IO, AnyStr, Generic, Literal
+from collections.abc import Iterable
+from typing import IO, AnyStr, NoReturn
-class ContainerIO(Generic[AnyStr]):
+class ContainerIO(IO[AnyStr]):
"""
A file object that provides read access to a part of an existing
file (for example a TAR file).
@@ -45,7 +46,10 @@ class ContainerIO(Generic[AnyStr]):
def isatty(self) -> bool:
return False
- def seek(self, offset: int, mode: Literal[0, 1, 2] = io.SEEK_SET) -> None:
+ def seekable(self) -> bool:
+ return True
+
+ def seek(self, offset: int, mode: int = io.SEEK_SET) -> int:
"""
Move file pointer.
@@ -53,6 +57,7 @@ class ContainerIO(Generic[AnyStr]):
:param mode: Starting position. Use 0 for beginning of region, 1
for current offset, and 2 for end of region. You cannot move
the pointer outside the defined region.
+ :returns: Offset from start of region, in bytes.
"""
if mode == 1:
self.pos = self.pos + offset
@@ -63,6 +68,7 @@ class ContainerIO(Generic[AnyStr]):
# clamp
self.pos = max(0, min(self.pos, self.length))
self.fh.seek(self.offset + self.pos)
+ return self.pos
def tell(self) -> int:
"""
@@ -72,27 +78,32 @@ class ContainerIO(Generic[AnyStr]):
"""
return self.pos
- def read(self, n: int = 0) -> AnyStr:
+ def readable(self) -> bool:
+ return True
+
+ def read(self, n: int = -1) -> AnyStr:
"""
Read data.
- :param n: Number of bytes to read. If omitted or zero,
+ :param n: Number of bytes to read. If omitted, zero or negative,
read until end of region.
:returns: An 8-bit string.
"""
- if n:
+ if n > 0:
n = min(n, self.length - self.pos)
else:
n = self.length - self.pos
- if not n: # EOF
+ if n <= 0: # EOF
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) -> AnyStr:
+ def readline(self, n: int = -1) -> AnyStr:
"""
Read a line of text.
+ :param n: Number of bytes to read. If omitted, zero or negative,
+ read until end of line.
:returns: An 8-bit string.
"""
s: AnyStr = b"" if "b" in self.fh.mode else "" # type: ignore[assignment]
@@ -102,14 +113,16 @@ class ContainerIO(Generic[AnyStr]):
if not c:
break
s = s + c
- if c == newline_character:
+ if c == newline_character or len(s) == n:
break
return s
- def readlines(self) -> list[AnyStr]:
+ def readlines(self, n: int | None = -1) -> list[AnyStr]:
"""
Read multiple lines of text.
+ :param n: Number of lines to read. If omitted, zero, negative or None,
+ read until end of region.
:returns: A list of 8-bit strings.
"""
lines = []
@@ -118,4 +131,43 @@ class ContainerIO(Generic[AnyStr]):
if not s:
break
lines.append(s)
+ if len(lines) == n:
+ break
return lines
+
+ def writable(self) -> bool:
+ return False
+
+ def write(self, b: AnyStr) -> NoReturn:
+ raise NotImplementedError()
+
+ def writelines(self, lines: Iterable[AnyStr]) -> NoReturn:
+ raise NotImplementedError()
+
+ def truncate(self, size: int | None = None) -> int:
+ raise NotImplementedError()
+
+ def __enter__(self) -> ContainerIO[AnyStr]:
+ return self
+
+ def __exit__(self, *args: object) -> None:
+ self.close()
+
+ def __iter__(self) -> ContainerIO[AnyStr]:
+ return self
+
+ def __next__(self) -> AnyStr:
+ line = self.readline()
+ if not line:
+ msg = "end of region"
+ raise StopIteration(msg)
+ return line
+
+ def fileno(self) -> int:
+ return self.fh.fileno()
+
+ def flush(self) -> None:
+ self.fh.flush()
+
+ def close(self) -> None:
+ self.fh.close()
diff --git a/src/PIL/CurImagePlugin.py b/src/PIL/CurImagePlugin.py
index 85e2145e7..c4be0ceca 100644
--- a/src/PIL/CurImagePlugin.py
+++ b/src/PIL/CurImagePlugin.py
@@ -17,7 +17,7 @@
#
from __future__ import annotations
-from . import BmpImagePlugin, Image
+from . import BmpImagePlugin, Image, ImageFile
from ._binary import i16le as i16
from ._binary import i32le as i32
@@ -64,7 +64,7 @@ class CurImageFile(BmpImagePlugin.BmpImageFile):
# patch up the bitmap height
self._size = self.size[0], self.size[1] // 2
d, e, o, a = self.tile[0]
- self.tile[0] = d, (0, 0) + self.size, o, a
+ self.tile[0] = ImageFile._Tile(d, (0, 0) + self.size, o, a)
#
diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py
index a3efadb03..9349e2841 100644
--- a/src/PIL/DdsImagePlugin.py
+++ b/src/PIL/DdsImagePlugin.py
@@ -367,7 +367,7 @@ class DdsImageFile(ImageFile.ImageFile):
mask_count = 3
masks = struct.unpack(f"<{mask_count}I", header.read(mask_count * 4))
- self.tile = [("dds_rgb", extents, 0, (bitcount, masks))]
+ self.tile = [ImageFile._Tile("dds_rgb", extents, 0, (bitcount, masks))]
return
elif pfflags & DDPF.LUMINANCE:
if bitcount == 8:
@@ -380,6 +380,7 @@ class DdsImageFile(ImageFile.ImageFile):
elif pfflags & DDPF.PALETTEINDEXED8:
self._mode = "P"
self.palette = ImagePalette.raw("RGBA", self.fp.read(1024))
+ self.palette.mode = "RGBA"
elif pfflags & DDPF.FOURCC:
offset = header_size + 4
if fourcc == D3DFMT.DXT1:
@@ -480,7 +481,8 @@ class DdsImageFile(ImageFile.ImageFile):
class DdsRgbDecoder(ImageFile.PyDecoder):
_pulls_fd = True
- def decode(self, buffer):
+ def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int]:
+ assert self.fd is not None
bitcount, masks = self.args
# Some masks will be padded with zeros, e.g. R 0b11 G 0b1100
@@ -511,7 +513,7 @@ class DdsRgbDecoder(ImageFile.PyDecoder):
return -1, 0
-def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None:
+def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
if im.mode not in ("RGB", "RGBA", "L", "LA"):
msg = f"cannot write mode {im.mode} as DDS"
raise OSError(msg)
@@ -558,9 +560,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None:
+ 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))]
- )
+ ImageFile._save(im, fp, [ImageFile._Tile("raw", (0, 0) + im.size, 0, rawmode)])
def _accept(prefix: bytes) -> bool:
diff --git a/src/PIL/EpsImagePlugin.py b/src/PIL/EpsImagePlugin.py
index d24a2ba80..36ba15ec5 100644
--- a/src/PIL/EpsImagePlugin.py
+++ b/src/PIL/EpsImagePlugin.py
@@ -27,10 +27,10 @@ import re
import subprocess
import sys
import tempfile
+from typing import IO
from . import Image, ImageFile
from ._binary import i32le as i32
-from ._deprecate import deprecate
# --------------------------------------------------------------------
@@ -65,16 +65,24 @@ def has_ghostscript() -> bool:
return gs_binary is not False
-def Ghostscript(tile, size, fp, scale=1, transparency=False):
+def Ghostscript(
+ tile: list[ImageFile._Tile],
+ size: tuple[int, int],
+ fp: IO[bytes],
+ scale: int = 1,
+ transparency: bool = False,
+) -> Image.core.ImagingCore:
"""Render an image using Ghostscript"""
global gs_binary
if not has_ghostscript():
msg = "Unable to locate Ghostscript on paths"
raise OSError(msg)
+ assert isinstance(gs_binary, str)
# Unpack decoder tile
- decoder, tile, offset, data = tile[0]
- length, bbox = data
+ args = tile[0].args
+ assert isinstance(args, tuple)
+ length, bbox = args
# Hack to support hi-res rendering
scale = int(scale) or 1
@@ -113,7 +121,13 @@ def Ghostscript(tile, size, fp, scale=1, transparency=False):
lengthfile -= len(s)
f.write(s)
- device = "pngalpha" if transparency else "ppmraw"
+ if transparency:
+ # "RGBA"
+ device = "pngalpha"
+ else:
+ # "pnmraw" automatically chooses between
+ # PBM ("1"), PGM ("L"), and PPM ("RGB").
+ device = "pnmraw"
# Build Ghostscript command
command = [
@@ -143,8 +157,9 @@ def Ghostscript(tile, size, fp, scale=1, transparency=False):
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
subprocess.check_call(command, startupinfo=startupinfo)
- out_im = Image.open(outfile)
- out_im.load()
+ with Image.open(outfile) as out_im:
+ out_im.load()
+ return out_im.im.copy()
finally:
try:
os.unlink(outfile)
@@ -153,47 +168,6 @@ def Ghostscript(tile, size, fp, scale=1, transparency=False):
except OSError:
pass
- im = out_im.im.copy()
- out_im.close()
- return im
-
-
-class PSFile:
- """
- Wrapper for bytesio object that treats either CR or LF as end of line.
- This class is no longer used internally, but kept for backwards compatibility.
- """
-
- def __init__(self, fp):
- deprecate(
- "PSFile",
- 11,
- action="If you need the functionality of this class "
- "you will need to implement it yourself.",
- )
- self.fp = fp
- self.char = None
-
- def seek(self, offset, whence=io.SEEK_SET):
- self.char = None
- self.fp.seek(offset, whence)
-
- def readline(self) -> str:
- s = [self.char or b""]
- self.char = None
-
- c = self.fp.read(1)
- while (c not in b"\r\n") and len(c):
- s.append(c)
- c = self.fp.read(1)
-
- self.char = self.fp.read(1)
- # line endings can be 1 or 2 of \r \n, in either order
- if self.char in b"\r\n":
- self.char = None
-
- return b"".join(s).decode("latin-1")
-
def _accept(prefix: bytes) -> bool:
return prefix[:4] == b"%!PS" or (len(prefix) >= 4 and i32(prefix) == 0xC6D3D0C5)
@@ -219,7 +193,11 @@ class EpsImageFile(ImageFile.ImageFile):
self.fp.seek(offset)
self._mode = "RGB"
- self._size = None
+
+ # When reading header comments, the first comment is used.
+ # When reading trailer comments, the last comment is used.
+ bounding_box: list[int] | None = None
+ imagedata_size: tuple[int, int] | None = None
byte_arr = bytearray(255)
bytes_mv = memoryview(byte_arr)
@@ -229,6 +207,11 @@ class EpsImageFile(ImageFile.ImageFile):
trailer_reached = False
def check_required_header_comments() -> None:
+ """
+ The EPS specification requires that some headers exist.
+ This should be checked when the header comments formally end,
+ when image data starts, or when the file ends, whichever comes first.
+ """
if "PS-Adobe" not in self.info:
msg = 'EPS header missing "%!PS-Adobe" comment'
raise SyntaxError(msg)
@@ -236,41 +219,39 @@ class EpsImageFile(ImageFile.ImageFile):
msg = 'EPS header missing "%%BoundingBox" comment'
raise SyntaxError(msg)
- def _read_comment(s):
- nonlocal reading_trailer_comments
+ def read_comment(s: str) -> bool:
+ nonlocal bounding_box, reading_trailer_comments
try:
m = split.match(s)
except re.error as e:
msg = "not an EPS file"
raise SyntaxError(msg) from e
- if m:
- k, v = m.group(1, 2)
- self.info[k] = v
- if k == "BoundingBox":
- if v == "(atend)":
- reading_trailer_comments = True
- elif not self._size or (
- trailer_reached and reading_trailer_comments
- ):
- try:
- # Note: The DSC spec says that BoundingBox
- # fields should be integers, but some drivers
- # put floating point values there anyway.
- box = [int(float(i)) for i in v.split()]
- self._size = box[2] - box[0], box[3] - box[1]
- self.tile = [
- ("eps", (0, 0) + self.size, offset, (length, box))
- ]
- except Exception:
- pass
- return True
+ if not m:
+ return False
+
+ k, v = m.group(1, 2)
+ self.info[k] = v
+ if k == "BoundingBox":
+ if v == "(atend)":
+ reading_trailer_comments = True
+ elif not bounding_box or (trailer_reached and reading_trailer_comments):
+ try:
+ # Note: The DSC spec says that BoundingBox
+ # fields should be integers, but some drivers
+ # put floating point values there anyway.
+ bounding_box = [int(float(i)) for i in v.split()]
+ except Exception:
+ pass
+ return True
while True:
byte = self.fp.read(1)
if byte == b"":
# if we didn't read a byte we must be at the end of the file
if bytes_read == 0:
+ if reading_header_comments:
+ check_required_header_comments()
break
elif byte in b"\r\n":
# if we read a line ending character, ignore it and parse what
@@ -310,7 +291,7 @@ class EpsImageFile(ImageFile.ImageFile):
continue
s = str(bytes_mv[:bytes_read], "latin-1")
- if not _read_comment(s):
+ if not read_comment(s):
m = field.match(s)
if m:
k = m.group(1)
@@ -329,6 +310,12 @@ class EpsImageFile(ImageFile.ImageFile):
# Check for an "ImageData" descriptor
# https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577413_pgfId-1035096
+ # If we've already read an "ImageData" descriptor,
+ # don't read another one.
+ if imagedata_size:
+ bytes_read = 0
+ continue
+
# Values:
# columns
# rows
@@ -354,25 +341,36 @@ class EpsImageFile(ImageFile.ImageFile):
else:
break
- self._size = columns, rows
- return
+ # Parse the columns and rows after checking the bit depth and mode
+ # in case the bit depth and/or mode are invalid.
+ imagedata_size = columns, rows
elif bytes_mv[:5] == b"%%EOF":
break
elif trailer_reached and reading_trailer_comments:
# Load EPS trailer
s = str(bytes_mv[:bytes_read], "latin-1")
- _read_comment(s)
+ read_comment(s)
elif bytes_mv[:9] == b"%%Trailer":
trailer_reached = True
bytes_read = 0
- check_required_header_comments()
-
- if not self._size:
+ # A "BoundingBox" is always required,
+ # even if an "ImageData" descriptor size exists.
+ if not bounding_box:
msg = "cannot determine EPS bounding box"
raise OSError(msg)
- def _find_offset(self, fp):
+ # An "ImageData" size takes precedence over the "BoundingBox".
+ self._size = imagedata_size or (
+ bounding_box[2] - bounding_box[0],
+ bounding_box[3] - bounding_box[1],
+ )
+
+ self.tile = [
+ ImageFile._Tile("eps", (0, 0) + self.size, offset, (length, bounding_box))
+ ]
+
+ def _find_offset(self, fp: IO[bytes]) -> tuple[int, int]:
s = fp.read(4)
if s == b"%!PS":
@@ -395,7 +393,9 @@ class EpsImageFile(ImageFile.ImageFile):
return length, offset
- def load(self, scale=1, transparency=False):
+ def load(
+ self, scale: int = 1, transparency: bool = False
+ ) -> Image.core.PixelAccess | None:
# Load EPS via Ghostscript
if self.tile:
self.im = Ghostscript(self.tile, self.size, self.fp, scale, transparency)
@@ -413,7 +413,7 @@ class EpsImageFile(ImageFile.ImageFile):
# --------------------------------------------------------------------
-def _save(im, fp, filename, eps=1):
+def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes, eps: int = 1) -> None:
"""EPS Writer for the Python Imaging Library."""
# make sure image data is available
@@ -454,7 +454,7 @@ def _save(im, fp, filename, eps=1):
if hasattr(fp, "flush"):
fp.flush()
- ImageFile._save(im, fp, [("eps", (0, 0) + im.size, 0, None)])
+ ImageFile._save(im, fp, [ImageFile._Tile("eps", (0, 0) + im.size)])
fp.write(b"\n%%%%EndBinary\n")
fp.write(b"grestore end\n")
diff --git a/src/PIL/ExifTags.py b/src/PIL/ExifTags.py
index 39b4aa552..207d4de4e 100644
--- a/src/PIL/ExifTags.py
+++ b/src/PIL/ExifTags.py
@@ -303,38 +303,38 @@ TAGS = {
class GPS(IntEnum):
- GPSVersionID = 0
- GPSLatitudeRef = 1
- GPSLatitude = 2
- GPSLongitudeRef = 3
- GPSLongitude = 4
- GPSAltitudeRef = 5
- GPSAltitude = 6
- GPSTimeStamp = 7
- GPSSatellites = 8
- GPSStatus = 9
- GPSMeasureMode = 10
- GPSDOP = 11
- GPSSpeedRef = 12
- GPSSpeed = 13
- GPSTrackRef = 14
- GPSTrack = 15
- GPSImgDirectionRef = 16
- GPSImgDirection = 17
- GPSMapDatum = 18
- GPSDestLatitudeRef = 19
- GPSDestLatitude = 20
- GPSDestLongitudeRef = 21
- GPSDestLongitude = 22
- GPSDestBearingRef = 23
- GPSDestBearing = 24
- GPSDestDistanceRef = 25
- GPSDestDistance = 26
- GPSProcessingMethod = 27
- GPSAreaInformation = 28
- GPSDateStamp = 29
- GPSDifferential = 30
- GPSHPositioningError = 31
+ GPSVersionID = 0x00
+ GPSLatitudeRef = 0x01
+ GPSLatitude = 0x02
+ GPSLongitudeRef = 0x03
+ GPSLongitude = 0x04
+ GPSAltitudeRef = 0x05
+ GPSAltitude = 0x06
+ GPSTimeStamp = 0x07
+ GPSSatellites = 0x08
+ GPSStatus = 0x09
+ GPSMeasureMode = 0x0A
+ GPSDOP = 0x0B
+ GPSSpeedRef = 0x0C
+ GPSSpeed = 0x0D
+ GPSTrackRef = 0x0E
+ GPSTrack = 0x0F
+ GPSImgDirectionRef = 0x10
+ GPSImgDirection = 0x11
+ GPSMapDatum = 0x12
+ GPSDestLatitudeRef = 0x13
+ GPSDestLatitude = 0x14
+ GPSDestLongitudeRef = 0x15
+ GPSDestLongitude = 0x16
+ GPSDestBearingRef = 0x17
+ GPSDestBearing = 0x18
+ GPSDestDistanceRef = 0x19
+ GPSDestDistance = 0x1A
+ GPSProcessingMethod = 0x1B
+ GPSAreaInformation = 0x1C
+ GPSDateStamp = 0x1D
+ GPSDifferential = 0x1E
+ GPSHPositioningError = 0x1F
"""Maps EXIF GPS tags to tag names."""
@@ -342,40 +342,40 @@ GPSTAGS = {i.value: i.name for i in GPS}
class Interop(IntEnum):
- InteropIndex = 1
- InteropVersion = 2
- RelatedImageFileFormat = 4096
- RelatedImageWidth = 4097
- RelatedImageHeight = 4098
+ InteropIndex = 0x0001
+ InteropVersion = 0x0002
+ RelatedImageFileFormat = 0x1000
+ RelatedImageWidth = 0x1001
+ RelatedImageHeight = 0x1002
class IFD(IntEnum):
- Exif = 34665
- GPSInfo = 34853
- Makernote = 37500
- Interop = 40965
+ Exif = 0x8769
+ GPSInfo = 0x8825
+ MakerNote = 0x927C
+ Interop = 0xA005
IFD1 = -1
class LightSource(IntEnum):
- Unknown = 0
- Daylight = 1
- Fluorescent = 2
- Tungsten = 3
- Flash = 4
- Fine = 9
- Cloudy = 10
- Shade = 11
- DaylightFluorescent = 12
- DayWhiteFluorescent = 13
- CoolWhiteFluorescent = 14
- WhiteFluorescent = 15
- StandardLightA = 17
- StandardLightB = 18
- StandardLightC = 19
- D55 = 20
- D65 = 21
- D75 = 22
- D50 = 23
- ISO = 24
- Other = 255
+ Unknown = 0x00
+ Daylight = 0x01
+ Fluorescent = 0x02
+ Tungsten = 0x03
+ Flash = 0x04
+ Fine = 0x09
+ Cloudy = 0x0A
+ Shade = 0x0B
+ DaylightFluorescent = 0x0C
+ DayWhiteFluorescent = 0x0D
+ CoolWhiteFluorescent = 0x0E
+ WhiteFluorescent = 0x0F
+ StandardLightA = 0x11
+ StandardLightB = 0x12
+ StandardLightC = 0x13
+ D55 = 0x14
+ D65 = 0x15
+ D75 = 0x16
+ D50 = 0x17
+ ISO = 0x18
+ Other = 0xFF
diff --git a/src/PIL/FitsImagePlugin.py b/src/PIL/FitsImagePlugin.py
index 071918925..6bbd2641a 100644
--- a/src/PIL/FitsImagePlugin.py
+++ b/src/PIL/FitsImagePlugin.py
@@ -67,7 +67,7 @@ class FitsImageFile(ImageFile.ImageFile):
raise ValueError(msg)
offset += self.fp.tell() - 80
- self.tile = [(decoder_name, (0, 0) + self.size, offset, args)]
+ self.tile = [ImageFile._Tile(decoder_name, (0, 0) + self.size, offset, args)]
def _get_size(
self, headers: dict[bytes, bytes], prefix: bytes
@@ -115,14 +115,18 @@ class FitsImageFile(ImageFile.ImageFile):
elif number_of_bits in (-32, -64):
self._mode = "F"
- args = (self.mode, 0, -1) if decoder_name == "raw" else (number_of_bits,)
+ args: tuple[str | int, ...]
+ if decoder_name == "raw":
+ args = (self.mode, 0, -1)
+ else:
+ args = (number_of_bits,)
return decoder_name, offset, args
class FitsGzipDecoder(ImageFile.PyDecoder):
_pulls_fd = True
- def decode(self, buffer):
+ def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int]:
assert self.fd is not None
value = gzip.decompress(self.fd.read())
diff --git a/src/PIL/FliImagePlugin.py b/src/PIL/FliImagePlugin.py
index dceb83927..b534b30ab 100644
--- a/src/PIL/FliImagePlugin.py
+++ b/src/PIL/FliImagePlugin.py
@@ -45,7 +45,7 @@ class FliImageFile(ImageFile.ImageFile):
format_description = "Autodesk FLI/FLC Animation"
_close_exclusive_fp_after_loading = False
- def _open(self):
+ def _open(self) -> None:
# HEAD
s = self.fp.read(128)
if not (_accept(s) and s[20:22] == b"\x00\x00"):
@@ -83,7 +83,7 @@ class FliImageFile(ImageFile.ImageFile):
if i16(s, 4) == 0xF1FA:
# look for palette chunk
number_of_subchunks = i16(s, 6)
- chunk_size = None
+ chunk_size: int | None = None
for _ in range(number_of_subchunks):
if chunk_size is not None:
self.fp.seek(chunk_size - 6, os.SEEK_CUR)
@@ -96,8 +96,9 @@ class FliImageFile(ImageFile.ImageFile):
if not chunk_size:
break
- palette = [o8(r) + o8(g) + o8(b) for (r, g, b) in palette]
- self.palette = ImagePalette.raw("RGB", b"".join(palette))
+ self.palette = ImagePalette.raw(
+ "RGB", b"".join(o8(r) + o8(g) + o8(b) for (r, g, b) in palette)
+ )
# set things up to decode first frame
self.__frame = -1
@@ -105,7 +106,7 @@ class FliImageFile(ImageFile.ImageFile):
self.__rewind = self.fp.tell()
self.seek(0)
- def _palette(self, palette, shift):
+ def _palette(self, palette: list[tuple[int, int, int]], shift: int) -> None:
# load palette
i = 0
@@ -158,7 +159,7 @@ class FliImageFile(ImageFile.ImageFile):
framesize = i32(s)
self.decodermaxblock = framesize
- self.tile = [("fli", (0, 0) + self.size, self.__offset, None)]
+ self.tile = [ImageFile._Tile("fli", (0, 0) + self.size, self.__offset)]
self.__offset += framesize
diff --git a/src/PIL/FpxImagePlugin.py b/src/PIL/FpxImagePlugin.py
index b3e6c6e36..4cfcb067d 100644
--- a/src/PIL/FpxImagePlugin.py
+++ b/src/PIL/FpxImagePlugin.py
@@ -53,7 +53,7 @@ class FpxImageFile(ImageFile.ImageFile):
format = "FPX"
format_description = "FlashPix"
- def _open(self):
+ def _open(self) -> None:
#
# read the OLE directory and see if this is a likely
# to be a FlashPix file
@@ -64,7 +64,8 @@ class FpxImageFile(ImageFile.ImageFile):
msg = "not an FPX file; invalid OLE file"
raise SyntaxError(msg) from e
- if self.ole.root.clsid != "56616700-C154-11CE-8553-00AA00A1F95B":
+ root = self.ole.root
+ if not root or root.clsid != "56616700-C154-11CE-8553-00AA00A1F95B":
msg = "not an FPX file; bad root CLSID"
raise SyntaxError(msg)
@@ -80,6 +81,8 @@ class FpxImageFile(ImageFile.ImageFile):
# size (highest resolution)
+ assert isinstance(prop[0x1000002], int)
+ assert isinstance(prop[0x1000003], int)
self._size = prop[0x1000002], prop[0x1000003]
size = max(self.size)
@@ -99,8 +102,7 @@ class FpxImageFile(ImageFile.ImageFile):
s = prop[0x2000002 | id]
- bands = i32(s, 4)
- if bands > 4:
+ if not isinstance(s, bytes) or (bands := i32(s, 4)) > 4:
msg = "Invalid number of bands"
raise OSError(msg)
@@ -164,18 +166,18 @@ class FpxImageFile(ImageFile.ImageFile):
if compression == 0:
self.tile.append(
- (
+ ImageFile._Tile(
"raw",
(x, y, x1, y1),
i32(s, i) + 28,
- (self.rawmode,),
+ self.rawmode,
)
)
elif compression == 1:
# FIXME: the fill decoder is not implemented
self.tile.append(
- (
+ ImageFile._Tile(
"fill",
(x, y, x1, y1),
i32(s, i) + 28,
@@ -203,7 +205,7 @@ class FpxImageFile(ImageFile.ImageFile):
jpegmode = rawmode
self.tile.append(
- (
+ ImageFile._Tile(
"jpeg",
(x, y, x1, y1),
i32(s, i) + 28,
@@ -231,7 +233,7 @@ class FpxImageFile(ImageFile.ImageFile):
self._fp = self.fp
self.fp = None
- def load(self):
+ def load(self) -> Image.core.PixelAccess | None:
if not self.fp:
self.fp = self.ole.openstream(self.stream[:2] + ["Subimage 0000 Data"])
@@ -241,7 +243,7 @@ class FpxImageFile(ImageFile.ImageFile):
self.ole.close()
super().close()
- def __exit__(self, *args):
+ def __exit__(self, *args: object) -> None:
self.ole.close()
super().__exit__()
diff --git a/src/PIL/FtexImagePlugin.py b/src/PIL/FtexImagePlugin.py
index 5acbb4912..0516b760c 100644
--- a/src/PIL/FtexImagePlugin.py
+++ b/src/PIL/FtexImagePlugin.py
@@ -93,9 +93,9 @@ class FtexImageFile(ImageFile.ImageFile):
if format == Format.DXT1:
self._mode = "RGBA"
- self.tile = [("bcn", (0, 0) + self.size, 0, 1)]
+ self.tile = [ImageFile._Tile("bcn", (0, 0) + self.size, 0, (1,))]
elif format == Format.UNCOMPRESSED:
- self.tile = [("raw", (0, 0) + self.size, 0, ("RGB", 0, 1))]
+ self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, 0, "RGB")]
else:
msg = f"Invalid texture compression format: {repr(format)}"
raise ValueError(msg)
diff --git a/src/PIL/GbrImagePlugin.py b/src/PIL/GbrImagePlugin.py
index 93e89b1e6..f319d7e84 100644
--- a/src/PIL/GbrImagePlugin.py
+++ b/src/PIL/GbrImagePlugin.py
@@ -88,8 +88,8 @@ class GbrImageFile(ImageFile.ImageFile):
# Data is an uncompressed block of w * h * bytes/pixel
self._data_size = width * height * color_depth
- def load(self):
- if not self.im:
+ def load(self) -> Image.core.PixelAccess | None:
+ if self._im is None:
self.im = Image.core.new(self.mode, self.size)
self.frombytes(self.fp.read(self._data_size))
return Image.Image.load(self)
diff --git a/src/PIL/GdImageFile.py b/src/PIL/GdImageFile.py
index 88b87a22c..fc4801e9d 100644
--- a/src/PIL/GdImageFile.py
+++ b/src/PIL/GdImageFile.py
@@ -72,11 +72,11 @@ class GdImageFile(ImageFile.ImageFile):
)
self.tile = [
- (
+ ImageFile._Tile(
"raw",
(0, 0) + self.size,
7 + true_color_offset + 4 + 256 * 4,
- ("L", 0, 1),
+ "L",
)
]
diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py
index 6686ac02a..09f1fb7f4 100644
--- a/src/PIL/GifImagePlugin.py
+++ b/src/PIL/GifImagePlugin.py
@@ -31,7 +31,7 @@ import os
import subprocess
from enum import IntEnum
from functools import cached_property
-from typing import IO
+from typing import IO, TYPE_CHECKING, Any, Literal, NamedTuple, Union
from . import (
Image,
@@ -46,6 +46,10 @@ from ._binary import i16le as i16
from ._binary import o8
from ._binary import o16le as o16
+if TYPE_CHECKING:
+ from . import _imaging
+ from ._typing import Buffer
+
class LoadingStrategy(IntEnum):
""".. versionadded:: 9.1.0"""
@@ -99,7 +103,6 @@ class GifImageFile(ImageFile.ImageFile):
self.info["version"] = s[:6]
self._size = i16(s, 6), i16(s, 8)
- self.tile = []
flags = s[10]
bits = (flags & 7) + 1
@@ -118,7 +121,7 @@ class GifImageFile(ImageFile.ImageFile):
self._seek(0) # get ready to read first frame
@property
- def n_frames(self):
+ def n_frames(self) -> int:
if self._n_frames is None:
current = self.tell()
try:
@@ -151,7 +154,7 @@ class GifImageFile(ImageFile.ImageFile):
if not self._seek_check(frame):
return
if frame < self.__frame:
- self.im = None
+ self._im = None
self._seek(0)
last_frame = self.__frame
@@ -163,11 +166,11 @@ class GifImageFile(ImageFile.ImageFile):
msg = "no more images in GIF file"
raise EOFError(msg) from e
- def _seek(self, frame, update_image=True):
+ def _seek(self, frame: int, update_image: bool = True) -> None:
if frame == 0:
# rewind
self.__offset = 0
- self.dispose = None
+ self.dispose: _imaging.ImagingCore | None = None
self.__frame = -1
self._fp.seek(self.__rewind)
self.disposal_method = 0
@@ -195,9 +198,9 @@ class GifImageFile(ImageFile.ImageFile):
msg = "no more images in GIF file"
raise EOFError(msg)
- palette = None
+ palette: ImagePalette.ImagePalette | Literal[False] | None = None
- info = {}
+ info: dict[str, Any] = {}
frame_transparency = None
interlace = None
frame_dispose_extent = None
@@ -213,7 +216,7 @@ class GifImageFile(ImageFile.ImageFile):
#
s = self.fp.read(1)
block = self.data()
- if s[0] == 249:
+ if s[0] == 249 and block is not None:
#
# graphic control extension
#
@@ -249,14 +252,14 @@ class GifImageFile(ImageFile.ImageFile):
info["comment"] = comment
s = None
continue
- elif s[0] == 255 and frame == 0:
+ elif s[0] == 255 and frame == 0 and block is not None:
#
# application extension
#
info["extension"] = block, self.fp.tell()
if block[:11] == b"NETSCAPE2.0":
block = self.data()
- if len(block) >= 3 and block[0] == 1:
+ if block and len(block) >= 3 and block[0] == 1:
self.info["loop"] = i16(block, 1)
while self.data():
pass
@@ -316,18 +319,20 @@ class GifImageFile(ImageFile.ImageFile):
else:
self._mode = "L"
- if not palette and self.global_palette:
+ if palette:
+ self.palette = palette
+ elif self.global_palette:
from copy import copy
- palette = copy(self.global_palette)
- self.palette = palette
+ self.palette = copy(self.global_palette)
+ else:
+ self.palette = None
else:
if self.mode == "P":
if (
LOADING_STRATEGY != LoadingStrategy.RGB_AFTER_DIFFERENT_PALETTE_ONLY
or palette
):
- self.pyaccess = None
if "transparency" in self.info:
self.im.putpalettealpha(self.info["transparency"], 0)
self.im = self.im.convert("RGBA", Image.Dither.FLOYDSTEINBERG)
@@ -345,51 +350,52 @@ class GifImageFile(ImageFile.ImageFile):
else:
return (color, color, color)
+ self.dispose = None
self.dispose_extent = frame_dispose_extent
- try:
- if self.disposal_method < 2:
- # do not dispose or none specified
- self.dispose = None
- elif self.disposal_method == 2:
- # replace with background colour
+ if self.dispose_extent and self.disposal_method >= 2:
+ try:
+ if self.disposal_method == 2:
+ # replace with background colour
- # only dispose the extent in this frame
- x0, y0, x1, y1 = self.dispose_extent
- dispose_size = (x1 - x0, y1 - y0)
-
- Image._decompression_bomb_check(dispose_size)
-
- # by convention, attempt to use transparency first
- dispose_mode = "P"
- color = self.info.get("transparency", frame_transparency)
- if color is not None:
- if self.mode in ("RGB", "RGBA"):
- dispose_mode = "RGBA"
- color = _rgb(color) + (0,)
- else:
- color = self.info.get("background", 0)
- if self.mode in ("RGB", "RGBA"):
- dispose_mode = "RGB"
- color = _rgb(color)
- self.dispose = Image.core.fill(dispose_mode, dispose_size, color)
- else:
- # replace with previous contents
- if self.im is not None:
# only dispose the extent in this frame
- self.dispose = self._crop(self.im, self.dispose_extent)
- elif frame_transparency is not None:
x0, y0, x1, y1 = self.dispose_extent
dispose_size = (x1 - x0, y1 - y0)
Image._decompression_bomb_check(dispose_size)
+
+ # by convention, attempt to use transparency first
dispose_mode = "P"
- color = frame_transparency
- if self.mode in ("RGB", "RGBA"):
- dispose_mode = "RGBA"
- color = _rgb(frame_transparency) + (0,)
+ color = self.info.get("transparency", frame_transparency)
+ if color is not None:
+ if self.mode in ("RGB", "RGBA"):
+ dispose_mode = "RGBA"
+ color = _rgb(color) + (0,)
+ else:
+ color = self.info.get("background", 0)
+ if self.mode in ("RGB", "RGBA"):
+ dispose_mode = "RGB"
+ color = _rgb(color)
self.dispose = Image.core.fill(dispose_mode, dispose_size, color)
- except AttributeError:
- pass
+ else:
+ # replace with previous contents
+ if self._im is not None:
+ # only dispose the extent in this frame
+ self.dispose = self._crop(self.im, self.dispose_extent)
+ elif frame_transparency is not None:
+ x0, y0, x1, y1 = self.dispose_extent
+ dispose_size = (x1 - x0, y1 - y0)
+
+ Image._decompression_bomb_check(dispose_size)
+ dispose_mode = "P"
+ color = frame_transparency
+ if self.mode in ("RGB", "RGBA"):
+ dispose_mode = "RGBA"
+ color = _rgb(frame_transparency) + (0,)
+ self.dispose = Image.core.fill(
+ dispose_mode, dispose_size, color
+ )
+ except AttributeError:
+ pass
if interlace is not None:
transparency = -1
@@ -400,7 +406,7 @@ class GifImageFile(ImageFile.ImageFile):
elif self.mode not in ("RGB", "RGBA"):
transparency = frame_transparency
self.tile = [
- (
+ ImageFile._Tile(
"gif",
(x0, y0, x1, y1),
self.__offset,
@@ -428,9 +434,16 @@ class GifImageFile(ImageFile.ImageFile):
self._prev_im = self.im
if self._frame_palette:
self.im = Image.core.fill("P", self.size, self._frame_transparency or 0)
- self.im.putpalette(*self._frame_palette.getdata())
+ self.im.putpalette("RGB", *self._frame_palette.getdata())
else:
- self.im = None
+ self._im = None
+ if not self._prev_im and self._im is not None and self.size != self.im.size:
+ expanded_im = Image.core.fill(self.im.mode, self.size)
+ if self._frame_palette:
+ expanded_im.putpalette("RGB", *self._frame_palette.getdata())
+ expanded_im.paste(self.im, (0, 0) + self.im.size)
+
+ self.im = expanded_im
self._mode = temp_mode
self._frame_palette = None
@@ -448,11 +461,24 @@ class GifImageFile(ImageFile.ImageFile):
return
if not self._prev_im:
return
+ if self.size != self._prev_im.size:
+ if self._frame_transparency is not None:
+ expanded_im = Image.core.fill("RGBA", self.size)
+ else:
+ expanded_im = Image.core.fill("P", self.size)
+ expanded_im.putpalette("RGB", "RGB", self.im.getpalette())
+ expanded_im = expanded_im.convert("RGB")
+ expanded_im.paste(self._prev_im, (0, 0) + self._prev_im.size)
+
+ self._prev_im = expanded_im
+ assert self._prev_im is not None
if self._frame_transparency is not None:
self.im.putpalettealpha(self._frame_transparency, 0)
frame_im = self.im.convert("RGBA")
else:
frame_im = self.im.convert("RGB")
+
+ assert self.dispose_extent is not None
frame_im = self._crop(frame_im, self.dispose_extent)
self.im = self._prev_im
@@ -489,6 +515,7 @@ def _normalize_mode(im: Image.Image) -> Image.Image:
return im
if Image.getmodebase(im.mode) == "RGB":
im = im.convert("P", palette=Image.Palette.ADAPTIVE)
+ assert im.palette is not None
if im.palette.mode == "RGBA":
for rgba in im.palette.colors:
if rgba[3] == 0:
@@ -498,7 +525,12 @@ def _normalize_mode(im: Image.Image) -> Image.Image:
return im.convert("L")
-def _normalize_palette(im, palette, info):
+_Palette = Union[bytes, bytearray, list[int], ImagePalette.ImagePalette]
+
+
+def _normalize_palette(
+ im: Image.Image, palette: _Palette | None, info: dict[str, Any]
+) -> Image.Image:
"""
Normalizes the palette for image.
- Sets the palette to the incoming palette, if provided.
@@ -520,14 +552,18 @@ def _normalize_palette(im, palette, info):
if im.mode == "P":
if not source_palette:
- source_palette = im.im.getpalette("RGB")[:768]
+ im_palette = im.getpalette(None)
+ assert im_palette is not None
+ source_palette = bytearray(im_palette)
else: # L-mode
if not source_palette:
source_palette = bytearray(i // 3 for i in range(768))
im.palette = ImagePalette.ImagePalette("RGB", palette=source_palette)
+ assert source_palette is not None
if palette:
- used_palette_colors = []
+ used_palette_colors: list[int | None] = []
+ assert im.palette is not None
for i in range(0, len(source_palette), 3):
source_color = tuple(source_palette[i : i + 3])
index = im.palette.colors.get(source_color)
@@ -540,20 +576,25 @@ def _normalize_palette(im, palette, info):
if j not in used_palette_colors:
used_palette_colors[i] = j
break
- im = im.remap_palette(used_palette_colors)
+ dest_map: list[int] = []
+ for index in used_palette_colors:
+ assert index is not None
+ dest_map.append(index)
+ im = im.remap_palette(dest_map)
else:
- used_palette_colors = _get_optimize(im, info)
- if used_palette_colors is not None:
- im = im.remap_palette(used_palette_colors, source_palette)
+ optimized_palette_colors = _get_optimize(im, info)
+ if optimized_palette_colors is not None:
+ im = im.remap_palette(optimized_palette_colors, source_palette)
if "transparency" in info:
try:
- info["transparency"] = used_palette_colors.index(
+ info["transparency"] = optimized_palette_colors.index(
info["transparency"]
)
except ValueError:
del info["transparency"]
return im
+ assert im.palette is not None
im.palette.palette = source_palette
return im
@@ -561,11 +602,12 @@ def _normalize_palette(im, palette, info):
def _write_single_frame(
im: Image.Image,
fp: IO[bytes],
- palette: bytes | bytearray | list[int] | ImagePalette.ImagePalette,
+ palette: _Palette | None,
) -> None:
im_out = _normalize_mode(im)
for k, v in im_out.info.items():
- im.encoderinfo.setdefault(k, v)
+ if isinstance(k, str):
+ im.encoderinfo.setdefault(k, v)
im_out = _normalize_palette(im_out, palette, im.encoderinfo)
for s in _get_global_header(im_out, im.encoderinfo):
@@ -578,27 +620,40 @@ def _write_single_frame(
_write_local_header(fp, im, (0, 0), flags)
im_out.encoderconfig = (8, get_interlace(im))
- ImageFile._save(im_out, fp, [("gif", (0, 0) + im.size, 0, RAWMODE[im_out.mode])])
+ ImageFile._save(
+ im_out, fp, [ImageFile._Tile("gif", (0, 0) + im.size, 0, RAWMODE[im_out.mode])]
+ )
fp.write(b"\0") # end of image data
def _getbbox(
base_im: Image.Image, im_frame: Image.Image
-) -> tuple[Image.Image, tuple[int, int, int, int]]:
- if _get_palette_bytes(im_frame) != _get_palette_bytes(base_im):
+) -> tuple[Image.Image, tuple[int, int, int, int] | None]:
+ palette_bytes = [
+ bytes(im.palette.palette) if im.palette else b"" for im in (base_im, im_frame)
+ ]
+ if palette_bytes[0] != palette_bytes[1]:
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):
+class _Frame(NamedTuple):
+ im: Image.Image
+ bbox: tuple[int, int, int, int] | None
+ encoderinfo: dict[str, Any]
+
+
+def _write_multiple_frames(
+ im: Image.Image, fp: IO[bytes], palette: _Palette | None
+) -> bool:
duration = im.encoderinfo.get("duration")
disposal = im.encoderinfo.get("disposal", im.info.get("disposal"))
- im_frames = []
- previous_im = None
+ im_frames: list[_Frame] = []
+ previous_im: Image.Image | None = None
frame_count = 0
background_im = None
for imSequence in itertools.chain([im], im.encoderinfo.get("append_images", [])):
@@ -609,7 +664,8 @@ def _write_multiple_frames(im, fp, palette):
for k, v in im_frame.info.items():
if k == "transparency":
continue
- im.encoderinfo.setdefault(k, v)
+ if isinstance(k, str):
+ im.encoderinfo.setdefault(k, v)
encoderinfo = im.encoderinfo.copy()
if "transparency" in im_frame.info:
@@ -624,27 +680,28 @@ def _write_multiple_frames(im, fp, palette):
frame_count += 1
diff_frame = None
- if im_frames:
+ if im_frames and previous_im:
# delta frame
delta, bbox = _getbbox(previous_im, im_frame)
if not bbox:
# This frame is identical to the previous frame
if encoderinfo.get("duration"):
- im_frames[-1]["encoderinfo"]["duration"] += encoderinfo[
- "duration"
- ]
+ im_frames[-1].encoderinfo["duration"] += encoderinfo["duration"]
continue
- if im_frames[-1]["encoderinfo"].get("disposal") == 2:
+ if im_frames[-1].encoderinfo.get("disposal") == 2:
if background_im is None:
color = im.encoderinfo.get(
"transparency", im.info.get("transparency", (0, 0, 0))
)
background = _get_background(im_frame, color)
background_im = Image.new("P", im_frame.size, background)
- background_im.putpalette(im_frames[0]["im"].palette)
+ first_palette = im_frames[0].im.palette
+ assert first_palette is not None
+ background_im.putpalette(first_palette, first_palette.mode)
bbox = _getbbox(background_im, im_frame)[1]
elif encoderinfo.get("optimize") and im_frame.mode != "1":
if "transparency" not in encoderinfo:
+ assert im_frame.palette is not None
try:
encoderinfo["transparency"] = (
im_frame.palette._new_color_index(im_frame)
@@ -687,40 +744,38 @@ def _write_multiple_frames(im, fp, palette):
else:
bbox = None
previous_im = im_frame
- im_frames.append(
- {"im": diff_frame or im_frame, "bbox": bbox, "encoderinfo": encoderinfo}
- )
+ im_frames.append(_Frame(diff_frame or im_frame, bbox, 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
+ im.encoderinfo["duration"] = im_frames[0].encoderinfo["duration"]
+ return False
for frame_data in im_frames:
- im_frame = frame_data["im"]
- if not frame_data["bbox"]:
+ im_frame = frame_data.im
+ if not frame_data.bbox:
# global header
- for s in _get_global_header(im_frame, frame_data["encoderinfo"]):
+ for s in _get_global_header(im_frame, frame_data.encoderinfo):
fp.write(s)
offset = (0, 0)
else:
# compress difference
if not palette:
- frame_data["encoderinfo"]["include_color_table"] = True
+ frame_data.encoderinfo["include_color_table"] = True
- im_frame = im_frame.crop(frame_data["bbox"])
- offset = frame_data["bbox"][:2]
- _write_frame_data(fp, im_frame, offset, frame_data["encoderinfo"])
+ 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: Image.Image, fp: IO[bytes], filename: str) -> None:
+def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
_save(im, fp, filename, save_all=True)
def _save(
- im: Image.Image, fp: IO[bytes], filename: str, save_all: bool = False
+ im: Image.Image, fp: IO[bytes], filename: str | bytes, save_all: bool = False
) -> None:
# header
if "palette" in im.encoderinfo or "palette" in im.info:
@@ -748,7 +803,9 @@ def get_interlace(im: Image.Image) -> int:
return interlace
-def _write_local_header(fp, im, offset, flags):
+def _write_local_header(
+ fp: IO[bytes], im: Image.Image, offset: tuple[int, int], flags: int
+) -> None:
try:
transparency = im.encoderinfo["transparency"]
except KeyError:
@@ -796,7 +853,7 @@ def _write_local_header(fp, im, offset, flags):
fp.write(o8(8)) # bits
-def _save_netpbm(im: Image.Image, fp: IO[bytes], filename: str) -> None:
+def _save_netpbm(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
# Unused by default.
# To use, uncomment the register_save call at the end of the file.
#
@@ -849,7 +906,7 @@ def _save_netpbm(im: Image.Image, fp: IO[bytes], filename: str) -> None:
_FORCE_OPTIMIZE = False
-def _get_optimize(im, info):
+def _get_optimize(im: Image.Image, info: dict[str, Any]) -> list[int] | None:
"""
Palette optimization is a potentially expensive operation.
@@ -882,6 +939,7 @@ def _get_optimize(im, info):
if optimise or max(used_palette_colors) >= len(used_palette_colors):
return used_palette_colors
+ assert im.palette is not None
num_palette_colors = len(im.palette.palette) // Image.getmodebands(
im.palette.mode
)
@@ -893,6 +951,7 @@ def _get_optimize(im, info):
and current_palette_size > 2
):
return used_palette_colors
+ return None
def _get_color_table_size(palette_bytes: bytes) -> int:
@@ -930,16 +989,26 @@ def _get_palette_bytes(im: Image.Image) -> bytes:
:param im: Image object
:returns: Bytes, len<=768 suitable for inclusion in gif header
"""
- return im.palette.palette if im.palette else b""
+ if not im.palette:
+ return b""
+
+ palette = bytes(im.palette.palette)
+ if im.palette.mode == "RGBA":
+ palette = b"".join(palette[i * 4 : i * 4 + 3] for i in range(len(palette) // 3))
+ return palette
-def _get_background(im, info_background):
+def _get_background(
+ im: Image.Image,
+ info_background: int | tuple[int, int, int] | tuple[int, int, int, int] | None,
+) -> int:
background = 0
if info_background:
if isinstance(info_background, tuple):
# WebPImagePlugin stores an RGBA value in info["background"]
# So it must be converted to the same format as GifImagePlugin's
# info["background"] - a global color table index
+ assert im.palette is not None
try:
background = im.palette.getcolor(info_background, im)
except ValueError as e:
@@ -956,7 +1025,7 @@ def _get_background(im, info_background):
return background
-def _get_global_header(im, info):
+def _get_global_header(im: Image.Image, info: dict[str, Any]) -> list[bytes]:
"""Return a list of strings representing a GIF header"""
# Header Block
@@ -1018,7 +1087,12 @@ def _get_global_header(im, info):
return header
-def _write_frame_data(fp, im_frame, offset, params):
+def _write_frame_data(
+ fp: IO[bytes],
+ im_frame: Image.Image,
+ offset: tuple[int, int],
+ params: dict[str, Any],
+) -> None:
try:
im_frame.encoderinfo = params
@@ -1026,7 +1100,9 @@ def _write_frame_data(fp, im_frame, offset, params):
_write_local_header(fp, im_frame, offset, 0)
ImageFile._save(
- im_frame, fp, [("gif", (0, 0) + im_frame.size, 0, RAWMODE[im_frame.mode])]
+ im_frame,
+ fp,
+ [ImageFile._Tile("gif", (0, 0) + im_frame.size, 0, RAWMODE[im_frame.mode])],
)
fp.write(b"\0") # end of image data
@@ -1038,7 +1114,9 @@ def _write_frame_data(fp, im_frame, offset, params):
# Legacy GIF utilities
-def getheader(im, palette=None, info=None):
+def getheader(
+ im: Image.Image, palette: _Palette | None = None, info: dict[str, Any] | None = None
+) -> tuple[list[bytes], list[int] | None]:
"""
Legacy Method to get Gif data from image.
@@ -1050,11 +1128,11 @@ def getheader(im, palette=None, info=None):
:returns: tuple of(list of header items, optimized palette)
"""
- used_palette_colors = _get_optimize(im, info)
-
if info is None:
info = {}
+ used_palette_colors = _get_optimize(im, info)
+
if "background" not in info and "background" in im.info:
info["background"] = im.info["background"]
@@ -1066,7 +1144,9 @@ def getheader(im, palette=None, info=None):
return header, used_palette_colors
-def getdata(im, offset=(0, 0), **params):
+def getdata(
+ im: Image.Image, offset: tuple[int, int] = (0, 0), **params: Any
+) -> list[bytes]:
"""
Legacy Method
@@ -1083,12 +1163,14 @@ def getdata(im, offset=(0, 0), **params):
:returns: List of bytes containing GIF encoded frame data
"""
+ from io import BytesIO
- class Collector:
+ class Collector(BytesIO):
data = []
- def write(self, data: bytes) -> None:
+ def write(self, data: Buffer) -> int:
self.data.append(data)
+ return len(data)
im.load() # make sure raster data is available
diff --git a/src/PIL/GribStubImagePlugin.py b/src/PIL/GribStubImagePlugin.py
index d3655f4dd..e9aa084b2 100644
--- a/src/PIL/GribStubImagePlugin.py
+++ b/src/PIL/GribStubImagePlugin.py
@@ -60,7 +60,7 @@ class GribStubImageFile(ImageFile.StubImageFile):
return _handler
-def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None:
+def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
if _handler is None or not hasattr(_handler, "save"):
msg = "GRIB save handler not installed"
raise OSError(msg)
diff --git a/src/PIL/Hdf5StubImagePlugin.py b/src/PIL/Hdf5StubImagePlugin.py
index b789c215f..cc9e73deb 100644
--- a/src/PIL/Hdf5StubImagePlugin.py
+++ b/src/PIL/Hdf5StubImagePlugin.py
@@ -60,7 +60,7 @@ class HDF5StubImageFile(ImageFile.StubImageFile):
return _handler
-def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None:
+def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
if _handler is None or not hasattr(_handler, "save"):
msg = "HDF5 save handler not installed"
raise OSError(msg)
diff --git a/src/PIL/IcnsImagePlugin.py b/src/PIL/IcnsImagePlugin.py
index 0a86ba883..9757b2b14 100644
--- a/src/PIL/IcnsImagePlugin.py
+++ b/src/PIL/IcnsImagePlugin.py
@@ -22,8 +22,10 @@ import io
import os
import struct
import sys
+from typing import IO
from . import Image, ImageFile, PngImagePlugin, features
+from ._deprecate import deprecate
enable_jpeg2k = features.check_codec("jpg_2000")
if enable_jpeg2k:
@@ -33,11 +35,13 @@ MAGIC = b"icns"
HEADERSIZE = 8
-def nextheader(fobj):
+def nextheader(fobj: IO[bytes]) -> tuple[bytes, int]:
return struct.unpack(">4sI", fobj.read(HEADERSIZE))
-def read_32t(fobj, start_length, size):
+def read_32t(
+ fobj: IO[bytes], start_length: tuple[int, int], size: tuple[int, int, int]
+) -> dict[str, Image.Image]:
# The 128x128 icon seems to have an extra header for some reason.
(start, length) = start_length
fobj.seek(start)
@@ -48,7 +52,9 @@ def read_32t(fobj, start_length, size):
return read_32(fobj, (start + 4, length - 4), size)
-def read_32(fobj, start_length, size):
+def read_32(
+ fobj: IO[bytes], start_length: tuple[int, int], size: tuple[int, int, int]
+) -> dict[str, Image.Image]:
"""
Read a 32bit RGB icon resource. Seems to be either uncompressed or
an RLE packbits-like scheme.
@@ -71,14 +77,14 @@ def read_32(fobj, start_length, size):
byte = fobj.read(1)
if not byte:
break
- byte = byte[0]
- if byte & 0x80:
- blocksize = byte - 125
+ byte_int = byte[0]
+ if byte_int & 0x80:
+ blocksize = byte_int - 125
byte = fobj.read(1)
for i in range(blocksize):
data.append(byte)
else:
- blocksize = byte + 1
+ blocksize = byte_int + 1
data.append(fobj.read(blocksize))
bytesleft -= blocksize
if bytesleft <= 0:
@@ -91,7 +97,9 @@ def read_32(fobj, start_length, size):
return {"RGB": im}
-def read_mk(fobj, start_length, size):
+def read_mk(
+ fobj: IO[bytes], start_length: tuple[int, int], size: tuple[int, int, int]
+) -> dict[str, Image.Image]:
# Alpha masks seem to be uncompressed
start = start_length[0]
fobj.seek(start)
@@ -101,10 +109,14 @@ def read_mk(fobj, start_length, size):
return {"A": band}
-def read_png_or_jpeg2000(fobj, start_length, size):
+def read_png_or_jpeg2000(
+ fobj: IO[bytes], start_length: tuple[int, int], size: tuple[int, int, int]
+) -> dict[str, Image.Image]:
(start, length) = start_length
fobj.seek(start)
sig = fobj.read(12)
+
+ im: Image.Image
if sig[:8] == b"\x89PNG\x0d\x0a\x1a\x0a":
fobj.seek(start)
im = PngImagePlugin.PngImageFile(fobj)
@@ -163,12 +175,12 @@ class IcnsFile:
],
}
- def __init__(self, fobj):
+ def __init__(self, fobj: IO[bytes]) -> None:
"""
fobj is a file-like object as an icns resource
"""
# signature : (start, length)
- self.dct = dct = {}
+ self.dct = {}
self.fobj = fobj
sig, filesize = nextheader(fobj)
if not _accept(sig):
@@ -182,11 +194,11 @@ class IcnsFile:
raise SyntaxError(msg)
i += HEADERSIZE
blocksize -= HEADERSIZE
- dct[sig] = (i, blocksize)
+ self.dct[sig] = (i, blocksize)
fobj.seek(blocksize, io.SEEK_CUR)
i += blocksize
- def itersizes(self):
+ def itersizes(self) -> list[tuple[int, int, int]]:
sizes = []
for size, fmts in self.SIZES.items():
for fmt, reader in fmts:
@@ -195,14 +207,14 @@ class IcnsFile:
break
return sizes
- def bestsize(self):
+ def bestsize(self) -> tuple[int, int, int]:
sizes = self.itersizes()
if not sizes:
msg = "No 32bit icon resources found"
raise SyntaxError(msg)
return max(sizes)
- def dataforsize(self, size):
+ def dataforsize(self, size: tuple[int, int, int]) -> dict[str, Image.Image]:
"""
Get an icon resource as {channel: array}. Note that
the arrays are bottom-up like windows bitmaps and will likely
@@ -215,18 +227,20 @@ class IcnsFile:
dct.update(reader(self.fobj, desc, size))
return dct
- def getimage(self, size=None):
+ def getimage(
+ self, size: tuple[int, int] | tuple[int, int, int] | None = None
+ ) -> Image.Image:
if size is None:
size = self.bestsize()
- if len(size) == 2:
+ elif len(size) == 2:
size = (size[0], size[1], 1)
channels = self.dataforsize(size)
- im = channels.get("RGBA", None)
+ im = channels.get("RGBA")
if im:
return im
- im = channels.get("RGB").copy()
+ im = channels["RGB"].copy()
try:
im.putalpha(channels["A"])
except KeyError:
@@ -262,40 +276,40 @@ class IcnsImageFile(ImageFile.ImageFile):
self.best_size[1] * self.best_size[2],
)
- @property
- def size(self):
+ @property # type: ignore[override]
+ def size(self) -> tuple[int, int] | tuple[int, int, int]:
return self._size
@size.setter
- def size(self, value):
- info_size = value
- if info_size not in self.info["sizes"] and len(info_size) == 2:
- info_size = (info_size[0], info_size[1], 1)
- if (
- info_size not in self.info["sizes"]
- and len(info_size) == 3
- and info_size[2] == 1
- ):
- simple_sizes = [
- (size[0] * size[2], size[1] * size[2]) for size in self.info["sizes"]
- ]
- if value in simple_sizes:
- info_size = self.info["sizes"][simple_sizes.index(value)]
- if info_size not in self.info["sizes"]:
- msg = "This is not one of the allowed sizes of this image"
- raise ValueError(msg)
- self._size = value
+ def size(self, value: tuple[int, int] | tuple[int, int, int]) -> None:
+ if len(value) == 3:
+ deprecate("Setting size to (width, height, scale)", 12, "load(scale)")
+ if value in self.info["sizes"]:
+ self._size = value # type: ignore[assignment]
+ return
+ else:
+ # Check that a matching size exists,
+ # or that there is a scale that would create a size that matches
+ for size in self.info["sizes"]:
+ simple_size = size[0] * size[2], size[1] * size[2]
+ scale = simple_size[0] // value[0]
+ if simple_size[1] / value[1] == scale:
+ self._size = value
+ return
+ msg = "This is not one of the allowed sizes of this image"
+ raise ValueError(msg)
- def load(self):
- if len(self.size) == 3:
- self.best_size = self.size
- self.size = (
- self.best_size[0] * self.best_size[2],
- self.best_size[1] * self.best_size[2],
- )
+ def load(self, scale: int | None = None) -> Image.core.PixelAccess | None:
+ if scale is not None or len(self.size) == 3:
+ if scale is None and len(self.size) == 3:
+ scale = self.size[2]
+ assert scale is not None
+ width, height = self.size[:2]
+ self.size = width * scale, height * scale
+ self.best_size = width, height, scale
px = Image.Image.load(self)
- if self.im is not None and self.im.size == self.size:
+ if self._im is not None and self.im.size == self.size:
# Already loaded
return px
self.load_prepare()
@@ -312,7 +326,7 @@ class IcnsImageFile(ImageFile.ImageFile):
return px
-def _save(im, fp, filename):
+def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
"""
Saves the image as a series of PNG files,
that are then combined into a .icns file.
@@ -346,29 +360,27 @@ def _save(im, fp, filename):
entries = []
for type, size in sizes.items():
stream = size_streams[size]
- entries.append(
- {"type": type, "size": HEADERSIZE + len(stream), "stream": stream}
- )
+ entries.append((type, HEADERSIZE + len(stream), stream))
# Header
fp.write(MAGIC)
file_length = HEADERSIZE # Header
file_length += HEADERSIZE + 8 * len(entries) # TOC
- file_length += sum(entry["size"] for entry in entries)
+ file_length += sum(entry[1] for entry in entries)
fp.write(struct.pack(">i", file_length))
# TOC
fp.write(b"TOC ")
fp.write(struct.pack(">i", HEADERSIZE + len(entries) * HEADERSIZE))
for entry in entries:
- fp.write(entry["type"])
- fp.write(struct.pack(">i", entry["size"]))
+ fp.write(entry[0])
+ fp.write(struct.pack(">i", entry[1]))
# Data
for entry in entries:
- fp.write(entry["type"])
- fp.write(struct.pack(">i", entry["size"]))
- fp.write(entry["stream"])
+ fp.write(entry[0])
+ fp.write(struct.pack(">i", entry[1]))
+ fp.write(entry[2])
if hasattr(fp, "flush"):
fp.flush()
diff --git a/src/PIL/IcoImagePlugin.py b/src/PIL/IcoImagePlugin.py
index af94e5a2e..e879f1801 100644
--- a/src/PIL/IcoImagePlugin.py
+++ b/src/PIL/IcoImagePlugin.py
@@ -25,7 +25,7 @@ from __future__ import annotations
import warnings
from io import BytesIO
from math import ceil, log
-from typing import IO
+from typing import IO, NamedTuple
from . import BmpImagePlugin, Image, ImageFile, PngImagePlugin
from ._binary import i16le as i16
@@ -40,7 +40,7 @@ from ._binary import o32le as o32
_MAGIC = b"\0\0\1\0"
-def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None:
+def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
fp.write(_MAGIC) # (2+2)
bmp = im.encoderinfo.get("bitmap_format") == "bmp"
sizes = im.encoderinfo.get(
@@ -97,7 +97,9 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None:
if bits != 32:
and_mask = Image.new("1", size)
ImageFile._save(
- and_mask, image_io, [("raw", (0, 0) + size, 0, ("1", 0, -1))]
+ and_mask,
+ image_io,
+ [ImageFile._Tile("raw", (0, 0) + size, 0, ("1", 0, -1))],
)
else:
frame.save(image_io, "png")
@@ -119,8 +121,22 @@ def _accept(prefix: bytes) -> bool:
return prefix[:4] == _MAGIC
+class IconHeader(NamedTuple):
+ width: int
+ height: int
+ nb_color: int
+ reserved: int
+ planes: int
+ bpp: int
+ size: int
+ offset: int
+ dim: tuple[int, int]
+ square: int
+ color_depth: int
+
+
class IcoFile:
- def __init__(self, buf):
+ def __init__(self, buf: IO[bytes]) -> None:
"""
Parse image from file-like object containing ico file data
"""
@@ -141,55 +157,48 @@ class IcoFile:
for i in range(self.nb_items):
s = buf.read(16)
- icon_header = {
- "width": s[0],
- "height": s[1],
- "nb_color": s[2], # No. of colors in image (0 if >=8bpp)
- "reserved": s[3],
- "planes": i16(s, 4),
- "bpp": i16(s, 6),
- "size": i32(s, 8),
- "offset": i32(s, 12),
- }
-
# See Wikipedia
- for j in ("width", "height"):
- if not icon_header[j]:
- icon_header[j] = 256
+ width = s[0] or 256
+ height = s[1] or 256
- # See Wikipedia notes about color depth.
- # We need this just to differ images with equal sizes
- icon_header["color_depth"] = (
- icon_header["bpp"]
- or (
- icon_header["nb_color"] != 0
- and ceil(log(icon_header["nb_color"], 2))
- )
- or 256
+ # No. of colors in image (0 if >=8bpp)
+ nb_color = s[2]
+ bpp = i16(s, 6)
+ icon_header = IconHeader(
+ width=width,
+ height=height,
+ nb_color=nb_color,
+ reserved=s[3],
+ planes=i16(s, 4),
+ bpp=i16(s, 6),
+ size=i32(s, 8),
+ offset=i32(s, 12),
+ dim=(width, height),
+ square=width * height,
+ # See Wikipedia notes about color depth.
+ # We need this just to differ images with equal sizes
+ color_depth=bpp or (nb_color != 0 and ceil(log(nb_color, 2))) or 256,
)
- icon_header["dim"] = (icon_header["width"], icon_header["height"])
- icon_header["square"] = icon_header["width"] * icon_header["height"]
-
self.entry.append(icon_header)
- self.entry = sorted(self.entry, key=lambda x: x["color_depth"])
+ 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["square"], reverse=True)
+ self.entry = sorted(self.entry, key=lambda x: x.square, reverse=True)
- def sizes(self):
+ def sizes(self) -> set[tuple[int, int]]:
"""
- Get a list of all available icon sizes and color depths.
+ Get a set of all available icon sizes and color depths.
"""
- return {(h["width"], h["height"]) for h in self.entry}
+ return {(h.width, h.height) for h in self.entry}
- def getentryindex(self, size, bpp=False):
+ def getentryindex(self, size: tuple[int, int], bpp: int | bool = False) -> int:
for i, h in enumerate(self.entry):
- if size == h["dim"] and (bpp is False or bpp == h["color_depth"]):
+ if size == h.dim and (bpp is False or bpp == h.color_depth):
return i
return 0
- def getimage(self, size, bpp=False):
+ def getimage(self, size: tuple[int, int], bpp: int | bool = False) -> Image.Image:
"""
Get an image from the icon
"""
@@ -202,9 +211,9 @@ class IcoFile:
header = self.entry[idx]
- self.buf.seek(header["offset"])
+ self.buf.seek(header.offset)
data = self.buf.read(8)
- self.buf.seek(header["offset"])
+ self.buf.seek(header.offset)
im: Image.Image
if data[:8] == PngImagePlugin._MAGIC:
@@ -219,11 +228,10 @@ class IcoFile:
# change tile dimension to only encompass XOR image
im._size = (im.size[0], int(im.size[1] / 2))
d, e, o, a = im.tile[0]
- im.tile[0] = d, (0, 0) + im.size, o, a
+ im.tile[0] = ImageFile._Tile(d, (0, 0) + im.size, o, a)
# figure out where AND mask image starts
- bpp = header["bpp"]
- if 32 == bpp:
+ if header.bpp == 32:
# 32-bit color depth icon image allows semitransparent areas
# PIL's DIB format ignores transparency bits, recover them.
# The DIB is packed in BGRX byte order where X is the alpha
@@ -235,13 +243,19 @@ class IcoFile:
alpha_bytes = self.buf.read(im.size[0] * im.size[1] * 4)[3::4]
# convert to an 8bpp grayscale image
- mask = Image.frombuffer(
- "L", # 8bpp
- im.size, # (w, h)
- alpha_bytes, # source chars
- "raw", # raw decoder
- ("L", 0, -1), # 8bpp inverted, unpadded, reversed
- )
+ try:
+ mask = Image.frombuffer(
+ "L", # 8bpp
+ im.size, # (w, h)
+ alpha_bytes, # source chars
+ "raw", # raw decoder
+ ("L", 0, -1), # 8bpp inverted, unpadded, reversed
+ )
+ except ValueError:
+ if ImageFile.LOAD_TRUNCATED_IMAGES:
+ mask = None
+ else:
+ raise
else:
# get AND image from end of bitmap
w = im.size[0]
@@ -253,25 +267,32 @@ class IcoFile:
# padded row size * height / bits per char
total_bytes = int((w * im.size[1]) / 8)
- and_mask_offset = header["offset"] + header["size"] - total_bytes
+ and_mask_offset = header.offset + header.size - total_bytes
self.buf.seek(and_mask_offset)
mask_data = self.buf.read(total_bytes)
# convert raw data to image
- mask = Image.frombuffer(
- "1", # 1 bpp
- im.size, # (w, h)
- mask_data, # source chars
- "raw", # raw decoder
- ("1;I", int(w / 8), -1), # 1bpp inverted, padded, reversed
- )
+ try:
+ mask = Image.frombuffer(
+ "1", # 1 bpp
+ im.size, # (w, h)
+ mask_data, # source chars
+ "raw", # raw decoder
+ ("1;I", int(w / 8), -1), # 1bpp inverted, padded, reversed
+ )
+ except ValueError:
+ if ImageFile.LOAD_TRUNCATED_IMAGES:
+ mask = None
+ else:
+ raise
# now we have two images, im is XOR image and mask is AND image
# apply mask image as alpha channel
- im = im.convert("RGBA")
- im.putalpha(mask)
+ if mask:
+ im = im.convert("RGBA")
+ im.putalpha(mask)
return im
@@ -307,29 +328,28 @@ class IcoImageFile(ImageFile.ImageFile):
def _open(self) -> None:
self.ico = IcoFile(self.fp)
self.info["sizes"] = self.ico.sizes()
- self.size = self.ico.entry[0]["dim"]
+ self.size = self.ico.entry[0].dim
self.load()
@property
- def size(self):
+ def size(self) -> tuple[int, int]:
return self._size
@size.setter
- def size(self, value):
+ def size(self, value: tuple[int, int]) -> None:
if value not in self.info["sizes"]:
msg = "This is not one of the allowed sizes of this image"
raise ValueError(msg)
self._size = value
- def load(self):
- if self.im is not None and self.im.size == self.size:
+ def load(self) -> Image.core.PixelAccess | None:
+ if self._im is not None and self.im.size == self.size:
# Already loaded
return Image.Image.load(self)
im = self.ico.getimage(self.size)
# if tile is PNG, it won't really be loaded yet
im.load()
self.im = im.im
- self.pyaccess = None
self._mode = im.mode
if im.palette:
self.palette = im.palette
@@ -342,6 +362,7 @@ class IcoImageFile(ImageFile.ImageFile):
self.info["sizes"] = set(sizes)
self.size = im.size
+ return None
def load_seek(self, pos: int) -> None:
# Flag the ImageFile.Parser so that it
diff --git a/src/PIL/ImImagePlugin.py b/src/PIL/ImImagePlugin.py
index c98cfb098..b4215a0b1 100644
--- a/src/PIL/ImImagePlugin.py
+++ b/src/PIL/ImImagePlugin.py
@@ -79,7 +79,7 @@ OPEN = {
"LA image": ("LA", "LA;L"),
"PA image": ("LA", "PA;L"),
"RGBA image": ("RGBA", "RGBA;L"),
- "RGBX image": ("RGBX", "RGBX;L"),
+ "RGBX image": ("RGB", "RGBX;L"),
"CMYK image": ("CMYK", "CMYK;L"),
"YCC image": ("YCbCr", "YCbCr;L"),
}
@@ -253,7 +253,11 @@ class ImImageFile(ImageFile.ImageFile):
# use bit decoder (if necessary)
bits = int(self.rawmode[2:])
if bits not in [8, 16, 32]:
- self.tile = [("bit", (0, 0) + self.size, offs, (bits, 8, 3, 0, -1))]
+ self.tile = [
+ ImageFile._Tile(
+ "bit", (0, 0) + self.size, offs, (bits, 8, 3, 0, -1)
+ )
+ ]
return
except ValueError:
pass
@@ -263,13 +267,17 @@ class ImImageFile(ImageFile.ImageFile):
# ever stumbled upon such a file ;-)
size = self.size[0] * self.size[1]
self.tile = [
- ("raw", (0, 0) + self.size, offs, ("G", 0, -1)),
- ("raw", (0, 0) + self.size, offs + size, ("R", 0, -1)),
- ("raw", (0, 0) + self.size, offs + 2 * size, ("B", 0, -1)),
+ ImageFile._Tile("raw", (0, 0) + self.size, offs, ("G", 0, -1)),
+ ImageFile._Tile("raw", (0, 0) + self.size, offs + size, ("R", 0, -1)),
+ ImageFile._Tile(
+ "raw", (0, 0) + self.size, offs + 2 * size, ("B", 0, -1)
+ ),
]
else:
# LabEye/IFUNC files
- self.tile = [("raw", (0, 0) + self.size, offs, (self.rawmode, 0, -1))]
+ self.tile = [
+ ImageFile._Tile("raw", (0, 0) + self.size, offs, (self.rawmode, 0, -1))
+ ]
@property
def n_frames(self) -> int:
@@ -295,7 +303,9 @@ class ImImageFile(ImageFile.ImageFile):
self.fp = self._fp
- self.tile = [("raw", (0, 0) + self.size, offs, (self.rawmode, 0, -1))]
+ self.tile = [
+ ImageFile._Tile("raw", (0, 0) + self.size, offs, (self.rawmode, 0, -1))
+ ]
def tell(self) -> int:
return self.frame
@@ -326,7 +336,7 @@ SAVE = {
}
-def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None:
+def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
try:
image_type, rawmode = SAVE[im.mode]
except KeyError as e:
@@ -341,11 +351,13 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None:
# or: SyntaxError("not an IM file")
# 8 characters are used for "Name: " and "\r\n"
# Keep just the filename, ditch the potentially overlong path
+ if isinstance(filename, bytes):
+ filename = filename.decode("ascii")
name, ext = os.path.splitext(os.path.basename(filename))
name = "".join([name[: 92 - len(ext)], ext])
fp.write(f"Name: {name}\r\n".encode("ascii"))
- fp.write(("Image size (x*y): %d*%d\r\n" % im.size).encode("ascii"))
+ fp.write(f"Image size (x*y): {im.size[0]}*{im.size[1]}\r\n".encode("ascii"))
fp.write(f"File size (no of images): {frames}\r\n".encode("ascii"))
if im.mode in ["P", "PA"]:
fp.write(b"Lut: 1\r\n")
@@ -358,7 +370,9 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None:
palette += im_palette[colors * i : colors * (i + 1)]
palette += b"\x00" * (256 - colors)
fp.write(palette) # 768 bytes
- ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, -1))])
+ ImageFile._save(
+ im, fp, [ImageFile._Tile("raw", (0, 0) + im.size, 0, (rawmode, 0, -1))]
+ )
#
diff --git a/src/PIL/Image.py b/src/PIL/Image.py
index a424f3bc2..a5ada6c50 100644
--- a/src/PIL/Image.py
+++ b/src/PIL/Image.py
@@ -38,10 +38,17 @@ import struct
import sys
import tempfile
import warnings
-from collections.abc import Callable, MutableMapping
+from collections.abc import Callable, Iterator, MutableMapping, Sequence
from enum import IntEnum
from types import ModuleType
-from typing import IO, TYPE_CHECKING, Any, Literal, Protocol, Sequence, cast
+from typing import (
+ IO,
+ TYPE_CHECKING,
+ Any,
+ Literal,
+ Protocol,
+ cast,
+)
# VERSION was removed in Pillow 6.0.0.
# PILLOW_VERSION was removed in Pillow 9.0.0.
@@ -56,7 +63,6 @@ from . import (
)
from ._binary import i32le, o32be, o32le
from ._deprecate import deprecate
-from ._typing import StrOrBytesPath, TypeGuard
from ._util import DeferredError, is_path
ElementTree: ModuleType | None
@@ -76,6 +82,8 @@ class DecompressionBombError(Exception):
pass
+WARN_POSSIBLE_FORMATS: bool = False
+
# Limit to around a quarter gigabyte for a 24-bit (3 bpp) image
MAX_IMAGE_PIXELS: int | None = int(1024 * 1024 * 1024 // 4 // 3)
@@ -114,14 +122,6 @@ except ImportError as v:
raise
-USE_CFFI_ACCESS = False
-cffi: ModuleType | None
-try:
- import cffi
-except ImportError:
- cffi = None
-
-
def isImageType(t: Any) -> TypeGuard[Image]:
"""
Checks if an object is an image object.
@@ -133,6 +133,7 @@ def isImageType(t: Any) -> TypeGuard[Image]:
:param t: object to check if it's an image
:returns: True if the object is an image
"""
+ deprecate("Image.isImageType(im)", 12, "isinstance(im, Image.Image)")
return hasattr(t, "im")
@@ -218,7 +219,13 @@ if hasattr(core, "DEFAULT_STRATEGY"):
# Registries
if TYPE_CHECKING:
- from . import ImageFile
+ import mmap
+ from xml.etree.ElementTree import Element
+
+ from IPython.lib.pretty import PrettyPrinter
+
+ from . import ImageFile, ImageFilter, ImagePalette, ImageQt, TiffImagePlugin
+ from ._typing import CapsuleType, NumpyArray, StrOrBytesPath, TypeGuard
ID: list[str] = []
OPEN: dict[
str,
@@ -240,9 +247,9 @@ ENCODERS: dict[str, type[ImageFile.PyEncoder]] = {}
_ENDIAN = "<" if sys.byteorder == "little" else ">"
-def _conv_type_shape(im):
+def _conv_type_shape(im: Image) -> tuple[tuple[int, ...], str]:
m = ImageMode.getmode(im.mode)
- shape = (im.height, im.width)
+ shape: tuple[int, ...] = (im.height, im.width)
extra = len(m.bands)
if extra != 1:
shape += (extra,)
@@ -410,7 +417,9 @@ def init() -> bool:
# Codec factories (used by tobytes/frombytes and ImageFile.load)
-def _getdecoder(mode, decoder_name, args, extra=()):
+def _getdecoder(
+ mode: str, decoder_name: str, args: Any, extra: tuple[Any, ...] = ()
+) -> core.ImagingDecoder | ImageFile.PyDecoder:
# tweak arguments
if args is None:
args = ()
@@ -433,7 +442,9 @@ def _getdecoder(mode, decoder_name, args, extra=()):
return decoder(mode, *args + extra)
-def _getencoder(mode, encoder_name, args, extra=()):
+def _getencoder(
+ mode: str, encoder_name: str, args: Any, extra: tuple[Any, ...] = ()
+) -> core.ImagingEncoder | ImageFile.PyEncoder:
# tweak arguments
if args is None:
args = ()
@@ -460,43 +471,53 @@ def _getencoder(mode, encoder_name, args, extra=()):
# Simple expression analyzer
-class _E:
- def __init__(self, scale, offset) -> None:
+class ImagePointTransform:
+ """
+ Used with :py:meth:`~PIL.Image.Image.point` for single band images with more than
+ 8 bits, this represents an affine transformation, where the value is multiplied by
+ ``scale`` and ``offset`` is added.
+ """
+
+ def __init__(self, scale: float, offset: float) -> None:
self.scale = scale
self.offset = offset
- def __neg__(self):
- return _E(-self.scale, -self.offset)
+ def __neg__(self) -> ImagePointTransform:
+ return ImagePointTransform(-self.scale, -self.offset)
- def __add__(self, other):
- if isinstance(other, _E):
- return _E(self.scale + other.scale, self.offset + other.offset)
- return _E(self.scale, self.offset + other)
+ def __add__(self, other: ImagePointTransform | float) -> ImagePointTransform:
+ if isinstance(other, ImagePointTransform):
+ return ImagePointTransform(
+ self.scale + other.scale, self.offset + other.offset
+ )
+ return ImagePointTransform(self.scale, self.offset + other)
__radd__ = __add__
- def __sub__(self, other):
+ def __sub__(self, other: ImagePointTransform | float) -> ImagePointTransform:
return self + -other
- def __rsub__(self, other):
+ def __rsub__(self, other: ImagePointTransform | float) -> ImagePointTransform:
return other + -self
- def __mul__(self, other):
- if isinstance(other, _E):
+ def __mul__(self, other: ImagePointTransform | float) -> ImagePointTransform:
+ if isinstance(other, ImagePointTransform):
return NotImplemented
- return _E(self.scale * other, self.offset * other)
+ return ImagePointTransform(self.scale * other, self.offset * other)
__rmul__ = __mul__
- def __truediv__(self, other):
- if isinstance(other, _E):
+ def __truediv__(self, other: ImagePointTransform | float) -> ImagePointTransform:
+ if isinstance(other, ImagePointTransform):
return NotImplemented
- return _E(self.scale / other, self.offset / other)
+ return ImagePointTransform(self.scale / other, self.offset / other)
-def _getscaleoffset(expr):
- a = expr(_E(1, 0))
- return (a.scale, a.offset) if isinstance(a, _E) else (0, a)
+def _getscaleoffset(
+ expr: Callable[[ImagePointTransform], ImagePointTransform | float]
+) -> tuple[float, float]:
+ a = expr(ImagePointTransform(1, 0))
+ return (a.scale, a.offset) if isinstance(a, ImagePointTransform) else (0, a)
# --------------------------------------------------------------------
@@ -525,17 +546,27 @@ class Image:
format_description: str | None = None
_close_exclusive_fp_after_loading = True
- def __init__(self):
+ def __init__(self) -> None:
# FIXME: take "new" parameters / other image?
# FIXME: turn mode and size into delegating properties?
- self.im = None
+ self._im: core.ImagingCore | DeferredError | None = None
self._mode = ""
self._size = (0, 0)
- self.palette = None
- self.info = {}
+ self.palette: ImagePalette.ImagePalette | None = None
+ self.info: dict[str | tuple[int, int], Any] = {}
self.readonly = 0
- self.pyaccess = None
- self._exif = None
+ self._exif: Exif | None = None
+
+ @property
+ def im(self) -> core.ImagingCore:
+ if isinstance(self._im, DeferredError):
+ raise self._im.ex
+ assert self._im is not None
+ return self._im
+
+ @im.setter
+ def im(self, im: core.ImagingCore) -> None:
+ self._im = im
@property
def width(self) -> int:
@@ -550,10 +581,10 @@ class Image:
return self._size
@property
- def mode(self):
+ def mode(self) -> str:
return self._mode
- def _new(self, im) -> Image:
+ def _new(self, im: core.ImagingCore) -> Image:
new = Image()
new.im = im
new._mode = im.mode
@@ -606,17 +637,16 @@ class Image:
logger.debug("Error closing: %s", msg)
if getattr(self, "map", None):
- self.map = None
+ self.map: mmap.mmap | None = None
# Instead of simply setting to None, we're setting up a
# deferred error that will better explain that the core image
# object is gone.
- self.im = DeferredError(ValueError("Operation on closed image"))
+ self._im = DeferredError(ValueError("Operation on closed image"))
def _copy(self) -> None:
self.load()
self.im = self.im.copy()
- self.pyaccess = None
self.readonly = 0
def _ensure_mutable(self) -> None:
@@ -626,7 +656,7 @@ class Image:
self.load()
def _dump(
- self, file: str | None = None, format: str | None = None, **options
+ self, file: str | None = None, format: str | None = None, **options: Any
) -> str:
suffix = ""
if format:
@@ -649,10 +679,12 @@ class Image:
return filename
- def __eq__(self, other):
+ def __eq__(self, other: object) -> bool:
+ if self.__class__ is not other.__class__:
+ return False
+ assert isinstance(other, Image)
return (
- self.__class__ is other.__class__
- and self.mode == other.mode
+ self.mode == other.mode
and self.size == other.size
and self.info == other.info
and self.getpalette() == other.getpalette()
@@ -660,32 +692,23 @@ class Image:
)
def __repr__(self) -> str:
- return "<%s.%s image mode=%s size=%dx%d at 0x%X>" % (
- self.__class__.__module__,
- self.__class__.__name__,
- self.mode,
- self.size[0],
- self.size[1],
- id(self),
+ return (
+ f"<{self.__class__.__module__}.{self.__class__.__name__} "
+ f"image mode={self.mode} size={self.size[0]}x{self.size[1]} "
+ f"at 0x{id(self):X}>"
)
- def _repr_pretty_(self, p, cycle) -> None:
+ def _repr_pretty_(self, p: PrettyPrinter, cycle: bool) -> None:
"""IPython plain text display support"""
# Same as __repr__ but without unpredictable id(self),
# to keep Jupyter notebook `text/plain` output stable.
p.text(
- "<%s.%s image mode=%s size=%dx%d>"
- % (
- self.__class__.__module__,
- self.__class__.__name__,
- self.mode,
- self.size[0],
- self.size[1],
- )
+ f"<{self.__class__.__module__}.{self.__class__.__name__} "
+ f"image mode={self.mode} size={self.size[0]}x{self.size[1]}>"
)
- def _repr_image(self, image_format, **kwargs):
+ def _repr_image(self, image_format: str, **kwargs: Any) -> bytes | None:
"""Helper function for iPython display hook.
:param image_format: Image format.
@@ -698,14 +721,14 @@ class Image:
return None
return b.getvalue()
- def _repr_png_(self):
+ def _repr_png_(self) -> bytes | None:
"""iPython display hook support for PNG format.
:returns: PNG version of the image as bytes
"""
return self._repr_image("PNG", compress_level=1)
- def _repr_jpeg_(self):
+ def _repr_jpeg_(self) -> bytes | None:
"""iPython display hook support for JPEG format.
:returns: JPEG version of the image as bytes
@@ -713,37 +736,25 @@ class Image:
return self._repr_image("JPEG")
@property
- def __array_interface__(self):
+ def __array_interface__(self) -> dict[str, str | bytes | int | tuple[int, ...]]:
# numpy array interface support
- new = {"version": 3}
- try:
- if self.mode == "1":
- # Binary images need to be extended from bits to bytes
- # See: https://github.com/python-pillow/Pillow/issues/350
- new["data"] = self.tobytes("raw", "L")
- else:
- new["data"] = self.tobytes()
- except Exception as e:
- if not isinstance(e, (MemoryError, RecursionError)):
- try:
- import numpy
- from packaging.version import parse as parse_version
- except ImportError:
- pass
- else:
- if parse_version(numpy.__version__) < parse_version("1.23"):
- warnings.warn(str(e))
- raise
+ new: dict[str, str | bytes | int | tuple[int, ...]] = {"version": 3}
+ if self.mode == "1":
+ # Binary images need to be extended from bits to bytes
+ # See: https://github.com/python-pillow/Pillow/issues/350
+ new["data"] = self.tobytes("raw", "L")
+ else:
+ new["data"] = self.tobytes()
new["shape"], new["typestr"] = _conv_type_shape(self)
return new
- def __getstate__(self):
+ def __getstate__(self) -> list[Any]:
im_data = self.tobytes() # load image first
return [self.info, self.mode, self.size, self.getpalette(), im_data]
- def __setstate__(self, state) -> None:
+ def __setstate__(self, state: list[Any]) -> None:
Image.__init__(self)
- info, mode, size, palette, data = state
+ info, mode, size, palette, data = state[:5]
self.info = info
self._mode = mode
self._size = size
@@ -752,7 +763,7 @@ class Image:
self.putpalette(palette)
self.frombytes(data)
- def tobytes(self, encoder_name: str = "raw", *args) -> bytes:
+ def tobytes(self, encoder_name: str = "raw", *args: Any) -> bytes:
"""
Return image as a bytes object.
@@ -774,12 +785,13 @@ class Image:
:returns: A :py:class:`bytes` object.
"""
- # may pass tuple instead of argument list
- if len(args) == 1 and isinstance(args[0], tuple):
- args = args[0]
+ encoder_args: Any = args
+ if len(encoder_args) == 1 and isinstance(encoder_args[0], tuple):
+ # may pass tuple instead of argument list
+ encoder_args = encoder_args[0]
- if encoder_name == "raw" and args == ():
- args = self.mode
+ if encoder_name == "raw" and encoder_args == ():
+ encoder_args = self.mode
self.load()
@@ -787,7 +799,7 @@ class Image:
return b""
# unpack data
- e = _getencoder(self.mode, encoder_name, args)
+ e = _getencoder(self.mode, encoder_name, encoder_args)
e.setimage(self.im)
bufsize = max(65536, self.size[0] * 4) # see RawEncode.c
@@ -830,7 +842,12 @@ class Image:
]
)
- def frombytes(self, data: bytes, decoder_name: str = "raw", *args) -> None:
+ def frombytes(
+ self,
+ data: bytes | bytearray | SupportsArrayInterface,
+ decoder_name: str = "raw",
+ *args: Any,
+ ) -> None:
"""
Loads this image with pixel data from a bytes object.
@@ -841,16 +858,17 @@ class Image:
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]
+ decoder_args: Any = args
+ if len(decoder_args) == 1 and isinstance(decoder_args[0], tuple):
+ # may pass tuple instead of argument list
+ decoder_args = decoder_args[0]
# default format
- if decoder_name == "raw" and args == ():
- args = self.mode
+ if decoder_name == "raw" and decoder_args == ():
+ decoder_args = self.mode
# unpack data
- d = _getdecoder(self.mode, decoder_name, args)
+ d = _getdecoder(self.mode, decoder_name, decoder_args)
d.setimage(self.im)
s = d.decode(data)
@@ -861,7 +879,7 @@ class Image:
msg = "cannot decode image data"
raise ValueError(msg)
- def load(self):
+ def load(self) -> core.PixelAccess | None:
"""
Allocates storage for the image and loads the pixel data. In
normal cases, you don't need to call this method, since the
@@ -874,12 +892,12 @@ class Image:
operations. See :ref:`file-handling` for more information.
:returns: An image access object.
- :rtype: :ref:`PixelAccess` or :py:class:`PIL.PyAccess`
+ :rtype: :py:class:`.PixelAccess`
"""
- if self.im is not None and self.palette and self.palette.dirty:
+ if self._im is not None and self.palette and self.palette.dirty:
# realize palette
mode, arr = self.palette.getdata()
- self.im.putpalette(mode, arr)
+ self.im.putpalette(self.palette.mode, mode, arr)
self.palette.dirty = 0
self.palette.rawmode = None
if "transparency" in self.info and mode in ("LA", "PA"):
@@ -889,20 +907,13 @@ class Image:
self.im.putpalettealphas(self.info["transparency"])
self.palette.mode = "RGBA"
else:
- palette_mode = "RGBA" if mode.startswith("RGBA") else "RGB"
- self.palette.mode = palette_mode
- self.palette.palette = self.im.getpalette(palette_mode, palette_mode)
+ self.palette.palette = self.im.getpalette(
+ self.palette.mode, self.palette.mode
+ )
- if self.im is not None:
- if cffi and USE_CFFI_ACCESS:
- if self.pyaccess:
- return self.pyaccess
- from . import PyAccess
-
- self.pyaccess = PyAccess.new(self, self.readonly)
- if self.pyaccess:
- return self.pyaccess
+ if self._im is not None:
return self.im.pixel_access(self.readonly)
+ return None
def verify(self) -> None:
"""
@@ -994,9 +1005,11 @@ class Image:
if has_transparency and self.im.bands == 3:
transparency = new_im.info["transparency"]
- def convert_transparency(m, v):
- v = m[0] * v[0] + m[1] * v[1] + m[2] * v[2] + m[3] * 0.5
- return max(0, min(255, int(v)))
+ def convert_transparency(
+ m: tuple[float, ...], v: tuple[int, int, int]
+ ) -> int:
+ value = m[0] * v[0] + m[1] * v[1] + m[2] * v[2] + m[3] * 0.5
+ return max(0, min(255, int(value)))
if mode == "L":
transparency = convert_transparency(matrix, transparency)
@@ -1039,9 +1052,11 @@ class Image:
# use existing conversions
trns_im = new(self.mode, (1, 1))
if self.mode == "P":
- trns_im.putpalette(self.palette)
+ assert self.palette is not None
+ trns_im.putpalette(self.palette, self.palette.mode)
if isinstance(t, tuple):
err = "Couldn't allocate a palette color for transparency"
+ assert trns_im.palette is not None
try:
t = trns_im.palette.getcolor(t, self)
except ValueError as e:
@@ -1090,7 +1105,10 @@ class Image:
del new_im.info["transparency"]
if trns is not None:
try:
- new_im.info["transparency"] = new_im.palette.getcolor(trns, new_im)
+ new_im.info["transparency"] = new_im.palette.getcolor(
+ cast(tuple[int, ...], trns), # trns was converted to RGB
+ new_im,
+ )
except Exception:
# if we can't make a transparent color, don't leave the old
# transparency hanging around to mess us up.
@@ -1099,17 +1117,23 @@ class Image:
return new_im
if "LAB" in (self.mode, mode):
- other_mode = mode if self.mode == "LAB" else self.mode
+ im = self
+ if mode == "LAB":
+ if im.mode not in ("RGB", "RGBA", "RGBX"):
+ im = im.convert("RGBA")
+ other_mode = im.mode
+ else:
+ other_mode = mode
if other_mode in ("RGB", "RGBA", "RGBX"):
from . import ImageCms
srgb = ImageCms.createProfile("sRGB")
lab = ImageCms.createProfile("LAB")
- profiles = [lab, srgb] if self.mode == "LAB" else [srgb, lab]
+ profiles = [lab, srgb] if im.mode == "LAB" else [srgb, lab]
transform = ImageCms.buildTransform(
- profiles[0], profiles[1], self.mode, mode
+ profiles[0], profiles[1], im.mode, mode
)
- return transform.apply(self)
+ return transform.apply(im)
# colorspace conversion
if dither is None:
@@ -1138,9 +1162,11 @@ class Image:
# crash fail if we leave a bytes transparency in an rgb/l mode.
del new_im.info["transparency"]
if trns is not None:
- if new_im.mode == "P":
+ if new_im.mode == "P" and new_im.palette:
try:
- new_im.info["transparency"] = new_im.palette.getcolor(trns, new_im)
+ new_im.info["transparency"] = new_im.palette.getcolor(
+ cast(tuple[int, ...], trns), new_im # trns was converted to RGB
+ )
except ValueError as e:
del new_im.info["transparency"]
if str(e) != "cannot allocate more than 256 colors":
@@ -1156,9 +1182,9 @@ class Image:
def quantize(
self,
colors: int = 256,
- method: Quantize | None = None,
+ method: int | None = None,
kmeans: int = 0,
- palette=None,
+ palette: Image | None = None,
dither: Dither = Dither.FLOYDSTEINBERG,
) -> Image:
"""
@@ -1218,6 +1244,7 @@ class Image:
raise ValueError(msg)
im = self.im.convert("P", dither, palette.im)
new_im = self._new(im)
+ assert palette.palette is not None
new_im.palette = palette.palette.copy()
return new_im
@@ -1230,8 +1257,8 @@ class Image:
from . import ImagePalette
mode = im.im.getpalettemode()
- palette = im.im.getpalette(mode, mode)[: colors * len(mode)]
- im.palette = ImagePalette.ImagePalette(mode, palette)
+ palette_data = im.im.getpalette(mode, mode)[: colors * len(mode)]
+ im.palette = ImagePalette.ImagePalette(mode, palette_data)
return im
@@ -1248,7 +1275,7 @@ class Image:
__copy__ = copy
- def crop(self, box: tuple[int, int, int, int] | None = None) -> Image:
+ def crop(self, box: tuple[float, float, float, float] | None = None) -> Image:
"""
Returns a rectangular region from this image. The box is a
4-tuple defining the left, upper, right, and lower pixel
@@ -1274,7 +1301,9 @@ class Image:
self.load()
return self._new(self._crop(self.im, box))
- def _crop(self, im, box):
+ def _crop(
+ self, im: core.ImagingCore, box: tuple[float, float, float, float]
+ ) -> core.ImagingCore:
"""
Returns a rectangular region from the core image object im.
@@ -1295,7 +1324,7 @@ class Image:
return im.crop((x0, y0, x1, y1))
def draft(
- self, mode: str | None, size: tuple[int, int]
+ self, mode: str | None, size: tuple[int, int] | None
) -> tuple[str, tuple[int, int, float, float]] | None:
"""
Configures the image file loader so it returns a version of the
@@ -1325,9 +1354,6 @@ class Image:
self.load()
return self._new(self.im.expand(xmargin, ymargin))
- if TYPE_CHECKING:
- from . import ImageFilter
-
def filter(self, filter: ImageFilter.Filter | type[ImageFilter.Filter]) -> Image:
"""
Filters this image using the given filter. For a list of
@@ -1365,7 +1391,7 @@ class Image:
"""
return ImageMode.getmode(self.mode).bands
- def getbbox(self, *, alpha_only: bool = True) -> tuple[int, int, int, int]:
+ def getbbox(self, *, alpha_only: bool = True) -> tuple[int, int, int, int] | None:
"""
Calculates the bounding box of the non-zero regions in the
image.
@@ -1384,7 +1410,9 @@ class Image:
self.load()
return self.im.getbbox(alpha_only)
- def getcolors(self, maxcolors: int = 256):
+ def getcolors(
+ self, maxcolors: int = 256
+ ) -> list[tuple[int, tuple[int, ...]]] | list[tuple[int, float]] | None:
"""
Returns a list of colors used in this image.
@@ -1401,13 +1429,13 @@ class Image:
self.load()
if self.mode in ("1", "L", "P"):
h = self.im.histogram()
- out = [(h[i], i) for i in range(256) if h[i]]
+ out: list[tuple[int, float]] = [(h[i], i) for i in range(256) if h[i]]
if len(out) > maxcolors:
return None
return out
return self.im.getcolors(maxcolors)
- def getdata(self, band: int | None = None):
+ def getdata(self, band: int | None = None) -> core.ImagingCore:
"""
Returns the contents of this image as a sequence object
containing pixel values. The sequence object is flattened, so
@@ -1445,12 +1473,19 @@ class Image:
return tuple(self.im.getband(i).getextrema() for i in range(self.im.bands))
return self.im.getextrema()
- def _getxmp(self, xmp_tags):
- def get_name(tag):
+ def getxmp(self) -> dict[str, Any]:
+ """
+ Returns a dictionary containing the XMP tags.
+ Requires defusedxml to be installed.
+
+ :returns: XMP tags in a dictionary.
+ """
+
+ def get_name(tag: str) -> str:
return re.sub("^{[^}]+}", "", tag)
- def get_value(element):
- value = {get_name(k): v for k, v in element.attrib.items()}
+ def get_value(element: Element) -> str | dict[str, Any] | None:
+ value: dict[str, Any] = {get_name(k): v for k, v in element.attrib.items()}
children = list(element)
if children:
for child in children:
@@ -1472,9 +1507,10 @@ class Image:
if ElementTree is None:
warnings.warn("XMP data cannot be read without defusedxml dependency")
return {}
- else:
- root = ElementTree.fromstring(xmp_tags)
- return {get_name(root.tag): get_value(root)}
+ if "xmp" not in self.info:
+ return {}
+ root = ElementTree.fromstring(self.info["xmp"].rstrip(b"\x00"))
+ return {get_name(root.tag): get_value(root)}
def getexif(self) -> Exif:
"""
@@ -1529,7 +1565,8 @@ class Image:
for subifd_offset in subifd_offsets:
ifds.append((exif._get_ifd_dict(subifd_offset), subifd_offset))
ifd1 = exif.get_ifd(ExifTags.IFD.IFD1)
- if ifd1 and ifd1.get(513):
+ if ifd1 and ifd1.get(ExifTags.Base.JpegIFOffset):
+ assert exif._info is not None
ifds.append((ifd1, exif._info.next))
offset = None
@@ -1539,15 +1576,20 @@ class Image:
offset = current_offset
fp = self.fp
- thumbnail_offset = ifd.get(513)
- if thumbnail_offset is not None:
- thumbnail_offset += getattr(self, "_exif_offset", 0)
- self.fp.seek(thumbnail_offset)
- data = self.fp.read(ifd.get(514))
- fp = io.BytesIO(data)
+ if ifd is not None:
+ thumbnail_offset = ifd.get(ExifTags.Base.JpegIFOffset)
+ if thumbnail_offset is not None:
+ thumbnail_offset += getattr(self, "_exif_offset", 0)
+ self.fp.seek(thumbnail_offset)
+ data = self.fp.read(ifd.get(ExifTags.Base.JpegIFByteCount))
+ fp = io.BytesIO(data)
with open(fp) as im:
- if thumbnail_offset is None:
+ from . import TiffImagePlugin
+
+ if thumbnail_offset is None and isinstance(
+ im, TiffImagePlugin.TiffImageFile
+ ):
im._frame_pos = [ifd_offset]
im._seek(0)
im.load()
@@ -1557,7 +1599,7 @@ class Image:
self.fp.seek(offset)
return child_images
- def getim(self):
+ def getim(self) -> CapsuleType:
"""
Returns a capsule that points to the internal image memory.
@@ -1601,11 +1643,15 @@ class Image:
:returns: A boolean.
"""
- return (
+ if (
self.mode in ("LA", "La", "PA", "RGBA", "RGBa")
- or (self.mode == "P" and self.palette.mode.endswith("A"))
or "transparency" in self.info
- )
+ ):
+ return True
+ if self.mode == "P":
+ assert self.palette is not None
+ return self.palette.mode.endswith("A")
+ return False
def apply_transparency(self) -> None:
"""
@@ -1631,7 +1677,9 @@ class Image:
del self.info["transparency"]
- def getpixel(self, xy):
+ def getpixel(
+ self, xy: tuple[int, int] | list[int]
+ ) -> float | tuple[int, ...] | None:
"""
Returns the pixel value at a given position.
@@ -1642,8 +1690,6 @@ class Image:
"""
self.load()
- if self.pyaccess:
- return self.pyaccess.getpixel(xy)
return self.im.getpixel(tuple(xy))
def getprojection(self) -> tuple[list[int], list[int]]:
@@ -1658,7 +1704,9 @@ class Image:
x, y = self.im.getprojection()
return list(x), list(y)
- def histogram(self, mask: Image | None = None, extrema=None) -> list[int]:
+ def histogram(
+ self, mask: Image | None = None, extrema: tuple[float, float] | None = None
+ ) -> list[int]:
"""
Returns a histogram for the image. The histogram is returned as a
list of pixel counts, one for each pixel value in the source
@@ -1684,12 +1732,14 @@ class Image:
mask.load()
return self.im.histogram((0, 0), mask.im)
if self.mode in ("I", "F"):
- if extrema is None:
- extrema = self.getextrema()
- return self.im.histogram(extrema)
+ return self.im.histogram(
+ extrema if extrema is not None else self.getextrema()
+ )
return self.im.histogram()
- def entropy(self, mask=None, extrema=None):
+ def entropy(
+ self, mask: Image | None = None, extrema: tuple[float, float] | None = None
+ ) -> float:
"""
Calculates and returns the entropy for the image.
@@ -1710,15 +1760,15 @@ class Image:
mask.load()
return self.im.entropy((0, 0), mask.im)
if self.mode in ("I", "F"):
- if extrema is None:
- extrema = self.getextrema()
- return self.im.entropy(extrema)
+ return self.im.entropy(
+ extrema if extrema is not None else self.getextrema()
+ )
return self.im.entropy()
def paste(
self,
im: Image | str | float | tuple[float, ...],
- box: tuple[int, int, int, int] | tuple[int, int] | None = None,
+ box: Image | tuple[int, int, int, int] | tuple[int, int] | None = None,
mask: Image | None = None,
) -> None:
"""
@@ -1760,7 +1810,10 @@ class Image:
:param mask: An optional mask image.
"""
- if isImageType(box) and mask is None:
+ if isinstance(box, Image):
+ if mask is not None:
+ msg = "If using second argument as mask, third argument must be None"
+ raise ValueError(msg)
# abbreviated paste(im, mask) syntax
mask = box
box = None
@@ -1770,9 +1823,9 @@ class Image:
if len(box) == 2:
# upper left corner given; get size from image or mask
- if isImageType(im):
+ if isinstance(im, Image):
size = im.size
- elif isImageType(mask):
+ elif isinstance(mask, Image):
size = mask.size
else:
# FIXME: use self.size here?
@@ -1780,28 +1833,32 @@ class Image:
raise ValueError(msg)
box += (box[0] + size[0], box[1] + size[1])
+ source: core.ImagingCore | str | float | tuple[float, ...]
if isinstance(im, str):
from . import ImageColor
- im = ImageColor.getcolor(im, self.mode)
-
- elif isImageType(im):
+ source = ImageColor.getcolor(im, self.mode)
+ elif isinstance(im, Image):
im.load()
if self.mode != im.mode:
if self.mode != "RGB" or im.mode not in ("LA", "RGBA", "RGBa"):
# should use an adapter for this!
im = im.convert(self.mode)
- im = im.im
+ source = im.im
+ else:
+ source = im
self._ensure_mutable()
if mask:
mask.load()
- self.im.paste(im, box, mask.im)
+ self.im.paste(source, box, mask.im)
else:
- self.im.paste(im, box)
+ self.im.paste(source, box)
- def alpha_composite(self, im, dest=(0, 0), source=(0, 0)):
+ def alpha_composite(
+ self, im: Image, dest: Sequence[int] = (0, 0), source: Sequence[int] = (0, 0)
+ ) -> None:
"""'In-place' analog of Image.alpha_composite. Composites an image
onto this image.
@@ -1816,32 +1873,35 @@ class Image:
"""
if not isinstance(source, (list, tuple)):
- msg = "Source must be a tuple"
+ msg = "Source must be a list or tuple"
raise ValueError(msg)
if not isinstance(dest, (list, tuple)):
- msg = "Destination must be a tuple"
+ msg = "Destination must be a list or tuple"
raise ValueError(msg)
- if len(source) not in (2, 4):
- msg = "Source must be a 2 or 4-tuple"
+
+ if len(source) == 4:
+ overlay_crop_box = tuple(source)
+ elif len(source) == 2:
+ overlay_crop_box = tuple(source) + im.size
+ else:
+ msg = "Source must be a sequence of length 2 or 4"
raise ValueError(msg)
+
if not len(dest) == 2:
- msg = "Destination must be a 2-tuple"
+ msg = "Destination must be a sequence of length 2"
raise ValueError(msg)
if min(source) < 0:
msg = "Source must be non-negative"
raise ValueError(msg)
- if len(source) == 2:
- source = source + im.size
-
- # over image, crop if it's not the whole thing.
- if source == (0, 0) + im.size:
+ # over image, crop if it's not the whole image.
+ if overlay_crop_box == (0, 0) + im.size:
overlay = im
else:
- overlay = im.crop(source)
+ overlay = im.crop(overlay_crop_box)
# target for the paste
- box = dest + (dest[0] + overlay.width, dest[1] + overlay.height)
+ box = tuple(dest) + (dest[0] + overlay.width, dest[1] + overlay.height)
# destination image. don't copy if we're using the whole image.
if box == (0, 0) + self.size:
@@ -1852,7 +1912,17 @@ class Image:
result = alpha_composite(background, overlay)
self.paste(result, box)
- def point(self, lut, mode: str | None = None) -> Image:
+ def point(
+ self,
+ lut: (
+ Sequence[float]
+ | NumpyArray
+ | Callable[[int], float]
+ | Callable[[ImagePointTransform], ImagePointTransform | float]
+ | ImagePointHandler
+ ),
+ mode: str | None = None,
+ ) -> Image:
"""
Maps this image through a lookup table or function.
@@ -1867,7 +1937,7 @@ class Image:
object::
class Example(Image.ImagePointHandler):
- def point(self, data):
+ def point(self, im: Image) -> Image:
# Return result
:param mode: Output mode (default is same as input). This can only be used if
the source image has mode "L" or "P", and the output has mode "1" or the
@@ -1886,10 +1956,12 @@ class Image:
# check if the function can be used with point_transform
# UNDONE wiredfool -- I think this prevents us from ever doing
# a gamma function point transform on > 8bit images.
- scale, offset = _getscaleoffset(lut)
+ scale, offset = _getscaleoffset(lut) # type: ignore[arg-type]
return self._new(self.im.point_transform(scale, offset))
# for other modes, convert the function to a table
- lut = [lut(i) for i in range(256)] * self.im.bands
+ flatLut = [lut(i) for i in range(256)] * self.im.bands # type: ignore[arg-type]
+ else:
+ flatLut = lut
if self.mode == "F":
# FIXME: _imaging returns a confusing error message for this case
@@ -1897,18 +1969,17 @@ class Image:
raise ValueError(msg)
if mode != "F":
- lut = [round(i) for i in lut]
- return self._new(self.im.point(lut, mode))
+ flatLut = [round(i) for i in flatLut]
+ return self._new(self.im.point(flatLut, mode))
- def putalpha(self, alpha):
+ def putalpha(self, alpha: Image | int) -> None:
"""
Adds or replaces the alpha layer in this image. If the image
does not have an alpha layer, it's converted to "LA" or "RGBA".
The new layer must be either "L" or "1".
:param alpha: The new alpha layer. This can either be an "L" or "1"
- image having the same size as this image, or an integer or
- other color value.
+ image having the same size as this image, or an integer.
"""
self._ensure_mutable()
@@ -1926,7 +1997,6 @@ class Image:
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
except KeyError as e:
msg = "illegal image mode"
@@ -1937,7 +2007,7 @@ class Image:
else:
band = 3
- if isImageType(alpha):
+ if isinstance(alpha, Image):
# alpha layer
if alpha.mode not in ("1", "L"):
msg = "illegal image mode"
@@ -1958,7 +2028,10 @@ class Image:
self.im.putband(alpha.im, band)
def putdata(
- self, data: Sequence[float], scale: float = 1.0, offset: float = 0.0
+ self,
+ data: Sequence[float] | Sequence[Sequence[int]] | core.ImagingCore | NumpyArray,
+ scale: float = 1.0,
+ offset: float = 0.0,
) -> None:
"""
Copies pixel data from a flattened sequence object into the image. The
@@ -1977,7 +2050,11 @@ class Image:
self.im.putdata(data, scale, offset)
- def putpalette(self, data, rawmode="RGB") -> None:
+ def putpalette(
+ self,
+ data: ImagePalette.ImagePalette | bytes | Sequence[int],
+ rawmode: str = "RGB",
+ ) -> None:
"""
Attaches a palette to this image. The image must be a "P", "PA", "L"
or "LA" image.
@@ -2002,17 +2079,23 @@ class Image:
msg = "illegal image mode"
raise ValueError(msg)
if isinstance(data, ImagePalette.ImagePalette):
- palette = ImagePalette.raw(data.rawmode, data.palette)
+ if data.rawmode is not None:
+ palette = ImagePalette.raw(data.rawmode, data.palette)
+ else:
+ palette = ImagePalette.ImagePalette(palette=data.palette)
+ palette.dirty = 1
else:
if not isinstance(data, bytes):
data = bytes(data)
palette = ImagePalette.raw(rawmode, data)
self._mode = "PA" if "A" in self.mode else "P"
self.palette = palette
- self.palette.mode = "RGB"
+ self.palette.mode = "RGBA" if "A" in rawmode else "RGB"
self.load() # install new palette
- def putpixel(self, xy, value):
+ def putpixel(
+ self, xy: tuple[int, int], value: float | tuple[int, ...] | list[int]
+ ) -> None:
"""
Modifies the pixel at the given position. The color is given as
a single numerical value for single-band images, and a tuple for
@@ -2038,9 +2121,6 @@ class Image:
self._copy()
self.load()
- if self.pyaccess:
- return self.pyaccess.putpixel(xy, value)
-
if (
self.mode in ("P", "PA")
and isinstance(value, (list, tuple))
@@ -2050,12 +2130,14 @@ class Image:
if self.mode == "PA":
alpha = value[3] if len(value) == 4 else 255
value = value[:3]
- value = self.palette.getcolor(value, self)
- if self.mode == "PA":
- value = (value, alpha)
+ assert self.palette is not None
+ palette_index = self.palette.getcolor(tuple(value), self)
+ value = (palette_index, alpha) if self.mode == "PA" else palette_index
return self.im.putpixel(xy, value)
- def remap_palette(self, dest_map, source_palette=None):
+ def remap_palette(
+ self, dest_map: list[int], source_palette: bytes | bytearray | None = None
+ ) -> Image:
"""
Rewrites the image to reorder the palette.
@@ -2083,6 +2165,9 @@ class Image:
source_palette = self.im.getpalette(palette_mode, palette_mode)
else: # L-mode
source_palette = bytearray(i // 3 for i in range(768))
+ elif len(source_palette) > 768:
+ bands = 4
+ palette_mode = "RGBA"
palette_bytes = b""
new_positions = [0] * 256
@@ -2124,7 +2209,7 @@ class Image:
# m_im.putpalette(mapping_palette, 'L') # converts to 'P'
# or just force it.
# UNDONE -- this is part of the general issue with palettes
- m_im.im.putpalette(palette_mode + ";L", m_im.palette.tobytes())
+ m_im.im.putpalette(palette_mode, palette_mode + ";L", m_im.palette.tobytes())
m_im = m_im.convert("L")
@@ -2140,7 +2225,12 @@ class Image:
return m_im
- def _get_safe_box(self, size, resample, box):
+ def _get_safe_box(
+ self,
+ size: tuple[int, int],
+ resample: Resampling,
+ box: tuple[float, float, float, float],
+ ) -> tuple[int, int, int, int]:
"""Expands the box so it includes adjacent pixels
that may be used by resampling with the given resampling filter.
"""
@@ -2159,7 +2249,7 @@ class Image:
def resize(
self,
- size: tuple[int, int],
+ size: tuple[int, int] | list[int] | NumpyArray,
resample: int | None = None,
box: tuple[float, float, float, float] | None = None,
reducing_gap: float | None = None,
@@ -2167,15 +2257,15 @@ class Image:
"""
Returns a resized copy of this image.
- :param size: The requested size in pixels, as a 2-tuple:
+ :param size: The requested size in pixels, as a tuple or array:
(width, height).
:param resample: An optional resampling filter. This can be
one of :py:data:`Resampling.NEAREST`, :py:data:`Resampling.BOX`,
:py:data:`Resampling.BILINEAR`, :py:data:`Resampling.HAMMING`,
:py:data:`Resampling.BICUBIC` or :py:data:`Resampling.LANCZOS`.
If the image has mode "1" or "P", it is always set to
- :py:data:`Resampling.NEAREST`. If the image mode specifies a number
- of bits, such as "I;16", then the default filter is
+ :py:data:`Resampling.NEAREST`. If the image mode is "BGR;15",
+ "BGR;16" or "BGR;24", then the default filter is
:py:data:`Resampling.NEAREST`. Otherwise, the default filter is
:py:data:`Resampling.BICUBIC`. See: :ref:`concept-filters`.
:param box: An optional 4-tuple of floats providing
@@ -2198,8 +2288,8 @@ class Image:
"""
if resample is None:
- type_special = ";" in self.mode
- resample = Resampling.NEAREST if type_special else Resampling.BICUBIC
+ bgr = self.mode.startswith("BGR;")
+ resample = Resampling.NEAREST if bgr else Resampling.BICUBIC
elif resample not in (
Resampling.NEAREST,
Resampling.BILINEAR,
@@ -2228,10 +2318,10 @@ class Image:
msg = "reducing_gap must be 1.0 or greater"
raise ValueError(msg)
- self.load()
if box is None:
box = (0, 0) + self.size
+ size = tuple(size)
if self.size == size and box == (0, 0) + self.size:
return self.copy()
@@ -2249,7 +2339,7 @@ class Image:
factor_x = int((box[2] - box[0]) / size[0] / reducing_gap) or 1
factor_y = int((box[3] - box[1]) / size[1] / reducing_gap) or 1
if factor_x > 1 or factor_y > 1:
- reduce_box = self._get_safe_box(size, resample, box)
+ reduce_box = self._get_safe_box(size, cast(Resampling, resample), box)
factor = (factor_x, factor_y)
self = (
self.reduce(factor, box=reduce_box)
@@ -2305,7 +2395,7 @@ class Image:
angle: float,
resample: Resampling = Resampling.NEAREST,
expand: int | bool = False,
- center: tuple[int, int] | None = None,
+ center: tuple[float, float] | None = None,
translate: tuple[int, int] | None = None,
fillcolor: float | tuple[float, ...] | str | None = None,
) -> Image:
@@ -2373,10 +2463,7 @@ class Image:
else:
post_trans = translate
if center is None:
- # FIXME These should be rounded to ints?
- rotn_center = (w / 2.0, h / 2.0)
- else:
- rotn_center = center
+ center = (w / 2, h / 2)
angle = -math.radians(angle)
matrix = [
@@ -2388,24 +2475,24 @@ class Image:
0.0,
]
- def transform(x, y, matrix):
+ def transform(x: float, y: float, matrix: list[float]) -> tuple[float, float]:
(a, b, c, d, e, f) = matrix
return a * x + b * y + c, d * x + e * y + f
matrix[2], matrix[5] = transform(
- -rotn_center[0] - post_trans[0], -rotn_center[1] - post_trans[1], matrix
+ -center[0] - post_trans[0], -center[1] - post_trans[1], matrix
)
- matrix[2] += rotn_center[0]
- matrix[5] += rotn_center[1]
+ matrix[2] += center[0]
+ matrix[5] += center[1]
if expand:
# calculate output size
xx = []
yy = []
for x, y in ((0, 0), (w, 0), (w, h), (0, h)):
- x, y = transform(x, y, matrix)
- xx.append(x)
- yy.append(y)
+ transformed_x, transformed_y = transform(x, y, matrix)
+ xx.append(transformed_x)
+ yy.append(transformed_y)
nw = math.ceil(max(xx)) - math.floor(min(xx))
nh = math.ceil(max(yy)) - math.floor(min(yy))
@@ -2454,7 +2541,7 @@ class Image:
filename: str | bytes = ""
open_fp = False
if is_path(fp):
- filename = os.path.realpath(os.fspath(fp))
+ filename = os.fspath(fp)
open_fp = True
elif fp == sys.stdout:
try:
@@ -2463,7 +2550,7 @@ class Image:
pass
if not filename and hasattr(fp, "name") and is_path(fp.name):
# only set the name for metadata purposes
- filename = os.path.realpath(os.fspath(fp.name))
+ filename = os.fspath(fp.name)
# may mutate self!
self._ensure_mutable()
@@ -2497,7 +2584,7 @@ class Image:
if converted_im:
return converted_im.save(fp, format, **params)
- self.encoderinfo = params
+ self.encoderinfo = {**getattr(self, "encoderinfo", {}), **params}
self.encoderconfig: tuple[Any, ...] = ()
created = False
@@ -2523,6 +2610,11 @@ class Image:
except PermissionError:
pass
raise
+ finally:
+ try:
+ del self.encoderinfo
+ except AttributeError:
+ pass
if open_fp:
fp.close()
@@ -2682,7 +2774,7 @@ class Image:
self,
size: tuple[float, float],
resample: Resampling = Resampling.BICUBIC,
- reducing_gap: float = 2.0,
+ reducing_gap: float | None = 2.0,
) -> None:
"""
Make this image into a thumbnail. This method modifies the
@@ -2726,7 +2818,7 @@ class Image:
provided_size = tuple(map(math.floor, size))
def preserve_aspect_ratio() -> tuple[int, int] | None:
- def round_aspect(number, key):
+ def round_aspect(number: float, key: Callable[[int], float]) -> int:
return max(min(math.floor(number), math.ceil(number), key=key), 1)
x, y = provided_size
@@ -2742,36 +2834,27 @@ class Image:
)
return x, y
+ preserved_size = preserve_aspect_ratio()
+ if preserved_size is None:
+ return
+ final_size = preserved_size
+
box = None
if reducing_gap is not None:
- preserved_size = preserve_aspect_ratio()
- if preserved_size is None:
- return
- size = preserved_size
-
res = self.draft(
None, (int(size[0] * reducing_gap), int(size[1] * reducing_gap))
)
if res is not None:
box = res[1]
- if box is None:
- self.load()
- # load() may have changed the size of the image
- preserved_size = preserve_aspect_ratio()
- if preserved_size is None:
- return
- size = preserved_size
-
- if self.size != size:
- im = self.resize(size, resample, box=box, reducing_gap=reducing_gap)
+ if self.size != final_size:
+ im = self.resize(final_size, resample, box=box, reducing_gap=reducing_gap)
self.im = im.im
- self._size = size
+ self._size = final_size
self._mode = self.im.mode
self.readonly = 0
- self.pyaccess = None
# FIXME: the different transform methods need further explanation
# instead of bloating the method docs, add a separate chapter.
@@ -2870,8 +2953,14 @@ class Image:
return im
def __transformer(
- self, box, image, method, data, resample=Resampling.NEAREST, fill=1
- ):
+ self,
+ box: tuple[int, int, int, int],
+ image: Image,
+ method: Transform,
+ data: Sequence[float],
+ resample: int = Resampling.NEAREST,
+ fill: bool = True,
+ ) -> None:
w = box[2] - box[0]
h = box[3] - box[1]
@@ -2920,11 +3009,12 @@ class Image:
Resampling.BICUBIC,
):
if resample in (Resampling.BOX, Resampling.HAMMING, Resampling.LANCZOS):
- msg = {
+ unusable: dict[int, str] = {
Resampling.BOX: "Image.Resampling.BOX",
Resampling.HAMMING: "Image.Resampling.HAMMING",
Resampling.LANCZOS: "Image.Resampling.LANCZOS",
- }[resample] + f" ({resample}) cannot be used."
+ }
+ msg = unusable[resample] + f" ({resample}) cannot be used."
else:
msg = f"Unknown resampling filter ({resample})."
@@ -2971,7 +3061,7 @@ class Image:
self.load()
return self._new(self.im.effect_spread(distance))
- def toqimage(self):
+ def toqimage(self) -> ImageQt.ImageQt:
"""Returns a QImage copy of this image"""
from . import ImageQt
@@ -2980,7 +3070,7 @@ class Image:
raise ImportError(msg)
return ImageQt.toqimage(self)
- def toqpixmap(self):
+ def toqpixmap(self) -> ImageQt.QPixmap:
"""Returns a QPixmap copy of this image"""
from . import ImageQt
@@ -3028,35 +3118,35 @@ class ImageTransformHandler:
# Debugging
-def _wedge():
+def _wedge() -> Image:
"""Create grayscale wedge (for debugging only)"""
return Image()._new(core.wedge("L"))
-def _check_size(size):
+def _check_size(size: Any) -> None:
"""
Common check to enforce type and sanity check on size tuples
:param size: Should be a 2 tuple of (width, height)
- :returns: True, or raises a ValueError
+ :returns: None, or raises a ValueError
"""
if not isinstance(size, (list, tuple)):
- msg = "Size must be a tuple"
+ msg = "Size must be a list or tuple"
raise ValueError(msg)
if len(size) != 2:
- msg = "Size must be a tuple of length 2"
+ msg = "Size must be a sequence of length 2"
raise ValueError(msg)
if size[0] < 0 or size[1] < 0:
msg = "Width and height must be >= 0"
raise ValueError(msg)
- return True
-
def new(
- mode: str, size: tuple[int, int], color: float | tuple[float, ...] | str | None = 0
+ mode: str,
+ size: tuple[int, int] | list[int],
+ color: float | tuple[float, ...] | str | None = 0,
) -> Image:
"""
Creates a new image with the given mode and size.
@@ -3090,16 +3180,28 @@ def new(
color = ImageColor.getcolor(color, mode)
im = Image()
- if mode == "P" and isinstance(color, (list, tuple)) and len(color) in [3, 4]:
- # RGB or RGBA value for a P image
- from . import ImagePalette
+ if (
+ mode == "P"
+ and isinstance(color, (list, tuple))
+ and all(isinstance(i, int) for i in color)
+ ):
+ color_ints: tuple[int, ...] = cast(tuple[int, ...], tuple(color))
+ if len(color_ints) == 3 or len(color_ints) == 4:
+ # RGB or RGBA value for a P image
+ from . import ImagePalette
- im.palette = ImagePalette.ImagePalette()
- color = im.palette.getcolor(color)
+ im.palette = ImagePalette.ImagePalette()
+ color = im.palette.getcolor(color_ints)
return im._new(core.fill(mode, size, color))
-def frombytes(mode, size, data, decoder_name: str = "raw", *args) -> Image:
+def frombytes(
+ mode: str,
+ size: tuple[int, int],
+ data: bytes | bytearray | SupportsArrayInterface,
+ decoder_name: str = "raw",
+ *args: Any,
+) -> Image:
"""
Creates a copy of an image memory from pixel data in a buffer.
@@ -3127,18 +3229,25 @@ def frombytes(mode, size, data, decoder_name: str = "raw", *args) -> Image:
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]
+ decoder_args: Any = args
+ if len(decoder_args) == 1 and isinstance(decoder_args[0], tuple):
+ # may pass tuple instead of argument list
+ decoder_args = decoder_args[0]
- if decoder_name == "raw" and args == ():
- args = mode
+ if decoder_name == "raw" and decoder_args == ():
+ decoder_args = mode
- im.frombytes(data, decoder_name, args)
+ im.frombytes(data, decoder_name, decoder_args)
return im
-def frombuffer(mode: str, size, data, decoder_name: str = "raw", *args) -> Image:
+def frombuffer(
+ mode: str,
+ size: tuple[int, int],
+ data: bytes | SupportsArrayInterface,
+ decoder_name: str = "raw",
+ *args: Any,
+) -> Image:
"""
Creates an image memory referencing pixel data in a byte buffer.
@@ -3292,7 +3401,7 @@ def fromarray(obj: SupportsArrayInterface, mode: str | None = None) -> Image:
return frombuffer(mode, size, obj, "raw", rawmode, 0, 1)
-def fromqimage(im):
+def fromqimage(im: ImageQt.QImage) -> ImageFile.ImageFile:
"""Creates an image instance from a QImage image"""
from . import ImageQt
@@ -3302,7 +3411,7 @@ def fromqimage(im):
return ImageQt.fromqimage(im)
-def fromqpixmap(im):
+def fromqpixmap(im: ImageQt.QPixmap) -> ImageFile.ImageFile:
"""Creates an image instance from a QPixmap image"""
from . import ImageQt
@@ -3413,7 +3522,7 @@ def open(
exclusive_fp = False
filename: str | bytes = ""
if is_path(fp):
- filename = os.path.realpath(os.fspath(fp))
+ filename = os.fspath(fp)
if filename:
fp = builtins.open(filename, "rb")
@@ -3431,7 +3540,7 @@ def open(
preinit()
- accept_warnings: list[str] = []
+ warning_messages: list[str] = []
def _open_core(
fp: IO[bytes],
@@ -3447,17 +3556,15 @@ def open(
factory, accept = OPEN[i]
result = not accept or accept(prefix)
if isinstance(result, str):
- accept_warnings.append(result)
+ warning_messages.append(result)
elif result:
fp.seek(0)
im = factory(fp, filename)
_decompression_bomb_check(im.size)
return im
- except (SyntaxError, IndexError, TypeError, struct.error):
- # Leave disabled by default, spams the logs with image
- # opening failures that are entirely expected.
- # logger.debug("", exc_info=True)
- continue
+ except (SyntaxError, IndexError, TypeError, struct.error) as e:
+ if WARN_POSSIBLE_FORMATS:
+ warning_messages.append(i + " opening failed. " + str(e))
except BaseException:
if exclusive_fp:
fp.close()
@@ -3482,7 +3589,7 @@ def open(
if exclusive_fp:
fp.close()
- for message in accept_warnings:
+ for message in warning_messages:
warnings.warn(message)
msg = "cannot identify image file %r" % (filename if filename else fp)
raise UnidentifiedImageError(msg)
@@ -3547,7 +3654,7 @@ def composite(image1: Image, image2: Image, mask: Image) -> Image:
return image
-def eval(image, *args):
+def eval(image: Image, *args: Callable[[int], float]) -> Image:
"""
Applies the function (which should take one argument) to each pixel
in the given image. If the image has more than one band, the same
@@ -3595,8 +3702,11 @@ def merge(mode: str, bands: Sequence[Image]) -> Image:
def register_open(
- id,
- factory: Callable[[IO[bytes], str | bytes], ImageFile.ImageFile],
+ id: str,
+ factory: (
+ Callable[[IO[bytes], str | bytes], ImageFile.ImageFile]
+ | type[ImageFile.ImageFile]
+ ),
accept: Callable[[bytes], bool | str] | None = None,
) -> None:
"""
@@ -3629,7 +3739,9 @@ def register_mime(id: str, mimetype: str) -> None:
MIME[id.upper()] = mimetype
-def register_save(id: str, driver) -> None:
+def register_save(
+ id: str, driver: Callable[[Image, IO[bytes], str | bytes], None]
+) -> None:
"""
Registers an image save function. This function should not be
used in application code.
@@ -3640,7 +3752,9 @@ def register_save(id: str, driver) -> None:
SAVE[id.upper()] = driver
-def register_save_all(id: str, driver) -> None:
+def register_save_all(
+ id: str, driver: Callable[[Image, IO[bytes], str | bytes], None]
+) -> None:
"""
Registers an image function to save all the frames
of a multiframe format. This function should not be
@@ -3714,7 +3828,7 @@ def register_encoder(name: str, encoder: type[ImageFile.PyEncoder]) -> None:
# Simple display support.
-def _show(image, **options) -> None:
+def _show(image: Image, **options: Any) -> None:
from . import ImageShow
ImageShow.show(image, **options)
@@ -3724,7 +3838,9 @@ def _show(image, **options) -> None:
# Effects
-def effect_mandelbrot(size, extent, quality):
+def effect_mandelbrot(
+ size: tuple[int, int], extent: tuple[float, float, float, float], quality: int
+) -> Image:
"""
Generate a Mandelbrot set covering the given extent.
@@ -3770,19 +3886,18 @@ def radial_gradient(mode: str) -> Image:
# Resources
-def _apply_env_variables(env=None) -> None:
- if env is None:
- env = os.environ
+def _apply_env_variables(env: dict[str, str] | None = None) -> None:
+ env_dict = env if env is not None else os.environ
for var_name, setter in [
("PILLOW_ALIGNMENT", core.set_alignment),
("PILLOW_BLOCK_SIZE", core.set_block_size),
("PILLOW_BLOCKS_MAX", core.set_blocks_max),
]:
- if var_name not in env:
+ if var_name not in env_dict:
continue
- var = env[var_name].lower()
+ var = env_dict[var_name].lower()
units = 1
for postfix, mul in [("k", 1024), ("m", 1024 * 1024)]:
@@ -3791,13 +3906,13 @@ def _apply_env_variables(env=None) -> None:
var = var[: -len(postfix)]
try:
- var = int(var) * units
+ var_int = int(var) * units
except ValueError:
warnings.warn(f"{var_name} is not int")
continue
try:
- setter(var)
+ setter(var_int)
except ValueError as e:
warnings.warn(f"{var_name}: {e}")
@@ -3837,7 +3952,7 @@ class Exif(_ExifBase):
gps_ifd = exif.get_ifd(ExifTags.IFD.GPSInfo)
print(gps_ifd)
- Other IFDs include ``ExifTags.IFD.Exif``, ``ExifTags.IFD.Makernote``,
+ Other IFDs include ``ExifTags.IFD.Exif``, ``ExifTags.IFD.MakerNote``,
``ExifTags.IFD.Interop`` and ``ExifTags.IFD.IFD1``.
:py:mod:`~PIL.ExifTags` also has enum classes to provide names for data::
@@ -3846,18 +3961,18 @@ class Exif(_ExifBase):
print(gps_ifd[ExifTags.GPS.GPSDateStamp]) # 1999:99:99 99:99:99
"""
- endian = None
+ endian: str | None = None
bigtiff = False
_loaded = False
- def __init__(self):
- self._data = {}
- self._hidden_data = {}
- self._ifds = {}
- self._info = None
- self._loaded_exif = None
+ def __init__(self) -> None:
+ self._data: dict[int, Any] = {}
+ self._hidden_data: dict[int, Any] = {}
+ self._ifds: dict[int, dict[int, Any]] = {}
+ self._info: TiffImagePlugin.ImageFileDirectory_v2 | None = None
+ self._loaded_exif: bytes | None = None
- def _fixup(self, value):
+ def _fixup(self, value: Any) -> Any:
try:
if len(value) == 1 and isinstance(value, tuple):
return value[0]
@@ -3865,26 +3980,28 @@ class Exif(_ExifBase):
pass
return value
- def _fixup_dict(self, src_dict):
+ def _fixup_dict(self, src_dict: dict[int, Any]) -> dict[int, Any]:
# Helper function
# returns a dict with any single item tuples/lists as individual values
return {k: self._fixup(v) for k, v in src_dict.items()}
- def _get_ifd_dict(self, offset, group=None):
+ def _get_ifd_dict(
+ self, offset: int, group: int | None = None
+ ) -> dict[int, Any] | None:
try:
# an offset pointer to the location of the nested embedded IFD.
# It should be a long, but may be corrupted.
self.fp.seek(offset)
except (KeyError, TypeError):
- pass
+ return None
else:
from . import TiffImagePlugin
info = TiffImagePlugin.ImageFileDirectory_v2(self.head, group=group)
info.load(self.fp)
- return self._fixup_dict(info)
+ return self._fixup_dict(dict(info))
- def _get_head(self):
+ def _get_head(self) -> bytes:
version = b"\x2B" if self.bigtiff else b"\x2A"
if self.endian == "<":
head = b"II" + version + b"\x00" + o32le(8)
@@ -3895,7 +4012,7 @@ class Exif(_ExifBase):
head += b"\x00\x00\x00\x00"
return head
- def load(self, data):
+ def load(self, data: bytes) -> None:
# Extract EXIF information. This is highly experimental,
# and is likely to be replaced with something better in a future
# version.
@@ -3908,13 +4025,13 @@ class Exif(_ExifBase):
self._data.clear()
self._hidden_data.clear()
self._ifds.clear()
- if data and data.startswith(b"Exif\x00\x00"):
+ while data and data.startswith(b"Exif\x00\x00"):
data = data[6:]
if not data:
self._info = None
return
- self.fp = io.BytesIO(data)
+ self.fp: IO[bytes] = io.BytesIO(data)
self.head = self.fp.read(8)
# process dictionary
from . import TiffImagePlugin
@@ -3924,7 +4041,7 @@ class Exif(_ExifBase):
self.fp.seek(self._info.next)
self._info.load(self.fp)
- def load_from_fp(self, fp, offset=None):
+ def load_from_fp(self, fp: IO[bytes], offset: int | None = None) -> None:
self._loaded_exif = None
self._data.clear()
self._hidden_data.clear()
@@ -3947,7 +4064,7 @@ class Exif(_ExifBase):
self.fp.seek(offset)
self._info.load(self.fp)
- def _get_merged_dict(self):
+ def _get_merged_dict(self) -> dict[int, Any]:
merged_dict = dict(self)
# get EXIF extension
@@ -3985,20 +4102,24 @@ class Exif(_ExifBase):
ifd[tag] = value
return b"Exif\x00\x00" + head + ifd.tobytes(offset)
- def get_ifd(self, tag):
+ def get_ifd(self, tag: int) -> dict[int, Any]:
if tag not in self._ifds:
if tag == ExifTags.IFD.IFD1:
if self._info is not None and self._info.next != 0:
- self._ifds[tag] = self._get_ifd_dict(self._info.next)
+ ifd = self._get_ifd_dict(self._info.next)
+ if ifd is not None:
+ self._ifds[tag] = ifd
elif tag in [ExifTags.IFD.Exif, ExifTags.IFD.GPSInfo]:
offset = self._hidden_data.get(tag, self.get(tag))
if offset is not None:
- self._ifds[tag] = self._get_ifd_dict(offset, tag)
- elif tag in [ExifTags.IFD.Interop, ExifTags.IFD.Makernote]:
+ ifd = self._get_ifd_dict(offset, tag)
+ if ifd is not None:
+ self._ifds[tag] = ifd
+ elif tag in [ExifTags.IFD.Interop, ExifTags.IFD.MakerNote]:
if ExifTags.IFD.Exif not in self._ifds:
self.get_ifd(ExifTags.IFD.Exif)
tag_data = self._ifds[ExifTags.IFD.Exif][tag]
- if tag == ExifTags.IFD.Makernote:
+ if tag == ExifTags.IFD.MakerNote:
from .TiffImagePlugin import ImageFileDirectory_v2
if tag_data[:8] == b"FUJIFILM":
@@ -4050,7 +4171,9 @@ class Exif(_ExifBase):
(offset,) = struct.unpack(">L", data)
self.fp.seek(offset)
- camerainfo = {"ModelID": self.fp.read(4)}
+ camerainfo: dict[str, int | bytes] = {
+ "ModelID": self.fp.read(4)
+ }
self.fp.read(4)
# Seconds since 2000
@@ -4066,22 +4189,24 @@ class Exif(_ExifBase):
][1]
camerainfo["Parallax"] = handler(
ImageFileDirectory_v2(), parallax, False
- )
+ )[0]
self.fp.read(4)
camerainfo["Category"] = self.fp.read(2)
- makernote = {0x1101: dict(self._fixup_dict(camerainfo))}
+ makernote = {0x1101: camerainfo}
self._ifds[tag] = makernote
else:
# Interop
- self._ifds[tag] = self._get_ifd_dict(tag_data, tag)
- ifd = self._ifds.get(tag, {})
+ ifd = self._get_ifd_dict(tag_data, tag)
+ if ifd is not None:
+ self._ifds[tag] = ifd
+ ifd = self._ifds.setdefault(tag, {})
if tag == ExifTags.IFD.Exif and self._hidden_data:
ifd = {
k: v
for (k, v) in ifd.items()
- if k not in (ExifTags.IFD.Interop, ExifTags.IFD.Makernote)
+ if k not in (ExifTags.IFD.Interop, ExifTags.IFD.MakerNote)
}
return ifd
@@ -4105,16 +4230,16 @@ class Exif(_ExifBase):
keys.update(self._info)
return len(keys)
- def __getitem__(self, tag):
+ def __getitem__(self, tag: int) -> Any:
if self._info is not None and tag not in self._data and tag in self._info:
self._data[tag] = self._fixup(self._info[tag])
del self._info[tag]
return self._data[tag]
- def __contains__(self, tag) -> bool:
+ def __contains__(self, tag: object) -> bool:
return tag in self._data or (self._info is not None and tag in self._info)
- def __setitem__(self, tag, value) -> None:
+ def __setitem__(self, tag: int, value: Any) -> None:
if self._info is not None and tag in self._info:
del self._info[tag]
self._data[tag] = value
@@ -4125,7 +4250,7 @@ class Exif(_ExifBase):
else:
del self._data[tag]
- def __iter__(self):
+ def __iter__(self) -> Iterator[int]:
keys = set(self._data)
if self._info is not None:
keys.update(self._info)
diff --git a/src/PIL/ImageCms.py b/src/PIL/ImageCms.py
index 19a79facc..fdfbee789 100644
--- a/src/PIL/ImageCms.py
+++ b/src/PIL/ImageCms.py
@@ -31,6 +31,10 @@ from ._typing import SupportsRead
try:
from . import _imagingcms as core
+
+ _CmsProfileCompatible = Union[
+ str, SupportsRead[bytes], core.CmsProfile, "ImageCmsProfile"
+ ]
except ImportError as ex:
# Allow error import for doc purposes, but error out when accessing
# anything in core.
@@ -299,6 +303,31 @@ class ImageCmsTransform(Image.ImagePointHandler):
proof_intent: Intent = Intent.ABSOLUTE_COLORIMETRIC,
flags: Flags = Flags.NONE,
):
+ supported_modes = (
+ "RGB",
+ "RGBA",
+ "RGBX",
+ "CMYK",
+ "I;16",
+ "I;16L",
+ "I;16B",
+ "YCbCr",
+ "LAB",
+ "L",
+ "1",
+ )
+ for mode in (input_mode, output_mode):
+ if mode not in supported_modes:
+ deprecate(
+ mode,
+ 12,
+ {
+ "L;16": "I;16 or I;16L",
+ "L:16B": "I;16B",
+ "YCCA": "YCbCr",
+ "YCC": "YCbCr",
+ }.get(mode),
+ )
if proof is None:
self.transform = core.buildTransform(
input.profile, output.profile, input_mode, output_mode, intent, flags
@@ -324,19 +353,17 @@ class ImageCmsTransform(Image.ImagePointHandler):
return self.apply(im)
def apply(self, im: Image.Image, imOut: Image.Image | None = None) -> Image.Image:
- im.load()
if imOut is None:
imOut = Image.new(self.output_mode, im.size, None)
- self.transform.apply(im.im.id, imOut.im.id)
+ self.transform.apply(im.getim(), imOut.getim())
imOut.info["icc_profile"] = self.output_profile.tobytes()
return imOut
def apply_in_place(self, im: Image.Image) -> Image.Image:
- im.load()
if im.mode != self.output_mode:
msg = "mode mismatch"
raise ValueError(msg) # wrong output mode
- self.transform.apply(im.im.id, im.im.id)
+ self.transform.apply(im.getim(), im.getim())
im.info["icc_profile"] = self.output_profile.tobytes()
return im
@@ -366,10 +393,6 @@ def get_display_profile(handle: SupportsInt | None = None) -> ImageCmsProfile |
# pyCMS compatible layer
# --------------------------------------------------------------------.
-_CmsProfileCompatible = Union[
- str, SupportsRead[bytes], core.CmsProfile, ImageCmsProfile
-]
-
class PyCMSError(Exception):
"""(pyCMS) Exception class.
diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py
index 73ed3d4a9..d8e4c0c60 100644
--- a/src/PIL/ImageDraw.py
+++ b/src/PIL/ImageDraw.py
@@ -32,13 +32,27 @@
from __future__ import annotations
import math
-import numbers
import struct
-from typing import TYPE_CHECKING, AnyStr, Sequence, cast
+from collections.abc import Sequence
+from types import ModuleType
+from typing import TYPE_CHECKING, Any, AnyStr, Callable, Union, cast
from . import Image, ImageColor
+from ._deprecate import deprecate
from ._typing import Coords
+# experimental access to the outline API
+Outline: Callable[[], Image.core._Outline] | None
+try:
+ Outline = Image.core.outline
+except AttributeError:
+ Outline = None
+
+if TYPE_CHECKING:
+ from . import ImageDraw2, ImageFont
+
+_Ink = Union[float, tuple[int, ...], str]
+
"""
A simple 2D drawing interface for PIL images.
@@ -48,7 +62,9 @@ directly.
class ImageDraw:
- font = None
+ font: (
+ ImageFont.ImageFont | ImageFont.FreeTypeFont | ImageFont.TransposedFont | None
+ ) = None
def __init__(self, im: Image.Image, mode: str | None = None) -> None:
"""
@@ -92,9 +108,6 @@ class ImageDraw:
self.fontmode = "L" # aliasing is okay for other modes
self.fill = False
- if TYPE_CHECKING:
- from . import ImageFont
-
def getfont(
self,
) -> ImageFont.ImageFont | ImageFont.FreeTypeFont | ImageFont.TransposedFont:
@@ -132,34 +145,47 @@ class ImageDraw:
else:
return self.getfont()
- def _getink(self, ink, fill=None) -> tuple[int | None, int | None]:
+ def _getink(
+ self, ink: _Ink | None, fill: _Ink | None = None
+ ) -> tuple[int | None, int | None]:
+ result_ink = None
+ result_fill = None
if ink is None and fill is None:
if self.fill:
- fill = self.ink
+ result_fill = self.ink
else:
- ink = self.ink
+ result_ink = self.ink
else:
if ink is not None:
if isinstance(ink, str):
ink = ImageColor.getcolor(ink, self.mode)
- if self.palette and not isinstance(ink, numbers.Number):
+ if self.palette and isinstance(ink, tuple):
ink = self.palette.getcolor(ink, self._image)
- ink = self.draw.draw_ink(ink)
+ result_ink = self.draw.draw_ink(ink)
if fill is not None:
if isinstance(fill, str):
fill = ImageColor.getcolor(fill, self.mode)
- if self.palette and not isinstance(fill, numbers.Number):
+ if self.palette and isinstance(fill, tuple):
fill = self.palette.getcolor(fill, self._image)
- fill = self.draw.draw_ink(fill)
- return ink, fill
+ result_fill = self.draw.draw_ink(fill)
+ return result_ink, result_fill
- def arc(self, xy: Coords, start, end, fill=None, width=1) -> None:
+ def arc(
+ self,
+ xy: Coords,
+ start: float,
+ end: float,
+ fill: _Ink | None = None,
+ width: int = 1,
+ ) -> None:
"""Draw an arc."""
ink, fill = self._getink(fill)
if ink is not None:
self.draw.draw_arc(xy, start, end, ink, width)
- def bitmap(self, xy: Sequence[int], bitmap, fill=None) -> None:
+ def bitmap(
+ self, xy: Sequence[int], bitmap: Image.Image, fill: _Ink | None = None
+ ) -> None:
"""Draw a bitmap."""
bitmap.load()
ink, fill = self._getink(fill)
@@ -168,30 +194,55 @@ class ImageDraw:
if ink is not None:
self.draw.draw_bitmap(xy, bitmap.im, ink)
- def chord(self, xy: Coords, start, end, fill=None, outline=None, width=1) -> None:
+ def chord(
+ self,
+ xy: Coords,
+ start: float,
+ end: float,
+ fill: _Ink | None = None,
+ outline: _Ink | None = None,
+ width: int = 1,
+ ) -> None:
"""Draw a chord."""
- ink, fill = self._getink(outline, fill)
- if fill is not None:
- self.draw.draw_chord(xy, start, end, fill, 1)
- if ink is not None and ink != fill and width != 0:
+ ink, fill_ink = self._getink(outline, fill)
+ if fill_ink is not None:
+ self.draw.draw_chord(xy, start, end, fill_ink, 1)
+ if ink is not None and ink != fill_ink and width != 0:
self.draw.draw_chord(xy, start, end, ink, 0, width)
- def ellipse(self, xy: Coords, fill=None, outline=None, width=1) -> None:
+ def ellipse(
+ self,
+ xy: Coords,
+ fill: _Ink | None = None,
+ outline: _Ink | None = None,
+ width: int = 1,
+ ) -> None:
"""Draw an ellipse."""
- ink, fill = self._getink(outline, fill)
- if fill is not None:
- self.draw.draw_ellipse(xy, fill, 1)
- if ink is not None and ink != fill and width != 0:
+ ink, fill_ink = self._getink(outline, fill)
+ if fill_ink is not None:
+ self.draw.draw_ellipse(xy, fill_ink, 1)
+ if ink is not None and ink != fill_ink and width != 0:
self.draw.draw_ellipse(xy, ink, 0, width)
def circle(
- self, xy: Sequence[float], radius: float, fill=None, outline=None, width=1
+ self,
+ xy: Sequence[float],
+ radius: float,
+ fill: _Ink | None = None,
+ outline: _Ink | None = None,
+ width: int = 1,
) -> None:
"""Draw a circle given center coordinates and a radius."""
ellipse_xy = (xy[0] - radius, xy[1] - radius, xy[0] + radius, xy[1] + radius)
self.ellipse(ellipse_xy, fill, outline, width)
- def line(self, xy: Coords, fill=None, width=0, joint=None) -> None:
+ def line(
+ self,
+ xy: Coords,
+ fill: _Ink | None = None,
+ width: int = 0,
+ joint: str | None = None,
+ ) -> None:
"""Draw a line, or a connected sequence of line segments."""
ink = self._getink(fill)[0]
if ink is not None:
@@ -219,7 +270,9 @@ class ImageDraw:
# This is a straight line, so no joint is required
continue
- def coord_at_angle(coord, angle):
+ def coord_at_angle(
+ coord: Sequence[float], angle: float
+ ) -> tuple[float, ...]:
x, y = coord
angle -= 90
distance = width / 2 - 1
@@ -260,37 +313,54 @@ class ImageDraw:
]
self.line(gap_coords, fill, width=3)
- def shape(self, shape, fill=None, outline=None) -> None:
+ def shape(
+ self,
+ shape: Image.core._Outline,
+ fill: _Ink | None = None,
+ outline: _Ink | None = None,
+ ) -> None:
"""(Experimental) Draw a shape."""
shape.close()
- ink, fill = self._getink(outline, fill)
- if fill is not None:
- self.draw.draw_outline(shape, fill, 1)
- if ink is not None and ink != fill:
+ ink, fill_ink = self._getink(outline, fill)
+ if fill_ink is not None:
+ self.draw.draw_outline(shape, fill_ink, 1)
+ if ink is not None and ink != fill_ink:
self.draw.draw_outline(shape, ink, 0)
def pieslice(
- self, xy: Coords, start, end, fill=None, outline=None, width=1
+ self,
+ xy: Coords,
+ start: float,
+ end: float,
+ fill: _Ink | None = None,
+ outline: _Ink | None = None,
+ width: int = 1,
) -> None:
"""Draw a pieslice."""
- ink, fill = self._getink(outline, fill)
- if fill is not None:
- self.draw.draw_pieslice(xy, start, end, fill, 1)
- if ink is not None and ink != fill and width != 0:
+ ink, fill_ink = self._getink(outline, fill)
+ if fill_ink is not None:
+ self.draw.draw_pieslice(xy, start, end, fill_ink, 1)
+ if ink is not None and ink != fill_ink and width != 0:
self.draw.draw_pieslice(xy, start, end, ink, 0, width)
- def point(self, xy: Coords, fill=None) -> None:
+ def point(self, xy: Coords, fill: _Ink | None = None) -> None:
"""Draw one or more individual pixels."""
ink, fill = self._getink(fill)
if ink is not None:
self.draw.draw_points(xy, ink)
- def polygon(self, xy: Coords, fill=None, outline=None, width=1) -> None:
+ def polygon(
+ self,
+ xy: Coords,
+ fill: _Ink | None = None,
+ outline: _Ink | None = None,
+ width: int = 1,
+ ) -> None:
"""Draw a polygon."""
- ink, fill = self._getink(outline, fill)
- if fill is not None:
- self.draw.draw_polygon(xy, fill, 1)
- if ink is not None and ink != fill and width != 0:
+ ink, fill_ink = self._getink(outline, fill)
+ if fill_ink is not None:
+ self.draw.draw_polygon(xy, fill_ink, 1)
+ if ink is not None and ink != fill_ink and width != 0:
if width == 1:
self.draw.draw_polygon(xy, ink, 0, width)
elif self.im is not None:
@@ -316,22 +386,41 @@ class ImageDraw:
self.im.paste(im.im, (0, 0) + im.size, mask.im)
def regular_polygon(
- self, bounding_circle, n_sides, rotation=0, fill=None, outline=None, width=1
+ self,
+ bounding_circle: Sequence[Sequence[float] | float],
+ n_sides: int,
+ rotation: float = 0,
+ fill: _Ink | None = None,
+ outline: _Ink | None = None,
+ width: int = 1,
) -> None:
"""Draw a regular polygon."""
xy = _compute_regular_polygon_vertices(bounding_circle, n_sides, rotation)
self.polygon(xy, fill, outline, width)
- def rectangle(self, xy: Coords, fill=None, outline=None, width=1) -> None:
+ def rectangle(
+ self,
+ xy: Coords,
+ fill: _Ink | None = None,
+ outline: _Ink | None = None,
+ width: int = 1,
+ ) -> None:
"""Draw a rectangle."""
- ink, fill = self._getink(outline, fill)
- if fill is not None:
- self.draw.draw_rectangle(xy, fill, 1)
- if ink is not None and ink != fill and width != 0:
+ ink, fill_ink = self._getink(outline, fill)
+ if fill_ink is not None:
+ self.draw.draw_rectangle(xy, fill_ink, 1)
+ if ink is not None and ink != fill_ink and width != 0:
self.draw.draw_rectangle(xy, ink, 0, width)
def rounded_rectangle(
- self, xy: Coords, radius=0, fill=None, outline=None, width=1, *, corners=None
+ self,
+ xy: Coords,
+ radius: float = 0,
+ fill: _Ink | None = None,
+ outline: _Ink | None = None,
+ width: int = 1,
+ *,
+ corners: tuple[bool, bool, bool, bool] | None = None,
) -> None:
"""Draw a rounded rectangle."""
if isinstance(xy[0], (list, tuple)):
@@ -373,10 +462,10 @@ class ImageDraw:
# that is a rectangle
return self.rectangle(xy, fill, outline, width)
- r = d // 2
- ink, fill = self._getink(outline, fill)
+ r = int(d // 2)
+ ink, fill_ink = self._getink(outline, fill)
- def draw_corners(pieslice) -> None:
+ def draw_corners(pieslice: bool) -> None:
parts: tuple[tuple[tuple[float, float, float, float], int, int], ...]
if full_x:
# Draw top and bottom halves
@@ -406,32 +495,32 @@ class ImageDraw:
)
for part in parts:
if pieslice:
- self.draw.draw_pieslice(*(part + (fill, 1)))
+ self.draw.draw_pieslice(*(part + (fill_ink, 1)))
else:
self.draw.draw_arc(*(part + (ink, width)))
- if fill is not None:
+ if fill_ink is not None:
draw_corners(True)
if full_x:
- self.draw.draw_rectangle((x0, y0 + r + 1, x1, y1 - r - 1), fill, 1)
- else:
- self.draw.draw_rectangle((x0 + r + 1, y0, x1 - r - 1, y1), fill, 1)
+ self.draw.draw_rectangle((x0, y0 + r + 1, x1, y1 - r - 1), fill_ink, 1)
+ elif x1 - r - 1 > x0 + r + 1:
+ self.draw.draw_rectangle((x0 + r + 1, y0, x1 - r - 1, y1), fill_ink, 1)
if not full_x and not full_y:
left = [x0, y0, x0 + r, y1]
if corners[0]:
left[1] += r + 1
if corners[3]:
left[3] -= r + 1
- self.draw.draw_rectangle(left, fill, 1)
+ self.draw.draw_rectangle(left, fill_ink, 1)
right = [x1 - r, y0, x1, y1]
if corners[1]:
right[1] += r + 1
if corners[2]:
right[3] -= r + 1
- self.draw.draw_rectangle(right, fill, 1)
- if ink is not None and ink != fill and width != 0:
+ self.draw.draw_rectangle(right, fill_ink, 1)
+ if ink is not None and ink != fill_ink and width != 0:
draw_corners(False)
if not full_x:
@@ -471,7 +560,12 @@ class ImageDraw:
def _multiline_split(self, text: AnyStr) -> list[AnyStr]:
return text.split("\n" if isinstance(text, str) else b"\n")
- def _multiline_spacing(self, font, spacing, stroke_width):
+ def _multiline_spacing(
+ self,
+ font: ImageFont.ImageFont | ImageFont.FreeTypeFont | ImageFont.TransposedFont,
+ spacing: float,
+ stroke_width: float,
+ ) -> float:
return (
self.textbbox((0, 0), "A", font, stroke_width=stroke_width)[3]
+ stroke_width
@@ -481,25 +575,25 @@ class ImageDraw:
def text(
self,
xy: tuple[float, float],
- text: str,
- fill=None,
+ text: AnyStr,
+ fill: _Ink | None = None,
font: (
ImageFont.ImageFont
| ImageFont.FreeTypeFont
| ImageFont.TransposedFont
| None
) = None,
- anchor=None,
- spacing=4,
- align="left",
- direction=None,
- features=None,
- language=None,
- stroke_width=0,
- stroke_fill=None,
- embedded_color=False,
- *args,
- **kwargs,
+ anchor: str | None = None,
+ spacing: float = 4,
+ align: str = "left",
+ direction: str | None = None,
+ features: list[str] | None = None,
+ language: str | None = None,
+ stroke_width: float = 0,
+ stroke_fill: _Ink | None = None,
+ embedded_color: bool = False,
+ *args: Any,
+ **kwargs: Any,
) -> None:
"""Draw text."""
if embedded_color and self.mode not in ("RGB", "RGBA"):
@@ -526,21 +620,21 @@ class ImageDraw:
embedded_color,
)
- def getink(fill):
- ink, fill = self._getink(fill)
+ def getink(fill: _Ink | None) -> int:
+ ink, fill_ink = self._getink(fill)
if ink is None:
- return fill
+ assert fill_ink is not None
+ return fill_ink
return ink
- def draw_text(ink, stroke_width=0, stroke_offset=None) -> None:
+ def draw_text(ink: int, stroke_width: float = 0) -> None:
mode = self.fontmode
if stroke_width == 0 and embedded_color:
mode = "RGBA"
coord = []
- start = []
for i in range(2):
coord.append(int(xy[i]))
- start.append(math.modf(xy[i])[0])
+ start = (math.modf(xy[0])[0], math.modf(xy[1])[0])
try:
mask, offset = font.getmask2( # type: ignore[union-attr,misc]
text,
@@ -573,8 +667,6 @@ class ImageDraw:
)
except TypeError:
mask = font.getmask(text)
- if stroke_offset:
- coord = [coord[0] + stroke_offset[0], coord[1] + stroke_offset[1]]
if mode == "RGBA":
# font.getmask2(mode="RGBA") returns color in RGB bands and mask in A
# extract mask and set text alpha
@@ -608,25 +700,25 @@ class ImageDraw:
def multiline_text(
self,
xy: tuple[float, float],
- text: str,
- fill=None,
+ text: AnyStr,
+ fill: _Ink | None = None,
font: (
ImageFont.ImageFont
| ImageFont.FreeTypeFont
| ImageFont.TransposedFont
| None
) = None,
- anchor=None,
- spacing=4,
- align="left",
- direction=None,
- features=None,
- language=None,
- stroke_width=0,
- stroke_fill=None,
- embedded_color=False,
+ anchor: str | None = None,
+ spacing: float = 4,
+ align: str = "left",
+ direction: str | None = None,
+ features: list[str] | None = None,
+ language: str | None = None,
+ stroke_width: float = 0,
+ stroke_fill: _Ink | None = None,
+ embedded_color: bool = False,
*,
- font_size=None,
+ font_size: float | None = None,
) -> None:
if direction == "ttb":
msg = "ttb direction is unsupported for multiline text"
@@ -699,19 +791,19 @@ class ImageDraw:
def textlength(
self,
- text: str,
+ text: AnyStr,
font: (
ImageFont.ImageFont
| ImageFont.FreeTypeFont
| ImageFont.TransposedFont
| None
) = None,
- direction=None,
- features=None,
- language=None,
- embedded_color=False,
+ direction: str | None = None,
+ features: list[str] | None = None,
+ language: str | None = None,
+ embedded_color: bool = False,
*,
- font_size=None,
+ font_size: float | None = None,
) -> float:
"""Get the length of a given string, in pixels with 1/64 precision."""
if self._multiline_check(text):
@@ -728,20 +820,25 @@ class ImageDraw:
def textbbox(
self,
- xy,
- text,
- font=None,
- anchor=None,
- spacing=4,
- align="left",
- direction=None,
- features=None,
- language=None,
- stroke_width=0,
- embedded_color=False,
+ xy: tuple[float, float],
+ text: AnyStr,
+ font: (
+ ImageFont.ImageFont
+ | ImageFont.FreeTypeFont
+ | ImageFont.TransposedFont
+ | None
+ ) = None,
+ anchor: str | None = None,
+ spacing: float = 4,
+ align: str = "left",
+ direction: str | None = None,
+ features: list[str] | None = None,
+ language: str | None = None,
+ stroke_width: float = 0,
+ embedded_color: bool = False,
*,
- font_size=None,
- ) -> tuple[int, int, int, int]:
+ font_size: float | None = None,
+ ) -> tuple[float, float, float, float]:
"""Get the bounding box of a given string, in pixels."""
if embedded_color and self.mode not in ("RGB", "RGBA"):
msg = "Embedded color supported only in RGB and RGBA modes"
@@ -773,20 +870,25 @@ class ImageDraw:
def multiline_textbbox(
self,
- xy,
- text,
- font=None,
- anchor=None,
- spacing=4,
- align="left",
- direction=None,
- features=None,
- language=None,
- stroke_width=0,
- embedded_color=False,
+ xy: tuple[float, float],
+ text: AnyStr,
+ font: (
+ ImageFont.ImageFont
+ | ImageFont.FreeTypeFont
+ | ImageFont.TransposedFont
+ | None
+ ) = None,
+ anchor: str | None = None,
+ spacing: float = 4,
+ align: str = "left",
+ direction: str | None = None,
+ features: list[str] | None = None,
+ language: str | None = None,
+ stroke_width: float = 0,
+ embedded_color: bool = False,
*,
- font_size=None,
- ) -> tuple[int, int, int, int]:
+ font_size: float | None = None,
+ ) -> tuple[float, float, float, float]:
if direction == "ttb":
msg = "ttb direction is unsupported for multiline text"
raise ValueError(msg)
@@ -825,7 +927,7 @@ class ImageDraw:
elif anchor[1] == "d":
top -= (len(lines) - 1) * line_spacing
- bbox: tuple[int, int, int, int] | None = None
+ bbox: tuple[float, float, float, float] | None = None
for idx, line in enumerate(lines):
left = xy[0]
@@ -876,7 +978,7 @@ class ImageDraw:
return bbox
-def Draw(im, mode: str | None = None) -> ImageDraw:
+def Draw(im: Image.Image, mode: str | None = None) -> ImageDraw:
"""
A simple 2D drawing interface for PIL images.
@@ -888,40 +990,25 @@ def Draw(im, mode: str | None = None) -> ImageDraw:
defaults to the mode of the image.
"""
try:
- return im.getdraw(mode)
+ return getattr(im, "getdraw")(mode)
except AttributeError:
return ImageDraw(im, mode)
-# experimental access to the outline API
-try:
- Outline = Image.core.outline
-except AttributeError:
- Outline = None
-
-
-def getdraw(im=None, hints=None):
+def getdraw(
+ im: Image.Image | None = None, hints: list[str] | None = None
+) -> tuple[ImageDraw2.Draw | None, ModuleType]:
"""
- (Experimental) A more advanced 2D drawing interface for PIL images,
- based on the WCK interface.
-
:param im: The image to draw in.
- :param hints: An optional list of hints.
+ :param hints: An optional list of hints. Deprecated.
:returns: A (drawing context, drawing resource factory) tuple.
"""
- # FIXME: this needs more work!
- # FIXME: come up with a better 'hints' scheme.
- handler = None
- if not hints or "nicest" in hints:
- try:
- from . import _imagingagg as handler
- except ImportError:
- pass
- if handler is None:
- from . import ImageDraw2 as handler
- if im:
- im = handler.Draw(im)
- return im, handler
+ if hints is not None:
+ deprecate("'hints' parameter", 12)
+ from . import ImageDraw2
+
+ draw = ImageDraw2.Draw(im) if im is not None else None
+ return draw, ImageDraw2
def floodfill(
@@ -932,7 +1019,9 @@ def floodfill(
thresh: float = 0,
) -> None:
"""
- (experimental) Fills a bounded region with a given color.
+ .. warning:: This method is experimental.
+
+ Fills a bounded region with a given color.
:param image: Target image.
:param xy: Seed position (a 2-item coordinate tuple). See
@@ -950,6 +1039,7 @@ def floodfill(
# based on an implementation by Eric S. Raymond
# amended by yo1995 @20180806
pixel = image.load()
+ assert pixel is not None
x, y = xy
try:
background = pixel[x, y]
@@ -987,12 +1077,12 @@ def floodfill(
def _compute_regular_polygon_vertices(
- bounding_circle, n_sides, rotation
+ bounding_circle: Sequence[Sequence[float] | float], n_sides: int, rotation: float
) -> list[tuple[float, float]]:
"""
Generate a list of vertices for a 2D regular polygon.
- :param bounding_circle: The bounding circle is a tuple defined
+ :param bounding_circle: The bounding circle is a sequence defined
by a point and radius. The polygon is inscribed in this circle.
(e.g. ``bounding_circle=(x, y, r)`` or ``((x, y), r)``)
:param n_sides: Number of sides
@@ -1030,7 +1120,7 @@ def _compute_regular_polygon_vertices(
# 1. Error Handling
# 1.1 Check `n_sides` has an appropriate value
if not isinstance(n_sides, int):
- msg = "n_sides should be an int"
+ msg = "n_sides should be an int" # type: ignore[unreachable]
raise TypeError(msg)
if n_sides < 3:
msg = "n_sides should be an int > 2"
@@ -1042,9 +1132,24 @@ def _compute_regular_polygon_vertices(
raise TypeError(msg)
if len(bounding_circle) == 3:
- *centroid, polygon_radius = bounding_circle
- elif len(bounding_circle) == 2:
- centroid, polygon_radius = bounding_circle
+ if not all(isinstance(i, (int, float)) for i in bounding_circle):
+ msg = "bounding_circle should only contain numeric data"
+ raise ValueError(msg)
+
+ *centroid, polygon_radius = cast(list[float], list(bounding_circle))
+ elif len(bounding_circle) == 2 and isinstance(bounding_circle[0], (list, tuple)):
+ if not all(
+ isinstance(i, (int, float)) for i in bounding_circle[0]
+ ) or not isinstance(bounding_circle[1], (int, float)):
+ msg = "bounding_circle should only contain numeric data"
+ raise ValueError(msg)
+
+ if len(bounding_circle[0]) != 2:
+ msg = "bounding_circle centre should contain 2D coordinates (e.g. (x, y))"
+ raise ValueError(msg)
+
+ centroid = cast(list[float], list(bounding_circle[0]))
+ polygon_radius = cast(float, bounding_circle[1])
else:
msg = (
"bounding_circle should contain 2D coordinates "
@@ -1052,25 +1157,17 @@ def _compute_regular_polygon_vertices(
)
raise ValueError(msg)
- if not all(isinstance(i, (int, float)) for i in (*centroid, polygon_radius)):
- msg = "bounding_circle should only contain numeric data"
- raise ValueError(msg)
-
- if not len(centroid) == 2:
- msg = "bounding_circle centre should contain 2D coordinates (e.g. (x, y))"
- raise ValueError(msg)
-
if polygon_radius <= 0:
msg = "bounding_circle radius should be > 0"
raise ValueError(msg)
# 1.3 Check `rotation` has an appropriate value
if not isinstance(rotation, (int, float)):
- msg = "rotation should be an int or float"
+ msg = "rotation should be an int or float" # type: ignore[unreachable]
raise ValueError(msg)
# 2. Define Helper Functions
- def _apply_rotation(point: list[float], degrees: float) -> tuple[int, int]:
+ def _apply_rotation(point: list[float], degrees: float) -> tuple[float, float]:
return (
round(
point[0] * math.cos(math.radians(360 - degrees))
@@ -1086,7 +1183,7 @@ def _compute_regular_polygon_vertices(
),
)
- def _compute_polygon_vertex(angle: float) -> tuple[int, int]:
+ def _compute_polygon_vertex(angle: float) -> tuple[float, float]:
start_point = [polygon_radius, 0]
return _apply_rotation(start_point, angle)
@@ -1109,11 +1206,13 @@ def _compute_regular_polygon_vertices(
return [_compute_polygon_vertex(angle) for angle in angles]
-def _color_diff(color1, color2: float | tuple[int, ...]) -> float:
+def _color_diff(
+ color1: float | tuple[int, ...], color2: float | tuple[int, ...]
+) -> float:
"""
Uses 1-norm distance to calculate difference between two values.
"""
- if isinstance(color2, tuple):
- return sum(abs(color1[i] - color2[i]) for i in range(0, len(color2)))
- else:
- return abs(color1 - color2)
+ first = color1 if isinstance(color1, tuple) else (color1,)
+ second = color2 if isinstance(color2, tuple) else (color2,)
+
+ return sum(abs(first[i] - second[i]) for i in range(0, len(second)))
diff --git a/src/PIL/ImageDraw2.py b/src/PIL/ImageDraw2.py
index b42f5d9ea..3d68658ed 100644
--- a/src/PIL/ImageDraw2.py
+++ b/src/PIL/ImageDraw2.py
@@ -24,7 +24,10 @@
"""
from __future__ import annotations
+from typing import Any, AnyStr, BinaryIO
+
from . import Image, ImageColor, ImageDraw, ImageFont, ImagePath
+from ._typing import Coords, StrOrBytesPath
class Pen:
@@ -45,7 +48,9 @@ class Brush:
class Font:
"""Stores a TrueType font and color"""
- def __init__(self, color, file, size=12):
+ def __init__(
+ self, color: str, file: StrOrBytesPath | BinaryIO, size: float = 12
+ ) -> None:
# FIXME: add support for bitmap fonts
self.color = ImageColor.getrgb(color)
self.font = ImageFont.truetype(file, size)
@@ -56,17 +61,32 @@ class Draw:
(Experimental) WCK-style drawing interface
"""
- def __init__(self, image, size=None, color=None):
- if not hasattr(image, "im"):
+ def __init__(
+ self,
+ image: Image.Image | str,
+ size: tuple[int, int] | list[int] | None = None,
+ color: float | tuple[float, ...] | str | None = None,
+ ) -> None:
+ if isinstance(image, str):
+ if size is None:
+ msg = "If image argument is mode string, size must be a list or tuple"
+ raise ValueError(msg)
image = Image.new(image, size, color)
self.draw = ImageDraw.Draw(image)
self.image = image
- self.transform = None
+ self.transform: tuple[float, float, float, float, float, float] | None = None
def flush(self) -> Image.Image:
return self.image
- def render(self, op, xy, pen, brush=None):
+ def render(
+ self,
+ op: str,
+ xy: Coords,
+ pen: Pen | Brush | None,
+ brush: Brush | Pen | None = None,
+ **kwargs: Any,
+ ) -> None:
# handle color arguments
outline = fill = None
width = 1
@@ -82,63 +102,89 @@ class Draw:
fill = pen.color
# handle transformation
if self.transform:
- xy = ImagePath.Path(xy)
- xy.transform(self.transform)
+ path = ImagePath.Path(xy)
+ path.transform(self.transform)
+ xy = path
# render the item
- if op == "line":
- self.draw.line(xy, fill=outline, width=width)
+ if op in ("arc", "line"):
+ kwargs.setdefault("fill", outline)
else:
- getattr(self.draw, op)(xy, fill=fill, outline=outline)
+ kwargs.setdefault("fill", fill)
+ kwargs.setdefault("outline", outline)
+ if op == "line":
+ kwargs.setdefault("width", width)
+ getattr(self.draw, op)(xy, **kwargs)
- def settransform(self, offset):
+ def settransform(self, offset: tuple[float, float]) -> None:
"""Sets a transformation offset."""
(xoffset, yoffset) = offset
self.transform = (1, 0, xoffset, 0, 1, yoffset)
- def arc(self, xy, start, end, *options):
+ def arc(
+ self,
+ xy: Coords,
+ pen: Pen | Brush | None,
+ start: float,
+ end: float,
+ *options: Any,
+ ) -> None:
"""
Draws an arc (a portion of a circle outline) between the start and end
angles, inside the given bounding box.
.. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.arc`
"""
- self.render("arc", xy, start, end, *options)
+ self.render("arc", xy, pen, *options, start=start, end=end)
- def chord(self, xy, start, end, *options):
+ def chord(
+ self,
+ xy: Coords,
+ pen: Pen | Brush | None,
+ start: float,
+ end: float,
+ *options: Any,
+ ) -> None:
"""
Same as :py:meth:`~PIL.ImageDraw2.Draw.arc`, but connects the end points
with a straight line.
.. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.chord`
"""
- self.render("chord", xy, start, end, *options)
+ self.render("chord", xy, pen, *options, start=start, end=end)
- def ellipse(self, xy, *options):
+ def ellipse(self, xy: Coords, pen: Pen | Brush | None, *options: Any) -> None:
"""
Draws an ellipse inside the given bounding box.
.. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.ellipse`
"""
- self.render("ellipse", xy, *options)
+ self.render("ellipse", xy, pen, *options)
- def line(self, xy, *options):
+ def line(self, xy: Coords, pen: Pen | Brush | None, *options: Any) -> None:
"""
Draws a line between the coordinates in the ``xy`` list.
.. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.line`
"""
- self.render("line", xy, *options)
+ self.render("line", xy, pen, *options)
- def pieslice(self, xy, start, end, *options):
+ def pieslice(
+ self,
+ xy: Coords,
+ pen: Pen | Brush | None,
+ start: float,
+ end: float,
+ *options: Any,
+ ) -> None:
"""
Same as arc, but also draws straight lines between the end points and the
center of the bounding box.
.. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.pieslice`
"""
- self.render("pieslice", xy, start, end, *options)
+ self.render("pieslice", xy, pen, *options, start=start, end=end)
- def polygon(self, xy, *options):
+ def polygon(self, xy: Coords, pen: Pen | Brush | None, *options: Any) -> None:
"""
Draws a polygon.
@@ -149,28 +195,31 @@ class Draw:
.. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.polygon`
"""
- self.render("polygon", xy, *options)
+ self.render("polygon", xy, pen, *options)
- def rectangle(self, xy, *options):
+ def rectangle(self, xy: Coords, pen: Pen | Brush | None, *options: Any) -> None:
"""
Draws a rectangle.
.. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.rectangle`
"""
- self.render("rectangle", xy, *options)
+ self.render("rectangle", xy, pen, *options)
- def text(self, xy, text, font):
+ def text(self, xy: tuple[float, float], text: AnyStr, font: Font) -> None:
"""
Draws the string at the given position.
.. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.text`
"""
if self.transform:
- xy = ImagePath.Path(xy)
- xy.transform(self.transform)
+ path = ImagePath.Path(xy)
+ path.transform(self.transform)
+ xy = path
self.draw.text(xy, text, font=font.font, fill=font.color)
- def textbbox(self, xy, text, font):
+ def textbbox(
+ self, xy: tuple[float, float], text: AnyStr, font: Font
+ ) -> tuple[float, float, float, float]:
"""
Returns bounding box (in pixels) of given text.
@@ -179,11 +228,12 @@ class Draw:
.. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.textbbox`
"""
if self.transform:
- xy = ImagePath.Path(xy)
- xy.transform(self.transform)
+ path = ImagePath.Path(xy)
+ path.transform(self.transform)
+ xy = path
return self.draw.textbbox(xy, text, font=font.font)
- def textlength(self, text, font):
+ def textlength(self, text: AnyStr, font: Font) -> float:
"""
Returns length (in pixels) of given text.
This is the amount by which following text should be offset.
diff --git a/src/PIL/ImageEnhance.py b/src/PIL/ImageEnhance.py
index d7e99a968..0e7e6dd8a 100644
--- a/src/PIL/ImageEnhance.py
+++ b/src/PIL/ImageEnhance.py
@@ -55,7 +55,9 @@ class Color(_Enhance):
if "A" in image.getbands():
self.intermediate_mode = "LA"
- self.degenerate = image.convert(self.intermediate_mode).convert(image.mode)
+ if self.intermediate_mode != image.mode:
+ image = image.convert(self.intermediate_mode).convert(image.mode)
+ self.degenerate = image
class Contrast(_Enhance):
@@ -68,11 +70,15 @@ class Contrast(_Enhance):
def __init__(self, image: Image.Image) -> None:
self.image = image
- mean = int(ImageStat.Stat(image.convert("L")).mean[0] + 0.5)
- self.degenerate = Image.new("L", image.size, mean).convert(image.mode)
+ if image.mode != "L":
+ image = image.convert("L")
+ mean = int(ImageStat.Stat(image).mean[0] + 0.5)
+ self.degenerate = Image.new("L", image.size, mean)
+ if self.degenerate.mode != self.image.mode:
+ self.degenerate = self.degenerate.convert(self.image.mode)
- if "A" in image.getbands():
- self.degenerate.putalpha(image.getchannel("A"))
+ if "A" in self.image.getbands():
+ self.degenerate.putalpha(self.image.getchannel("A"))
class Brightness(_Enhance):
diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py
index f0e492387..5d0f87a9f 100644
--- a/src/PIL/ImageFile.py
+++ b/src/PIL/ImageFile.py
@@ -31,14 +31,18 @@ from __future__ import annotations
import abc
import io
import itertools
+import os
import struct
import sys
-from typing import IO, Any, NamedTuple
+from typing import IO, TYPE_CHECKING, Any, NamedTuple, cast
from . import Image
from ._deprecate import deprecate
from ._util import is_path
+if TYPE_CHECKING:
+ from ._typing import StrOrBytesPath
+
MAXBLOCK = 65536
SAFEBLOCK = 1024 * 1024
@@ -65,7 +69,7 @@ Dict of known error codes returned from :meth:`.PyDecoder.decode`,
# Helpers
-def _get_oserror(error, *, encoder):
+def _get_oserror(error: int, *, encoder: bool) -> OSError:
try:
msg = Image.core.getcodecstatus(error)
except AttributeError:
@@ -76,7 +80,7 @@ def _get_oserror(error, *, encoder):
return OSError(msg)
-def raise_oserror(error):
+def raise_oserror(error: int) -> OSError:
deprecate(
"raise_oserror",
12,
@@ -86,16 +90,16 @@ def raise_oserror(error):
raise _get_oserror(error, encoder=False)
-def _tilesort(t):
+def _tilesort(t: _Tile) -> int:
# sort on offset
return t[2]
class _Tile(NamedTuple):
codec_name: str
- extents: tuple[int, int, int, int]
- offset: int
- args: tuple[Any, ...] | str | None
+ extents: tuple[int, int, int, int] | None
+ offset: int = 0
+ args: tuple[Any, ...] | str | None = None
#
@@ -106,32 +110,34 @@ class _Tile(NamedTuple):
class ImageFile(Image.Image):
"""Base class for image file format handlers."""
- def __init__(self, fp=None, filename=None):
+ def __init__(
+ self, fp: StrOrBytesPath | IO[bytes], filename: str | bytes | None = None
+ ) -> None:
super().__init__()
self._min_frame = 0
- self.custom_mimetype = None
+ self.custom_mimetype: str | None = None
- self.tile = None
- """ A list of tile descriptors, or ``None`` """
+ self.tile: list[_Tile] = []
+ """ A list of tile descriptors """
self.readonly = 1 # until we know better
- self.decoderconfig = ()
+ self.decoderconfig: tuple[Any, ...] = ()
self.decodermaxblock = MAXBLOCK
if is_path(fp):
# filename
self.fp = open(fp, "rb")
- self.filename = fp
+ self.filename = os.fspath(fp)
self._exclusive_fp = True
else:
# stream
- self.fp = fp
- self.filename = filename
+ self.fp = cast(IO[bytes], fp)
+ self.filename = filename if filename is not None else ""
# can be overridden
- self._exclusive_fp = None
+ self._exclusive_fp = False
try:
try:
@@ -154,13 +160,17 @@ class ImageFile(Image.Image):
self.fp.close()
raise
- def get_format_mimetype(self):
+ def _open(self) -> None:
+ pass
+
+ def get_format_mimetype(self) -> str | None:
if self.custom_mimetype:
return self.custom_mimetype
if self.format is not None:
return Image.MIME.get(self.format.upper())
+ return None
- def __setstate__(self, state):
+ def __setstate__(self, state: list[Any]) -> None:
self.tile = []
super().__setstate__(state)
@@ -173,10 +183,10 @@ class ImageFile(Image.Image):
self.fp.close()
self.fp = None
- def load(self):
+ def load(self) -> Image.core.PixelAccess | None:
"""Load image data based on tile list"""
- if self.tile is None:
+ if not self.tile and self._im is None:
msg = "cannot load this image"
raise OSError(msg)
@@ -184,7 +194,7 @@ class ImageFile(Image.Image):
if not self.tile:
return pixel
- self.map = None
+ self.map: mmap.mmap | None = None
use_mmap = self.filename and len(self.tile) == 1
# As of pypy 2.1.0, memory mapping was failing here.
use_mmap = use_mmap and not hasattr(sys, "pypy_version_info")
@@ -192,17 +202,17 @@ class ImageFile(Image.Image):
readonly = 0
# look for read/seek overrides
- try:
+ if hasattr(self, "load_read"):
read = self.load_read
# don't use mmap if there are custom read/seek functions
use_mmap = False
- except AttributeError:
+ else:
read = self.fp.read
- try:
+ if hasattr(self, "load_seek"):
seek = self.load_seek
use_mmap = False
- except AttributeError:
+ else:
seek = self.fp.seek
if use_mmap:
@@ -212,6 +222,7 @@ class ImageFile(Image.Image):
args = (args, 0, 1)
if (
decoder_name == "raw"
+ and isinstance(args, tuple)
and len(args) >= 3
and args[0] == self.mode
and args[0] in Image._MAPMODES
@@ -242,11 +253,8 @@ class ImageFile(Image.Image):
# sort tiles in file order
self.tile.sort(key=_tilesort)
- try:
- # FIXME: This is a hack to handle TIFF's JpegTables tag.
- prefix = self.tile_prefix
- except AttributeError:
- prefix = b""
+ # FIXME: This is a hack to handle TIFF's JpegTables tag.
+ prefix = getattr(self, "tile_prefix", b"")
# Remove consecutive duplicates that only differ by their offset
self.tile = [
@@ -314,7 +322,7 @@ class ImageFile(Image.Image):
def load_prepare(self) -> None:
# create image memory if necessary
- if not self.im or self.im.mode != self.mode or self.im.size != self.size:
+ if self._im is None:
self.im = Image.core.new(self.mode, self.size)
# create palette (optional)
if self.mode == "P":
@@ -332,14 +340,14 @@ class ImageFile(Image.Image):
# def load_read(self, read_bytes: int) -> bytes:
# pass
- def _seek_check(self, frame):
+ def _seek_check(self, frame: int) -> bool:
if (
frame < self._min_frame
# Only check upper limit on frames if additional seek operations
# are not required to do so
or (
not (hasattr(self, "_n_frames") and self._n_frames is None)
- and frame >= self.n_frames + self._min_frame
+ and frame >= getattr(self, "n_frames") + self._min_frame
)
):
msg = "attempt to seek outside sequence"
@@ -365,11 +373,11 @@ class StubImageFile(ImageFile):
certain format, but relies on external code to load the file.
"""
- def _open(self):
+ def _open(self) -> None:
msg = "StubImageFile subclass must implement _open"
raise NotImplementedError(msg)
- def load(self):
+ def load(self) -> Image.core.PixelAccess | None:
loader = self._load()
if loader is None:
msg = f"cannot find loader for this {self.format} file"
@@ -377,11 +385,11 @@ class StubImageFile(ImageFile):
image = loader.load(self)
assert image is not None
# become the other object (!)
- self.__class__ = image.__class__
+ self.__class__ = image.__class__ # type: ignore[assignment]
self.__dict__ = image.__dict__
return image.load()
- def _load(self):
+ def _load(self) -> StubHandler | None:
"""(Hook) Find actual image loader."""
msg = "StubImageFile subclass must implement _load"
raise NotImplementedError(msg)
@@ -395,8 +403,8 @@ class Parser:
incremental = None
image: Image.Image | None = None
- data = None
- decoder = None
+ data: bytes | None = None
+ decoder: Image.core.ImagingDecoder | PyDecoder | None = None
offset = 0
finished = 0
@@ -408,7 +416,7 @@ class Parser:
"""
assert self.data is None, "cannot reuse parsers"
- def feed(self, data):
+ def feed(self, data: bytes) -> None:
"""
(Consumer) Feed data to the parser.
@@ -484,13 +492,13 @@ class Parser:
self.image = im
- def __enter__(self):
+ def __enter__(self) -> Parser:
return self
- def __exit__(self, *args):
+ def __exit__(self, *args: object) -> None:
self.close()
- def close(self):
+ def close(self) -> Image.Image:
"""
(Consumer) Close the stream.
@@ -524,7 +532,7 @@ class Parser:
# --------------------------------------------------------------------
-def _save(im, fp, tile, bufsize=0) -> None:
+def _save(im: Image.Image, fp: IO[bytes], tile: list[_Tile], bufsize: int = 0) -> None:
"""Helper to save image based on tile list
:param im: Image object.
@@ -552,7 +560,14 @@ def _save(im, fp, tile, bufsize=0) -> None:
fp.flush()
-def _encode_tile(im, fp, tile: list[_Tile], bufsize, fh, exc=None):
+def _encode_tile(
+ im: Image.Image,
+ fp: IO[bytes],
+ tile: list[_Tile],
+ bufsize: int,
+ fh: int | None,
+ exc: BaseException | None = None,
+) -> None:
for encoder_name, extents, offset, args in tile:
if offset > 0:
fp.seek(offset)
@@ -572,6 +587,7 @@ def _encode_tile(im, fp, tile: list[_Tile], bufsize, fh, exc=None):
break
else:
# slight speedup: compress to real file object
+ assert fh is not None
errcode = encoder.encode_to_file(fh, bufsize)
if errcode < 0:
raise _get_oserror(errcode, encoder=True) from exc
@@ -579,7 +595,7 @@ def _encode_tile(im, fp, tile: list[_Tile], bufsize, fh, exc=None):
encoder.cleanup()
-def _safe_read(fp, size):
+def _safe_read(fp: IO[bytes], size: int) -> bytes:
"""
Reads large blocks in a safe way. Unlike fp.read(n), this function
doesn't trust the user. If the requested size is larger than
@@ -600,18 +616,18 @@ def _safe_read(fp, size):
msg = "Truncated File Read"
raise OSError(msg)
return data
- data = []
+ blocks: list[bytes] = []
remaining_size = size
while remaining_size > 0:
block = fp.read(min(remaining_size, SAFEBLOCK))
if not block:
break
- data.append(block)
+ blocks.append(block)
remaining_size -= len(block)
- if sum(len(d) for d in data) < size:
+ if sum(len(block) for block in blocks) < size:
msg = "Truncated File Read"
raise OSError(msg)
- return b"".join(data)
+ return b"".join(blocks)
class PyCodecState:
@@ -621,25 +637,25 @@ class PyCodecState:
self.xoff = 0
self.yoff = 0
- def extents(self):
+ def extents(self) -> tuple[int, int, int, int]:
return self.xoff, self.yoff, self.xoff + self.xsize, self.yoff + self.ysize
class PyCodec:
fd: IO[bytes] | None
- def __init__(self, mode, *args):
- self.im = None
+ def __init__(self, mode: str, *args: Any) -> None:
+ self.im: Image.core.ImagingCore | None = None
self.state = PyCodecState()
self.fd = None
self.mode = mode
self.init(args)
- def init(self, args):
+ def init(self, args: tuple[Any, ...]) -> None:
"""
Override to perform codec specific initialization
- :param args: Array of args items from the tile entry
+ :param args: Tuple of arg items from the tile entry
:returns: None
"""
self.args = args
@@ -652,7 +668,7 @@ class PyCodec:
"""
pass
- def setfd(self, fd):
+ def setfd(self, fd: IO[bytes]) -> None:
"""
Called from ImageFile to set the Python file-like object
@@ -661,7 +677,11 @@ class PyCodec:
"""
self.fd = fd
- def setimage(self, im, extents=None):
+ def setimage(
+ self,
+ im: Image.core.ImagingCore,
+ extents: tuple[int, int, int, int] | None = None,
+ ) -> None:
"""
Called from ImageFile to set the core output image for the codec
@@ -710,10 +730,10 @@ class PyDecoder(PyCodec):
_pulls_fd = False
@property
- def pulls_fd(self):
+ def pulls_fd(self) -> bool:
return self._pulls_fd
- def decode(self, buffer):
+ def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int]:
"""
Override to perform the decoding process.
@@ -725,19 +745,23 @@ class PyDecoder(PyCodec):
msg = "unavailable in base decoder"
raise NotImplementedError(msg)
- def set_as_raw(self, data: bytes, rawmode=None) -> None:
+ def set_as_raw(
+ self, data: bytes, rawmode: str | None = None, extra: tuple[Any, ...] = ()
+ ) -> None:
"""
Convenience method to set the internal image from a stream of raw data
:param data: Bytes to be set
:param rawmode: The rawmode to be used for the decoder.
If not specified, it will default to the mode of the image
+ :param extra: Extra arguments for the decoder.
:returns: None
"""
if not rawmode:
rawmode = self.mode
- d = Image._getdecoder(self.mode, "raw", rawmode)
+ d = Image._getdecoder(self.mode, "raw", rawmode, extra)
+ assert self.im is not None
d.setimage(self.im, self.state.extents())
s = d.decode(data)
@@ -760,10 +784,10 @@ class PyEncoder(PyCodec):
_pushes_fd = False
@property
- def pushes_fd(self):
+ def pushes_fd(self) -> bool:
return self._pushes_fd
- def encode(self, bufsize):
+ def encode(self, bufsize: int) -> tuple[int, int, bytes]:
"""
Override to perform the encoding process.
@@ -775,7 +799,7 @@ class PyEncoder(PyCodec):
msg = "unavailable in base encoder"
raise NotImplementedError(msg)
- def encode_to_pyfd(self):
+ def encode_to_pyfd(self) -> tuple[int, int]:
"""
If ``pushes_fd`` is ``True``, then this method will be used,
and ``encode()`` will only be called once.
@@ -787,10 +811,11 @@ class PyEncoder(PyCodec):
return 0, -8 # bad configuration
bytes_consumed, errcode, data = self.encode(0)
if data:
+ assert self.fd is not None
self.fd.write(data)
return bytes_consumed, errcode
- def encode_to_file(self, fh, bufsize):
+ def encode_to_file(self, fh: int, bufsize: int) -> int:
"""
:param fh: File handle.
:param bufsize: Buffer size.
@@ -803,5 +828,5 @@ class PyEncoder(PyCodec):
while errcode == 0:
status, errcode, buf = self.encode(bufsize)
if status > 0:
- fh.write(buf[status:])
+ os.write(fh, buf[status:])
return errcode
diff --git a/src/PIL/ImageFilter.py b/src/PIL/ImageFilter.py
index 02288e135..b350e56f4 100644
--- a/src/PIL/ImageFilter.py
+++ b/src/PIL/ImageFilter.py
@@ -18,13 +18,18 @@ from __future__ import annotations
import abc
import functools
+from collections.abc import Sequence
from types import ModuleType
-from typing import Any, Sequence
+from typing import TYPE_CHECKING, Any, Callable, cast
+
+if TYPE_CHECKING:
+ from . import _imaging
+ from ._typing import NumpyArray
class Filter:
@abc.abstractmethod
- def filter(self, image):
+ def filter(self, image: _imaging.ImagingCore) -> _imaging.ImagingCore:
pass
@@ -33,7 +38,9 @@ class MultibandFilter(Filter):
class BuiltinFilter(MultibandFilter):
- def filter(self, image):
+ filterargs: tuple[Any, ...]
+
+ def filter(self, image: _imaging.ImagingCore) -> _imaging.ImagingCore:
if image.mode == "P":
msg = "cannot filter palette images"
raise ValueError(msg)
@@ -91,7 +98,7 @@ class RankFilter(Filter):
self.size = size
self.rank = rank
- def filter(self, image):
+ def filter(self, image: _imaging.ImagingCore) -> _imaging.ImagingCore:
if image.mode == "P":
msg = "cannot filter palette images"
raise ValueError(msg)
@@ -158,7 +165,7 @@ class ModeFilter(Filter):
def __init__(self, size: int = 3) -> None:
self.size = size
- def filter(self, image):
+ def filter(self, image: _imaging.ImagingCore) -> _imaging.ImagingCore:
return image.modefilter(self.size)
@@ -176,9 +183,9 @@ class GaussianBlur(MultibandFilter):
def __init__(self, radius: float | Sequence[float] = 2) -> None:
self.radius = radius
- def filter(self, image):
+ def filter(self, image: _imaging.ImagingCore) -> _imaging.ImagingCore:
xy = self.radius
- if not isinstance(xy, (tuple, list)):
+ if isinstance(xy, (int, float)):
xy = (xy, xy)
if xy == (0, 0):
return image.copy()
@@ -208,9 +215,9 @@ class BoxBlur(MultibandFilter):
raise ValueError(msg)
self.radius = radius
- def filter(self, image):
+ def filter(self, image: _imaging.ImagingCore) -> _imaging.ImagingCore:
xy = self.radius
- if not isinstance(xy, (tuple, list)):
+ if isinstance(xy, (int, float)):
xy = (xy, xy)
if xy == (0, 0):
return image.copy()
@@ -241,7 +248,7 @@ class UnsharpMask(MultibandFilter):
self.percent = percent
self.threshold = threshold
- def filter(self, image):
+ def filter(self, image: _imaging.ImagingCore) -> _imaging.ImagingCore:
return image.unsharp_mask(self.radius, self.percent, self.threshold)
@@ -387,8 +394,13 @@ class Color3DLUT(MultibandFilter):
name = "Color 3D LUT"
def __init__(
- self, size, table, channels: int = 3, target_mode: str | None = None, **kwargs
- ):
+ self,
+ size: int | tuple[int, int, int],
+ table: Sequence[float] | Sequence[Sequence[int]] | NumpyArray,
+ channels: int = 3,
+ target_mode: str | None = None,
+ **kwargs: bool,
+ ) -> None:
if channels not in (3, 4):
msg = "Only 3 or 4 output channels are supported"
raise ValueError(msg)
@@ -410,15 +422,16 @@ class Color3DLUT(MultibandFilter):
pass
if numpy and isinstance(table, numpy.ndarray):
+ numpy_table: NumpyArray = table
if copy_table:
- table = table.copy()
+ numpy_table = numpy_table.copy()
- if table.shape in [
+ if numpy_table.shape in [
(items * channels,),
(items, channels),
(size[2], size[1], size[0], channels),
]:
- table = table.reshape(items * channels)
+ table = numpy_table.reshape(items * channels)
else:
wrong_size = True
@@ -428,7 +441,8 @@ class Color3DLUT(MultibandFilter):
# Convert to a flat list
if table and isinstance(table[0], (list, tuple)):
- table, raw_table = [], table
+ raw_table = cast(Sequence[Sequence[int]], table)
+ flat_table: list[int] = []
for pixel in raw_table:
if len(pixel) != channels:
msg = (
@@ -436,7 +450,8 @@ class Color3DLUT(MultibandFilter):
f"have a length of {channels}."
)
raise ValueError(msg)
- table.extend(pixel)
+ flat_table.extend(pixel)
+ table = flat_table
if wrong_size or len(table) != items * channels:
msg = (
@@ -449,7 +464,7 @@ class Color3DLUT(MultibandFilter):
self.table = table
@staticmethod
- def _check_size(size: Any) -> list[int]:
+ def _check_size(size: Any) -> tuple[int, int, int]:
try:
_, _, _ = size
except ValueError as e:
@@ -457,7 +472,7 @@ class Color3DLUT(MultibandFilter):
raise ValueError(msg) from e
except TypeError:
size = (size, size, size)
- size = [int(x) for x in size]
+ size = tuple(int(x) for x in size)
for size_1d in size:
if not 2 <= size_1d <= 65:
msg = "Size should be in [2, 65] range."
@@ -465,7 +480,13 @@ class Color3DLUT(MultibandFilter):
return size
@classmethod
- def generate(cls, size, callback, channels=3, target_mode=None):
+ def generate(
+ cls,
+ size: int | tuple[int, int, int],
+ callback: Callable[[float, float, float], tuple[float, ...]],
+ channels: int = 3,
+ target_mode: str | None = None,
+ ) -> Color3DLUT:
"""Generates new LUT using provided callback.
:param size: Size of the table. Passed to the constructor.
@@ -482,7 +503,7 @@ class Color3DLUT(MultibandFilter):
msg = "Only 3 or 4 output channels are supported"
raise ValueError(msg)
- table = [0] * (size_1d * size_2d * size_3d * channels)
+ table: list[float] = [0] * (size_1d * size_2d * size_3d * channels)
idx_out = 0
for b in range(size_3d):
for g in range(size_2d):
@@ -500,7 +521,13 @@ class Color3DLUT(MultibandFilter):
_copy_table=False,
)
- def transform(self, callback, with_normals=False, channels=None, target_mode=None):
+ def transform(
+ self,
+ callback: Callable[..., tuple[float, ...]],
+ with_normals: bool = False,
+ channels: int | None = None,
+ target_mode: str | None = None,
+ ) -> Color3DLUT:
"""Transforms the table values using provided callback and returns
a new LUT with altered values.
@@ -526,7 +553,7 @@ class Color3DLUT(MultibandFilter):
ch_out = channels or ch_in
size_1d, size_2d, size_3d = self.size
- table = [0] * (size_1d * size_2d * size_3d * ch_out)
+ table: list[float] = [0] * (size_1d * size_2d * size_3d * ch_out)
idx_in = 0
idx_out = 0
for b in range(size_3d):
@@ -564,7 +591,7 @@ class Color3DLUT(MultibandFilter):
r.append(f"target_mode={self.mode}")
return "<{}>".format(" ".join(r))
- def filter(self, image):
+ def filter(self, image: _imaging.ImagingCore) -> _imaging.ImagingCore:
from . import Image
return image.color_lut_3d(
diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py
index fa5608e6c..d8c265560 100644
--- a/src/PIL/ImageFont.py
+++ b/src/PIL/ImageFont.py
@@ -33,11 +33,12 @@ import sys
import warnings
from enum import IntEnum
from io import BytesIO
-from typing import IO, TYPE_CHECKING, Any, BinaryIO
+from types import ModuleType
+from typing import IO, TYPE_CHECKING, Any, BinaryIO, TypedDict, cast
-from . import Image
+from . import Image, features
from ._typing import StrOrBytesPath
-from ._util import is_path
+from ._util import DeferredError, is_path
if TYPE_CHECKING:
from . import ImageFile
@@ -45,6 +46,13 @@ if TYPE_CHECKING:
from ._imagingft import Font
+class Axis(TypedDict):
+ minimum: int | None
+ default: int | None
+ maximum: int | None
+ name: bytes | None
+
+
class Layout(IntEnum):
BASIC = 0
RAQM = 1
@@ -53,11 +61,10 @@ class Layout(IntEnum):
MAX_STRING_LENGTH = 1_000_000
+core: ModuleType | DeferredError
try:
from . import _imagingft as core
except ImportError as ex:
- from ._util import DeferredError
-
core = DeferredError.new(ex)
@@ -91,11 +98,13 @@ class ImageFont:
def _load_pilfont(self, filename: str) -> None:
with open(filename, "rb") as fp:
image: ImageFile.ImageFile | None = None
+ root = os.path.splitext(filename)[0]
+
for ext in (".png", ".gif", ".pbm"):
if image:
image.close()
try:
- fullname = os.path.splitext(filename)[0] + ext
+ fullname = root + ext
image = Image.open(fullname)
except Exception:
pass
@@ -105,7 +114,8 @@ class ImageFont:
else:
if image:
image.close()
- msg = "cannot find glyph data file"
+
+ msg = f"cannot find glyph data file {root}.{{gif|pbm|png}}"
raise OSError(msg)
self.file = fullname
@@ -138,7 +148,9 @@ class ImageFont:
self.font = Image.core.font(image.im, data)
- def getmask(self, text, mode="", *args, **kwargs):
+ def getmask(
+ self, text: str | bytes, mode: str = "", *args: Any, **kwargs: Any
+ ) -> Image.core.ImagingCore:
"""
Create a bitmap for the text.
@@ -199,10 +211,11 @@ class FreeTypeFont:
"""FreeType font wrapper (requires _imagingft service)"""
font: Font
+ font_bytes: bytes
def __init__(
self,
- font: StrOrBytesPath | BinaryIO | None = None,
+ font: StrOrBytesPath | BinaryIO,
size: float = 10,
index: int = 0,
encoding: str = "",
@@ -210,8 +223,11 @@ class FreeTypeFont:
) -> None:
# FIXME: use service provider instead
+ if isinstance(core, DeferredError):
+ raise core.ex
+
if size <= 0:
- msg = "font size must be greater than 0"
+ msg = f"font size must be greater than 0, not {size}"
raise ValueError(msg)
self.path = font
@@ -219,6 +235,21 @@ class FreeTypeFont:
self.index = index
self.encoding = encoding
+ try:
+ from packaging.version import parse as parse_version
+ except ImportError:
+ pass
+ else:
+ if freetype_version := features.version_module("freetype2"):
+ if parse_version(freetype_version) < parse_version("2.9.1"):
+ warnings.warn(
+ "Support for FreeType 2.9.0 is deprecated and will be removed "
+ "in Pillow 12 (2025-10-15). Please upgrade to FreeType 2.9.1 "
+ "or newer, preferably FreeType 2.10.4 which fixes "
+ "CVE-2020-15999.",
+ DeprecationWarning,
+ )
+
if layout_engine not in (Layout.BASIC, Layout.RAQM):
layout_engine = Layout.BASIC
if core.HAVE_RAQM:
@@ -232,14 +263,14 @@ class FreeTypeFont:
self.layout_engine = layout_engine
- def load_from_bytes(f):
+ def load_from_bytes(f: IO[bytes]) -> None:
self.font_bytes = f.read()
self.font = core.getfont(
"", size, index, encoding, self.font_bytes, layout_engine
)
if is_path(font):
- font = os.path.realpath(os.fspath(font))
+ font = os.fspath(font)
if sys.platform == "win32":
font_bytes_path = font if isinstance(font, bytes) else font.encode()
try:
@@ -254,14 +285,14 @@ class FreeTypeFont:
font, size, index, encoding, layout_engine=layout_engine
)
else:
- load_from_bytes(font)
+ load_from_bytes(cast(IO[bytes], font))
- def __getstate__(self):
+ def __getstate__(self) -> list[Any]:
return [self.path, self.size, self.index, self.encoding, self.layout_engine]
- def __setstate__(self, state):
+ def __setstate__(self, state: list[Any]) -> None:
path, size, index, encoding, layout_engine = state
- self.__init__(path, size, index, encoding, layout_engine)
+ FreeTypeFont.__init__(self, path, size, index, encoding, layout_engine)
def getname(self) -> tuple[str | None, str | None]:
"""
@@ -279,7 +310,12 @@ class FreeTypeFont:
return self.font.ascent, self.font.descent
def getlength(
- self, text: str, mode="", direction=None, features=None, language=None
+ self,
+ text: str | bytes,
+ mode: str = "",
+ direction: str | None = None,
+ features: list[str] | None = None,
+ language: str | None = None,
) -> float:
"""
Returns length (in pixels with 1/64 precision) of given text when rendered
@@ -354,7 +390,7 @@ class FreeTypeFont:
def getbbox(
self,
- text: str,
+ text: str | bytes,
mode: str = "",
direction: str | None = None,
features: list[str] | None = None,
@@ -420,16 +456,16 @@ class FreeTypeFont:
def getmask(
self,
- text,
- mode="",
- direction=None,
- features=None,
- language=None,
- stroke_width=0,
- anchor=None,
- ink=0,
- start=None,
- ):
+ text: str | bytes,
+ mode: str = "",
+ direction: str | None = None,
+ features: list[str] | None = None,
+ language: str | None = None,
+ stroke_width: float = 0,
+ anchor: str | None = None,
+ ink: int = 0,
+ start: tuple[float, float] | None = None,
+ ) -> Image.core.ImagingCore:
"""
Create a bitmap for the text.
@@ -511,18 +547,18 @@ class FreeTypeFont:
def getmask2(
self,
- text: str,
- mode="",
- direction=None,
- features=None,
- language=None,
- stroke_width=0,
- anchor=None,
- ink=0,
- start=None,
- *args,
- **kwargs,
- ):
+ text: str | bytes,
+ mode: str = "",
+ direction: str | None = None,
+ features: list[str] | None = None,
+ language: str | None = None,
+ stroke_width: float = 0,
+ anchor: str | None = None,
+ ink: int = 0,
+ start: tuple[float, float] | None = None,
+ *args: Any,
+ **kwargs: Any,
+ ) -> tuple[Image.core.ImagingCore, tuple[int, int]]:
"""
Create a bitmap for the text.
@@ -595,7 +631,7 @@ class FreeTypeFont:
if start is None:
start = (0, 0)
- def fill(width, height):
+ def fill(width: int, height: int) -> Image.core.ImagingCore:
size = (width, height)
Image._decompression_bomb_check(size)
return Image.core.fill("RGBA" if mode == "RGBA" else "L", size)
@@ -615,8 +651,13 @@ class FreeTypeFont:
)
def font_variant(
- self, font=None, size=None, index=None, encoding=None, layout_engine=None
- ):
+ self,
+ font: StrOrBytesPath | BinaryIO | None = None,
+ size: float | None = None,
+ index: int | None = None,
+ encoding: str | None = None,
+ layout_engine: Layout | None = None,
+ ) -> FreeTypeFont:
"""
Create a copy of this FreeTypeFont object,
using any specified arguments to override the settings.
@@ -651,7 +692,7 @@ class FreeTypeFont:
raise NotImplementedError(msg) from e
return [name.replace(b"\x00", b"") for name in names]
- def set_variation_by_name(self, name):
+ def set_variation_by_name(self, name: str | bytes) -> None:
"""
:param name: The name of the style.
:exception OSError: If the font is not a variation font.
@@ -670,7 +711,7 @@ class FreeTypeFont:
self.font.setvarname(index)
- def get_variation_axes(self):
+ def get_variation_axes(self) -> list[Axis]:
"""
:returns: A list of the axes in a variation font.
:exception OSError: If the font is not a variation font.
@@ -700,7 +741,9 @@ class FreeTypeFont:
class TransposedFont:
"""Wrapper for writing rotated or mirrored text"""
- def __init__(self, font, orientation=None):
+ def __init__(
+ self, font: ImageFont | FreeTypeFont, orientation: Image.Transpose | None = None
+ ):
"""
Wrapper that creates a transposed font from any existing font
object.
@@ -714,13 +757,17 @@ class TransposedFont:
self.font = font
self.orientation = orientation # any 'transpose' argument, or None
- def getmask(self, text, mode="", *args, **kwargs):
+ def getmask(
+ self, text: str | bytes, mode: str = "", *args: Any, **kwargs: Any
+ ) -> Image.core.ImagingCore:
im = self.font.getmask(text, mode, *args, **kwargs)
if self.orientation is not None:
return im.transpose(self.orientation)
return im
- def getbbox(self, text, *args, **kwargs):
+ def getbbox(
+ self, text: str | bytes, *args: Any, **kwargs: Any
+ ) -> tuple[int, int, float, float]:
# TransposedFont doesn't support getmask2, move top-left point to (0, 0)
# this has no effect on ImageFont and simulates anchor="lt" for FreeTypeFont
left, top, right, bottom = self.font.getbbox(text, *args, **kwargs)
@@ -730,7 +777,7 @@ class TransposedFont:
return 0, 0, height, width
return 0, 0, width, height
- def getlength(self, text: str, *args, **kwargs) -> float:
+ def getlength(self, text: str | bytes, *args: Any, **kwargs: Any) -> float:
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)
@@ -739,8 +786,9 @@ class TransposedFont:
def load(filename: str) -> ImageFont:
"""
- Load a font file. This function loads a font object from the given
- bitmap font file, and returns the corresponding font object.
+ Load a font file. This function loads a font object from the given
+ bitmap font file, and returns the corresponding font object. For loading TrueType
+ or OpenType fonts instead, see :py:func:`~PIL.ImageFont.truetype`.
:param filename: Name of font file.
:return: A font object.
@@ -752,7 +800,7 @@ def load(filename: str) -> ImageFont:
def truetype(
- font: StrOrBytesPath | BinaryIO | None = None,
+ font: StrOrBytesPath | BinaryIO,
size: float = 10,
index: int = 0,
encoding: str = "",
@@ -760,9 +808,10 @@ def truetype(
) -> FreeTypeFont:
"""
Load a TrueType or OpenType font from a file or file-like object,
- and create a font object.
- This function loads a font object from the given file or file-like
- object, and creates a font object for a font of the given size.
+ and create a font object. This function loads a font object from the given
+ file or file-like object, and creates a font object for a font of the given
+ size. For loading bitmap fonts instead, see :py:func:`~PIL.ImageFont.load`
+ and :py:func:`~PIL.ImageFont.load_path`.
Pillow uses FreeType to open font files. On Windows, be aware that FreeType
will keep the file open as long as the FreeTypeFont object exists. Windows
@@ -775,10 +824,15 @@ def truetype(
:param font: A filename or file-like object containing a TrueType font.
If the file is not found in this filename, the loader may also
- search in other directories, such as the :file:`fonts/`
- directory on Windows or :file:`/Library/Fonts/`,
- :file:`/System/Library/Fonts/` and :file:`~/Library/Fonts/` on
- macOS.
+ search in other directories, such as:
+
+ * The :file:`fonts/` directory on Windows,
+ * :file:`/Library/Fonts/`, :file:`/System/Library/Fonts/`
+ and :file:`~/Library/Fonts/` on macOS.
+ * :file:`~/.local/share/fonts`, :file:`/usr/local/share/fonts`,
+ and :file:`/usr/share/fonts` on Linux; or those specified by
+ the ``XDG_DATA_HOME`` and ``XDG_DATA_DIRS`` environment variables
+ for user-installed and system-wide fonts, respectively.
:param size: The requested size, in pixels.
:param index: Which font face to load (default is first available face).
@@ -818,7 +872,7 @@ def truetype(
:exception ValueError: If the font size is not greater than zero.
"""
- def freetype(font: StrOrBytesPath | BinaryIO | None) -> FreeTypeFont:
+ def freetype(font: StrOrBytesPath | BinaryIO) -> FreeTypeFont:
return FreeTypeFont(font, size, index, encoding, layout_engine)
try:
@@ -837,12 +891,21 @@ def truetype(
if windir:
dirs.append(os.path.join(windir, "fonts"))
elif sys.platform in ("linux", "linux2"):
- lindirs = os.environ.get("XDG_DATA_DIRS")
- if not lindirs:
- # According to the freedesktop spec, XDG_DATA_DIRS should
- # default to /usr/share
- lindirs = "/usr/share"
- dirs += [os.path.join(lindir, "fonts") for lindir in lindirs.split(":")]
+ data_home = os.environ.get("XDG_DATA_HOME")
+ if not data_home:
+ # The freedesktop spec defines the following default directory for
+ # when XDG_DATA_HOME is unset or empty. This user-level directory
+ # takes precedence over system-level directories.
+ data_home = os.path.expanduser("~/.local/share")
+ xdg_dirs = [data_home]
+
+ data_dirs = os.environ.get("XDG_DATA_DIRS")
+ if not data_dirs:
+ # Similarly, defaults are defined for the system-level directories
+ data_dirs = "/usr/local/share:/usr/share"
+ xdg_dirs += data_dirs.split(":")
+
+ dirs += [os.path.join(xdg_dir, "fonts") for xdg_dir in xdg_dirs]
elif sys.platform == "darwin":
dirs += [
"/Library/Fonts",
@@ -884,10 +947,149 @@ def load_path(filename: str | bytes) -> ImageFont:
return load(os.path.join(directory, filename))
except OSError:
pass
- msg = "cannot find font file"
+ msg = f'cannot find font file "{filename}" in sys.path'
+ if os.path.exists(filename):
+ msg += f', did you mean ImageFont.load("{filename}") instead?'
+
raise OSError(msg)
+def load_default_imagefont() -> ImageFont:
+ f = ImageFont()
+ f._load_pilfont_data(
+ # courB08
+ BytesIO(
+ base64.b64decode(
+ b"""
+UElMZm9udAo7Ozs7OzsxMDsKREFUQQoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAAA//8AAQAAAAAAAAABAAEA
+BgAAAAH/+gADAAAAAQAAAAMABgAGAAAAAf/6AAT//QADAAAABgADAAYAAAAA//kABQABAAYAAAAL
+AAgABgAAAAD/+AAFAAEACwAAABAACQAGAAAAAP/5AAUAAAAQAAAAFQAHAAYAAP////oABQAAABUA
+AAAbAAYABgAAAAH/+QAE//wAGwAAAB4AAwAGAAAAAf/5AAQAAQAeAAAAIQAIAAYAAAAB//kABAAB
+ACEAAAAkAAgABgAAAAD/+QAE//0AJAAAACgABAAGAAAAAP/6AAX//wAoAAAALQAFAAYAAAAB//8A
+BAACAC0AAAAwAAMABgAAAAD//AAF//0AMAAAADUAAQAGAAAAAf//AAMAAAA1AAAANwABAAYAAAAB
+//kABQABADcAAAA7AAgABgAAAAD/+QAFAAAAOwAAAEAABwAGAAAAAP/5AAYAAABAAAAARgAHAAYA
+AAAA//kABQAAAEYAAABLAAcABgAAAAD/+QAFAAAASwAAAFAABwAGAAAAAP/5AAYAAABQAAAAVgAH
+AAYAAAAA//kABQAAAFYAAABbAAcABgAAAAD/+QAFAAAAWwAAAGAABwAGAAAAAP/5AAUAAABgAAAA
+ZQAHAAYAAAAA//kABQAAAGUAAABqAAcABgAAAAD/+QAFAAAAagAAAG8ABwAGAAAAAf/8AAMAAABv
+AAAAcQAEAAYAAAAA//wAAwACAHEAAAB0AAYABgAAAAD/+gAE//8AdAAAAHgABQAGAAAAAP/7AAT/
+/gB4AAAAfAADAAYAAAAB//oABf//AHwAAACAAAUABgAAAAD/+gAFAAAAgAAAAIUABgAGAAAAAP/5
+AAYAAQCFAAAAiwAIAAYAAP////oABgAAAIsAAACSAAYABgAA////+gAFAAAAkgAAAJgABgAGAAAA
+AP/6AAUAAACYAAAAnQAGAAYAAP////oABQAAAJ0AAACjAAYABgAA////+gAFAAAAowAAAKkABgAG
+AAD////6AAUAAACpAAAArwAGAAYAAAAA//oABQAAAK8AAAC0AAYABgAA////+gAGAAAAtAAAALsA
+BgAGAAAAAP/6AAQAAAC7AAAAvwAGAAYAAP////oABQAAAL8AAADFAAYABgAA////+gAGAAAAxQAA
+AMwABgAGAAD////6AAUAAADMAAAA0gAGAAYAAP////oABQAAANIAAADYAAYABgAA////+gAGAAAA
+2AAAAN8ABgAGAAAAAP/6AAUAAADfAAAA5AAGAAYAAP////oABQAAAOQAAADqAAYABgAAAAD/+gAF
+AAEA6gAAAO8ABwAGAAD////6AAYAAADvAAAA9gAGAAYAAAAA//oABQAAAPYAAAD7AAYABgAA////
++gAFAAAA+wAAAQEABgAGAAD////6AAYAAAEBAAABCAAGAAYAAP////oABgAAAQgAAAEPAAYABgAA
+////+gAGAAABDwAAARYABgAGAAAAAP/6AAYAAAEWAAABHAAGAAYAAP////oABgAAARwAAAEjAAYA
+BgAAAAD/+gAFAAABIwAAASgABgAGAAAAAf/5AAQAAQEoAAABKwAIAAYAAAAA//kABAABASsAAAEv
+AAgABgAAAAH/+QAEAAEBLwAAATIACAAGAAAAAP/5AAX//AEyAAABNwADAAYAAAAAAAEABgACATcA
+AAE9AAEABgAAAAH/+QAE//wBPQAAAUAAAwAGAAAAAP/7AAYAAAFAAAABRgAFAAYAAP////kABQAA
+AUYAAAFMAAcABgAAAAD/+wAFAAABTAAAAVEABQAGAAAAAP/5AAYAAAFRAAABVwAHAAYAAAAA//sA
+BQAAAVcAAAFcAAUABgAAAAD/+QAFAAABXAAAAWEABwAGAAAAAP/7AAYAAgFhAAABZwAHAAYAAP//
+//kABQAAAWcAAAFtAAcABgAAAAD/+QAGAAABbQAAAXMABwAGAAAAAP/5AAQAAgFzAAABdwAJAAYA
+AP////kABgAAAXcAAAF+AAcABgAAAAD/+QAGAAABfgAAAYQABwAGAAD////7AAUAAAGEAAABigAF
+AAYAAP////sABQAAAYoAAAGQAAUABgAAAAD/+wAFAAABkAAAAZUABQAGAAD////7AAUAAgGVAAAB
+mwAHAAYAAAAA//sABgACAZsAAAGhAAcABgAAAAD/+wAGAAABoQAAAacABQAGAAAAAP/7AAYAAAGn
+AAABrQAFAAYAAAAA//kABgAAAa0AAAGzAAcABgAA////+wAGAAABswAAAboABQAGAAD////7AAUA
+AAG6AAABwAAFAAYAAP////sABgAAAcAAAAHHAAUABgAAAAD/+wAGAAABxwAAAc0ABQAGAAD////7
+AAYAAgHNAAAB1AAHAAYAAAAA//sABQAAAdQAAAHZAAUABgAAAAH/+QAFAAEB2QAAAd0ACAAGAAAA
+Av/6AAMAAQHdAAAB3gAHAAYAAAAA//kABAABAd4AAAHiAAgABgAAAAD/+wAF//0B4gAAAecAAgAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAAB
+//sAAwACAecAAAHpAAcABgAAAAD/+QAFAAEB6QAAAe4ACAAGAAAAAP/5AAYAAAHuAAAB9AAHAAYA
+AAAA//oABf//AfQAAAH5AAUABgAAAAD/+QAGAAAB+QAAAf8ABwAGAAAAAv/5AAMAAgH/AAACAAAJ
+AAYAAAAA//kABQABAgAAAAIFAAgABgAAAAH/+gAE//sCBQAAAggAAQAGAAAAAP/5AAYAAAIIAAAC
+DgAHAAYAAAAB//kABf/+Ag4AAAISAAUABgAA////+wAGAAACEgAAAhkABQAGAAAAAP/7AAX//gIZ
+AAACHgADAAYAAAAA//wABf/9Ah4AAAIjAAEABgAAAAD/+QAHAAACIwAAAioABwAGAAAAAP/6AAT/
++wIqAAACLgABAAYAAAAA//kABP/8Ai4AAAIyAAMABgAAAAD/+gAFAAACMgAAAjcABgAGAAAAAf/5
+AAT//QI3AAACOgAEAAYAAAAB//kABP/9AjoAAAI9AAQABgAAAAL/+QAE//sCPQAAAj8AAgAGAAD/
+///7AAYAAgI/AAACRgAHAAYAAAAA//kABgABAkYAAAJMAAgABgAAAAH//AAD//0CTAAAAk4AAQAG
+AAAAAf//AAQAAgJOAAACUQADAAYAAAAB//kABP/9AlEAAAJUAAQABgAAAAH/+QAF//4CVAAAAlgA
+BQAGAAD////7AAYAAAJYAAACXwAFAAYAAP////kABgAAAl8AAAJmAAcABgAA////+QAGAAACZgAA
+Am0ABwAGAAD////5AAYAAAJtAAACdAAHAAYAAAAA//sABQACAnQAAAJ5AAcABgAA////9wAGAAAC
+eQAAAoAACQAGAAD////3AAYAAAKAAAAChwAJAAYAAP////cABgAAAocAAAKOAAkABgAA////9wAG
+AAACjgAAApUACQAGAAD////4AAYAAAKVAAACnAAIAAYAAP////cABgAAApwAAAKjAAkABgAA////
++gAGAAACowAAAqoABgAGAAAAAP/6AAUAAgKqAAACrwAIAAYAAP////cABQAAAq8AAAK1AAkABgAA
+////9wAFAAACtQAAArsACQAGAAD////3AAUAAAK7AAACwQAJAAYAAP////gABQAAAsEAAALHAAgA
+BgAAAAD/9wAEAAACxwAAAssACQAGAAAAAP/3AAQAAALLAAACzwAJAAYAAAAA//cABAAAAs8AAALT
+AAkABgAAAAD/+AAEAAAC0wAAAtcACAAGAAD////6AAUAAALXAAAC3QAGAAYAAP////cABgAAAt0A
+AALkAAkABgAAAAD/9wAFAAAC5AAAAukACQAGAAAAAP/3AAUAAALpAAAC7gAJAAYAAAAA//cABQAA
+Au4AAALzAAkABgAAAAD/9wAFAAAC8wAAAvgACQAGAAAAAP/4AAUAAAL4AAAC/QAIAAYAAAAA//oA
+Bf//Av0AAAMCAAUABgAA////+gAGAAADAgAAAwkABgAGAAD////3AAYAAAMJAAADEAAJAAYAAP//
+//cABgAAAxAAAAMXAAkABgAA////9wAGAAADFwAAAx4ACQAGAAD////4AAYAAAAAAAoABwASAAYA
+AP////cABgAAAAcACgAOABMABgAA////+gAFAAAADgAKABQAEAAGAAD////6AAYAAAAUAAoAGwAQ
+AAYAAAAA//gABgAAABsACgAhABIABgAAAAD/+AAGAAAAIQAKACcAEgAGAAAAAP/4AAYAAAAnAAoA
+LQASAAYAAAAA//gABgAAAC0ACgAzABIABgAAAAD/+QAGAAAAMwAKADkAEQAGAAAAAP/3AAYAAAA5
+AAoAPwATAAYAAP////sABQAAAD8ACgBFAA8ABgAAAAD/+wAFAAIARQAKAEoAEQAGAAAAAP/4AAUA
+AABKAAoATwASAAYAAAAA//gABQAAAE8ACgBUABIABgAAAAD/+AAFAAAAVAAKAFkAEgAGAAAAAP/5
+AAUAAABZAAoAXgARAAYAAAAA//gABgAAAF4ACgBkABIABgAAAAD/+AAGAAAAZAAKAGoAEgAGAAAA
+AP/4AAYAAABqAAoAcAASAAYAAAAA//kABgAAAHAACgB2ABEABgAAAAD/+AAFAAAAdgAKAHsAEgAG
+AAD////4AAYAAAB7AAoAggASAAYAAAAA//gABQAAAIIACgCHABIABgAAAAD/+AAFAAAAhwAKAIwA
+EgAGAAAAAP/4AAUAAACMAAoAkQASAAYAAAAA//gABQAAAJEACgCWABIABgAAAAD/+QAFAAAAlgAK
+AJsAEQAGAAAAAP/6AAX//wCbAAoAoAAPAAYAAAAA//oABQABAKAACgClABEABgAA////+AAGAAAA
+pQAKAKwAEgAGAAD////4AAYAAACsAAoAswASAAYAAP////gABgAAALMACgC6ABIABgAA////+QAG
+AAAAugAKAMEAEQAGAAD////4AAYAAgDBAAoAyAAUAAYAAP////kABQACAMgACgDOABMABgAA////
++QAGAAIAzgAKANUAEw==
+"""
+ )
+ ),
+ Image.open(
+ BytesIO(
+ base64.b64decode(
+ b"""
+iVBORw0KGgoAAAANSUhEUgAAAx4AAAAUAQAAAAArMtZoAAAEwElEQVR4nABlAJr/AHVE4czCI/4u
+Mc4b7vuds/xzjz5/3/7u/n9vMe7vnfH/9++vPn/xyf5zhxzjt8GHw8+2d83u8x27199/nxuQ6Od9
+M43/5z2I+9n9ZtmDBwMQECDRQw/eQIQohJXxpBCNVE6QCCAAAAD//wBlAJr/AgALyj1t/wINwq0g
+LeNZUworuN1cjTPIzrTX6ofHWeo3v336qPzfEwRmBnHTtf95/fglZK5N0PDgfRTslpGBvz7LFc4F
+IUXBWQGjQ5MGCx34EDFPwXiY4YbYxavpnhHFrk14CDAAAAD//wBlAJr/AgKqRooH2gAgPeggvUAA
+Bu2WfgPoAwzRAABAAAAAAACQgLz/3Uv4Gv+gX7BJgDeeGP6AAAD1NMDzKHD7ANWr3loYbxsAD791
+NAADfcoIDyP44K/jv4Y63/Z+t98Ovt+ub4T48LAAAAD//wBlAJr/AuplMlADJAAAAGuAphWpqhMx
+in0A/fRvAYBABPgBwBUgABBQ/sYAyv9g0bCHgOLoGAAAAAAAREAAwI7nr0ArYpow7aX8//9LaP/9
+SjdavWA8ePHeBIKB//81/83ndznOaXx379wAAAD//wBlAJr/AqDxW+D3AABAAbUh/QMnbQag/gAY
+AYDAAACgtgD/gOqAAAB5IA/8AAAk+n9w0AAA8AAAmFRJuPo27ciC0cD5oeW4E7KA/wD3ECMAn2tt
+y8PgwH8AfAxFzC0JzeAMtratAsC/ffwAAAD//wBlAJr/BGKAyCAA4AAAAvgeYTAwHd1kmQF5chkG
+ABoMIHcL5xVpTfQbUqzlAAAErwAQBgAAEOClA5D9il08AEh/tUzdCBsXkbgACED+woQg8Si9VeqY
+lODCn7lmF6NhnAEYgAAA/NMIAAAAAAD//2JgjLZgVGBg5Pv/Tvpc8hwGBjYGJADjHDrAwPzAjv/H
+/Wf3PzCwtzcwHmBgYGcwbZz8wHaCAQMDOwMDQ8MCBgYOC3W7mp+f0w+wHOYxO3OG+e376hsMZjk3
+AAAAAP//YmCMY2A4wMAIN5e5gQETPD6AZisDAwMDgzSDAAPjByiHcQMDAwMDg1nOze1lByRu5/47
+c4859311AYNZzg0AAAAA//9iYGDBYihOIIMuwIjGL39/fwffA8b//xv/P2BPtzzHwCBjUQAAAAD/
+/yLFBrIBAAAA//9i1HhcwdhizX7u8NZNzyLbvT97bfrMf/QHI8evOwcSqGUJAAAA//9iYBB81iSw
+pEE170Qrg5MIYydHqwdDQRMrAwcVrQAAAAD//2J4x7j9AAMDn8Q/BgYLBoaiAwwMjPdvMDBYM1Tv
+oJodAAAAAP//Yqo/83+dxePWlxl3npsel9lvLfPcqlE9725C+acfVLMEAAAA//9i+s9gwCoaaGMR
+evta/58PTEWzr21hufPjA8N+qlnBwAAAAAD//2JiWLci5v1+HmFXDqcnULE/MxgYGBj+f6CaJQAA
+AAD//2Ji2FrkY3iYpYC5qDeGgeEMAwPDvwQBBoYvcTwOVLMEAAAA//9isDBgkP///0EOg9z35v//
+Gc/eeW7BwPj5+QGZhANUswMAAAD//2JgqGBgYGBgqEMXlvhMPUsAAAAA//8iYDd1AAAAAP//AwDR
+w7IkEbzhVQAAAABJRU5ErkJggg==
+"""
+ )
+ )
+ ),
+ )
+ return f
+
+
def load_default(size: float | None = None) -> FreeTypeFont | ImageFont:
"""If FreeType support is available, load a version of Aileron Regular,
https://dotcolon.net/font/aileron, with a more limited character set.
@@ -902,9 +1104,8 @@ def load_default(size: float | None = None) -> FreeTypeFont | ImageFont:
:return: A font object.
"""
- f: FreeTypeFont | ImageFont
- if core.__class__.__name__ == "module" or size is not None:
- f = truetype(
+ if isinstance(core, ModuleType) or size is not None:
+ return truetype(
BytesIO(
base64.b64decode(
b"""
@@ -1134,137 +1335,4 @@ AAAAAAQAAAADa3tfFAAAAANAan9kAAAAA4QodoQ==
10 if size is None else size,
layout_engine=Layout.BASIC,
)
- else:
- f = ImageFont()
- f._load_pilfont_data(
- # courB08
- BytesIO(
- base64.b64decode(
- b"""
-UElMZm9udAo7Ozs7OzsxMDsKREFUQQoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAAA//8AAQAAAAAAAAABAAEA
-BgAAAAH/+gADAAAAAQAAAAMABgAGAAAAAf/6AAT//QADAAAABgADAAYAAAAA//kABQABAAYAAAAL
-AAgABgAAAAD/+AAFAAEACwAAABAACQAGAAAAAP/5AAUAAAAQAAAAFQAHAAYAAP////oABQAAABUA
-AAAbAAYABgAAAAH/+QAE//wAGwAAAB4AAwAGAAAAAf/5AAQAAQAeAAAAIQAIAAYAAAAB//kABAAB
-ACEAAAAkAAgABgAAAAD/+QAE//0AJAAAACgABAAGAAAAAP/6AAX//wAoAAAALQAFAAYAAAAB//8A
-BAACAC0AAAAwAAMABgAAAAD//AAF//0AMAAAADUAAQAGAAAAAf//AAMAAAA1AAAANwABAAYAAAAB
-//kABQABADcAAAA7AAgABgAAAAD/+QAFAAAAOwAAAEAABwAGAAAAAP/5AAYAAABAAAAARgAHAAYA
-AAAA//kABQAAAEYAAABLAAcABgAAAAD/+QAFAAAASwAAAFAABwAGAAAAAP/5AAYAAABQAAAAVgAH
-AAYAAAAA//kABQAAAFYAAABbAAcABgAAAAD/+QAFAAAAWwAAAGAABwAGAAAAAP/5AAUAAABgAAAA
-ZQAHAAYAAAAA//kABQAAAGUAAABqAAcABgAAAAD/+QAFAAAAagAAAG8ABwAGAAAAAf/8AAMAAABv
-AAAAcQAEAAYAAAAA//wAAwACAHEAAAB0AAYABgAAAAD/+gAE//8AdAAAAHgABQAGAAAAAP/7AAT/
-/gB4AAAAfAADAAYAAAAB//oABf//AHwAAACAAAUABgAAAAD/+gAFAAAAgAAAAIUABgAGAAAAAP/5
-AAYAAQCFAAAAiwAIAAYAAP////oABgAAAIsAAACSAAYABgAA////+gAFAAAAkgAAAJgABgAGAAAA
-AP/6AAUAAACYAAAAnQAGAAYAAP////oABQAAAJ0AAACjAAYABgAA////+gAFAAAAowAAAKkABgAG
-AAD////6AAUAAACpAAAArwAGAAYAAAAA//oABQAAAK8AAAC0AAYABgAA////+gAGAAAAtAAAALsA
-BgAGAAAAAP/6AAQAAAC7AAAAvwAGAAYAAP////oABQAAAL8AAADFAAYABgAA////+gAGAAAAxQAA
-AMwABgAGAAD////6AAUAAADMAAAA0gAGAAYAAP////oABQAAANIAAADYAAYABgAA////+gAGAAAA
-2AAAAN8ABgAGAAAAAP/6AAUAAADfAAAA5AAGAAYAAP////oABQAAAOQAAADqAAYABgAAAAD/+gAF
-AAEA6gAAAO8ABwAGAAD////6AAYAAADvAAAA9gAGAAYAAAAA//oABQAAAPYAAAD7AAYABgAA////
-+gAFAAAA+wAAAQEABgAGAAD////6AAYAAAEBAAABCAAGAAYAAP////oABgAAAQgAAAEPAAYABgAA
-////+gAGAAABDwAAARYABgAGAAAAAP/6AAYAAAEWAAABHAAGAAYAAP////oABgAAARwAAAEjAAYA
-BgAAAAD/+gAFAAABIwAAASgABgAGAAAAAf/5AAQAAQEoAAABKwAIAAYAAAAA//kABAABASsAAAEv
-AAgABgAAAAH/+QAEAAEBLwAAATIACAAGAAAAAP/5AAX//AEyAAABNwADAAYAAAAAAAEABgACATcA
-AAE9AAEABgAAAAH/+QAE//wBPQAAAUAAAwAGAAAAAP/7AAYAAAFAAAABRgAFAAYAAP////kABQAA
-AUYAAAFMAAcABgAAAAD/+wAFAAABTAAAAVEABQAGAAAAAP/5AAYAAAFRAAABVwAHAAYAAAAA//sA
-BQAAAVcAAAFcAAUABgAAAAD/+QAFAAABXAAAAWEABwAGAAAAAP/7AAYAAgFhAAABZwAHAAYAAP//
-//kABQAAAWcAAAFtAAcABgAAAAD/+QAGAAABbQAAAXMABwAGAAAAAP/5AAQAAgFzAAABdwAJAAYA
-AP////kABgAAAXcAAAF+AAcABgAAAAD/+QAGAAABfgAAAYQABwAGAAD////7AAUAAAGEAAABigAF
-AAYAAP////sABQAAAYoAAAGQAAUABgAAAAD/+wAFAAABkAAAAZUABQAGAAD////7AAUAAgGVAAAB
-mwAHAAYAAAAA//sABgACAZsAAAGhAAcABgAAAAD/+wAGAAABoQAAAacABQAGAAAAAP/7AAYAAAGn
-AAABrQAFAAYAAAAA//kABgAAAa0AAAGzAAcABgAA////+wAGAAABswAAAboABQAGAAD////7AAUA
-AAG6AAABwAAFAAYAAP////sABgAAAcAAAAHHAAUABgAAAAD/+wAGAAABxwAAAc0ABQAGAAD////7
-AAYAAgHNAAAB1AAHAAYAAAAA//sABQAAAdQAAAHZAAUABgAAAAH/+QAFAAEB2QAAAd0ACAAGAAAA
-Av/6AAMAAQHdAAAB3gAHAAYAAAAA//kABAABAd4AAAHiAAgABgAAAAD/+wAF//0B4gAAAecAAgAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAAB
-//sAAwACAecAAAHpAAcABgAAAAD/+QAFAAEB6QAAAe4ACAAGAAAAAP/5AAYAAAHuAAAB9AAHAAYA
-AAAA//oABf//AfQAAAH5AAUABgAAAAD/+QAGAAAB+QAAAf8ABwAGAAAAAv/5AAMAAgH/AAACAAAJ
-AAYAAAAA//kABQABAgAAAAIFAAgABgAAAAH/+gAE//sCBQAAAggAAQAGAAAAAP/5AAYAAAIIAAAC
-DgAHAAYAAAAB//kABf/+Ag4AAAISAAUABgAA////+wAGAAACEgAAAhkABQAGAAAAAP/7AAX//gIZ
-AAACHgADAAYAAAAA//wABf/9Ah4AAAIjAAEABgAAAAD/+QAHAAACIwAAAioABwAGAAAAAP/6AAT/
-+wIqAAACLgABAAYAAAAA//kABP/8Ai4AAAIyAAMABgAAAAD/+gAFAAACMgAAAjcABgAGAAAAAf/5
-AAT//QI3AAACOgAEAAYAAAAB//kABP/9AjoAAAI9AAQABgAAAAL/+QAE//sCPQAAAj8AAgAGAAD/
-///7AAYAAgI/AAACRgAHAAYAAAAA//kABgABAkYAAAJMAAgABgAAAAH//AAD//0CTAAAAk4AAQAG
-AAAAAf//AAQAAgJOAAACUQADAAYAAAAB//kABP/9AlEAAAJUAAQABgAAAAH/+QAF//4CVAAAAlgA
-BQAGAAD////7AAYAAAJYAAACXwAFAAYAAP////kABgAAAl8AAAJmAAcABgAA////+QAGAAACZgAA
-Am0ABwAGAAD////5AAYAAAJtAAACdAAHAAYAAAAA//sABQACAnQAAAJ5AAcABgAA////9wAGAAAC
-eQAAAoAACQAGAAD////3AAYAAAKAAAAChwAJAAYAAP////cABgAAAocAAAKOAAkABgAA////9wAG
-AAACjgAAApUACQAGAAD////4AAYAAAKVAAACnAAIAAYAAP////cABgAAApwAAAKjAAkABgAA////
-+gAGAAACowAAAqoABgAGAAAAAP/6AAUAAgKqAAACrwAIAAYAAP////cABQAAAq8AAAK1AAkABgAA
-////9wAFAAACtQAAArsACQAGAAD////3AAUAAAK7AAACwQAJAAYAAP////gABQAAAsEAAALHAAgA
-BgAAAAD/9wAEAAACxwAAAssACQAGAAAAAP/3AAQAAALLAAACzwAJAAYAAAAA//cABAAAAs8AAALT
-AAkABgAAAAD/+AAEAAAC0wAAAtcACAAGAAD////6AAUAAALXAAAC3QAGAAYAAP////cABgAAAt0A
-AALkAAkABgAAAAD/9wAFAAAC5AAAAukACQAGAAAAAP/3AAUAAALpAAAC7gAJAAYAAAAA//cABQAA
-Au4AAALzAAkABgAAAAD/9wAFAAAC8wAAAvgACQAGAAAAAP/4AAUAAAL4AAAC/QAIAAYAAAAA//oA
-Bf//Av0AAAMCAAUABgAA////+gAGAAADAgAAAwkABgAGAAD////3AAYAAAMJAAADEAAJAAYAAP//
-//cABgAAAxAAAAMXAAkABgAA////9wAGAAADFwAAAx4ACQAGAAD////4AAYAAAAAAAoABwASAAYA
-AP////cABgAAAAcACgAOABMABgAA////+gAFAAAADgAKABQAEAAGAAD////6AAYAAAAUAAoAGwAQ
-AAYAAAAA//gABgAAABsACgAhABIABgAAAAD/+AAGAAAAIQAKACcAEgAGAAAAAP/4AAYAAAAnAAoA
-LQASAAYAAAAA//gABgAAAC0ACgAzABIABgAAAAD/+QAGAAAAMwAKADkAEQAGAAAAAP/3AAYAAAA5
-AAoAPwATAAYAAP////sABQAAAD8ACgBFAA8ABgAAAAD/+wAFAAIARQAKAEoAEQAGAAAAAP/4AAUA
-AABKAAoATwASAAYAAAAA//gABQAAAE8ACgBUABIABgAAAAD/+AAFAAAAVAAKAFkAEgAGAAAAAP/5
-AAUAAABZAAoAXgARAAYAAAAA//gABgAAAF4ACgBkABIABgAAAAD/+AAGAAAAZAAKAGoAEgAGAAAA
-AP/4AAYAAABqAAoAcAASAAYAAAAA//kABgAAAHAACgB2ABEABgAAAAD/+AAFAAAAdgAKAHsAEgAG
-AAD////4AAYAAAB7AAoAggASAAYAAAAA//gABQAAAIIACgCHABIABgAAAAD/+AAFAAAAhwAKAIwA
-EgAGAAAAAP/4AAUAAACMAAoAkQASAAYAAAAA//gABQAAAJEACgCWABIABgAAAAD/+QAFAAAAlgAK
-AJsAEQAGAAAAAP/6AAX//wCbAAoAoAAPAAYAAAAA//oABQABAKAACgClABEABgAA////+AAGAAAA
-pQAKAKwAEgAGAAD////4AAYAAACsAAoAswASAAYAAP////gABgAAALMACgC6ABIABgAA////+QAG
-AAAAugAKAMEAEQAGAAD////4AAYAAgDBAAoAyAAUAAYAAP////kABQACAMgACgDOABMABgAA////
-+QAGAAIAzgAKANUAEw==
-"""
- )
- ),
- Image.open(
- BytesIO(
- base64.b64decode(
- b"""
-iVBORw0KGgoAAAANSUhEUgAAAx4AAAAUAQAAAAArMtZoAAAEwElEQVR4nABlAJr/AHVE4czCI/4u
-Mc4b7vuds/xzjz5/3/7u/n9vMe7vnfH/9++vPn/xyf5zhxzjt8GHw8+2d83u8x27199/nxuQ6Od9
-M43/5z2I+9n9ZtmDBwMQECDRQw/eQIQohJXxpBCNVE6QCCAAAAD//wBlAJr/AgALyj1t/wINwq0g
-LeNZUworuN1cjTPIzrTX6ofHWeo3v336qPzfEwRmBnHTtf95/fglZK5N0PDgfRTslpGBvz7LFc4F
-IUXBWQGjQ5MGCx34EDFPwXiY4YbYxavpnhHFrk14CDAAAAD//wBlAJr/AgKqRooH2gAgPeggvUAA
-Bu2WfgPoAwzRAABAAAAAAACQgLz/3Uv4Gv+gX7BJgDeeGP6AAAD1NMDzKHD7ANWr3loYbxsAD791
-NAADfcoIDyP44K/jv4Y63/Z+t98Ovt+ub4T48LAAAAD//wBlAJr/AuplMlADJAAAAGuAphWpqhMx
-in0A/fRvAYBABPgBwBUgABBQ/sYAyv9g0bCHgOLoGAAAAAAAREAAwI7nr0ArYpow7aX8//9LaP/9
-SjdavWA8ePHeBIKB//81/83ndznOaXx379wAAAD//wBlAJr/AqDxW+D3AABAAbUh/QMnbQag/gAY
-AYDAAACgtgD/gOqAAAB5IA/8AAAk+n9w0AAA8AAAmFRJuPo27ciC0cD5oeW4E7KA/wD3ECMAn2tt
-y8PgwH8AfAxFzC0JzeAMtratAsC/ffwAAAD//wBlAJr/BGKAyCAA4AAAAvgeYTAwHd1kmQF5chkG
-ABoMIHcL5xVpTfQbUqzlAAAErwAQBgAAEOClA5D9il08AEh/tUzdCBsXkbgACED+woQg8Si9VeqY
-lODCn7lmF6NhnAEYgAAA/NMIAAAAAAD//2JgjLZgVGBg5Pv/Tvpc8hwGBjYGJADjHDrAwPzAjv/H
-/Wf3PzCwtzcwHmBgYGcwbZz8wHaCAQMDOwMDQ8MCBgYOC3W7mp+f0w+wHOYxO3OG+e376hsMZjk3
-AAAAAP//YmCMY2A4wMAIN5e5gQETPD6AZisDAwMDgzSDAAPjByiHcQMDAwMDg1nOze1lByRu5/47
-c4859311AYNZzg0AAAAA//9iYGDBYihOIIMuwIjGL39/fwffA8b//xv/P2BPtzzHwCBjUQAAAAD/
-/yLFBrIBAAAA//9i1HhcwdhizX7u8NZNzyLbvT97bfrMf/QHI8evOwcSqGUJAAAA//9iYBB81iSw
-pEE170Qrg5MIYydHqwdDQRMrAwcVrQAAAAD//2J4x7j9AAMDn8Q/BgYLBoaiAwwMjPdvMDBYM1Tv
-oJodAAAAAP//Yqo/83+dxePWlxl3npsel9lvLfPcqlE9725C+acfVLMEAAAA//9i+s9gwCoaaGMR
-evta/58PTEWzr21hufPjA8N+qlnBwAAAAAD//2JiWLci5v1+HmFXDqcnULE/MxgYGBj+f6CaJQAA
-AAD//2Ji2FrkY3iYpYC5qDeGgeEMAwPDvwQBBoYvcTwOVLMEAAAA//9isDBgkP///0EOg9z35v//
-Gc/eeW7BwPj5+QGZhANUswMAAAD//2JgqGBgYGBgqEMXlvhMPUsAAAAA//8iYDd1AAAAAP//AwDR
-w7IkEbzhVQAAAABJRU5ErkJggg==
-"""
- )
- )
- ),
- )
- return f
+ return load_default_imagefont()
diff --git a/src/PIL/ImageGrab.py b/src/PIL/ImageGrab.py
index 3f3be706d..e27ca7e50 100644
--- a/src/PIL/ImageGrab.py
+++ b/src/PIL/ImageGrab.py
@@ -26,7 +26,13 @@ import tempfile
from . import Image
-def grab(bbox=None, include_layered_windows=False, all_screens=False, xdisplay=None):
+def grab(
+ bbox: tuple[int, int, int, int] | None = None,
+ include_layered_windows: bool = False,
+ all_screens: bool = False,
+ xdisplay: str | None = None,
+) -> Image.Image:
+ im: Image.Image
if xdisplay is None:
if sys.platform == "darwin":
fh, filepath = tempfile.mkstemp(".png")
@@ -63,14 +69,16 @@ def grab(bbox=None, include_layered_windows=False, all_screens=False, xdisplay=N
left, top, right, bottom = bbox
im = im.crop((left - x0, top - y0, right - x0, bottom - y0))
return im
+ # Cast to Optional[str] needed for Windows and macOS.
+ display_name: str | None = xdisplay
try:
if not Image.core.HAVE_XCB:
msg = "Pillow was built without XCB support"
raise OSError(msg)
- size, data = Image.core.grabscreen_x11(xdisplay)
+ size, data = Image.core.grabscreen_x11(display_name)
except OSError:
if (
- xdisplay is None
+ display_name is None
and sys.platform not in ("darwin", "win32")
and shutil.which("gnome-screenshot")
):
@@ -94,7 +102,7 @@ def grab(bbox=None, include_layered_windows=False, all_screens=False, xdisplay=N
return im
-def grabclipboard():
+def grabclipboard() -> Image.Image | list[str] | None:
if sys.platform == "darwin":
fh, filepath = tempfile.mkstemp(".png")
os.close(fh)
diff --git a/src/PIL/ImageMath.py b/src/PIL/ImageMath.py
index 6664434ea..484797f91 100644
--- a/src/PIL/ImageMath.py
+++ b/src/PIL/ImageMath.py
@@ -59,13 +59,12 @@ class _Operand:
if im2 is None:
# unary operation
out = Image.new(mode or im_1.mode, im_1.size, None)
- im_1.load()
try:
op = getattr(_imagingmath, f"{op}_{im_1.mode}")
except AttributeError as e:
msg = f"bad operand type for '{op}'"
raise TypeError(msg) from e
- _imagingmath.unop(op, out.im.id, im_1.im.id)
+ _imagingmath.unop(op, out.getim(), im_1.getim())
else:
# binary operation
im_2 = self.__fixup(im2)
@@ -86,14 +85,12 @@ class _Operand:
if im_2.size != size:
im_2 = im_2.crop((0, 0) + size)
out = Image.new(mode or im_1.mode, im_1.size, None)
- im_1.load()
- im_2.load()
try:
op = getattr(_imagingmath, f"{op}_{im_1.mode}")
except AttributeError as e:
msg = f"bad operand type for '{op}'"
raise TypeError(msg) from e
- _imagingmath.binop(op, out.im.id, im_1.im.id, im_2.im.id)
+ _imagingmath.binop(op, out.getim(), im_1.getim(), im_2.getim())
return _Operand(out)
# unary operators
@@ -176,10 +173,10 @@ class _Operand:
return self.apply("rshift", self, other)
# logical
- def __eq__(self, other):
+ def __eq__(self, other: _Operand | float) -> _Operand: # type: ignore[override]
return self.apply("eq", self, other)
- def __ne__(self, other):
+ def __ne__(self, other: _Operand | float) -> _Operand: # type: ignore[override]
return self.apply("ne", self, other)
def __lt__(self, other: _Operand | float) -> _Operand:
@@ -249,19 +246,26 @@ def lambda_eval(
:py:func:`~PIL.Image.merge` function.
:param expression: A function that receives a dictionary.
- :param options: Values to add to the function's dictionary. You
- can either use a dictionary, or one or more keyword
- arguments.
+ :param options: Values to add to the function's dictionary. Deprecated.
+ You can instead use one or more keyword arguments.
+ :param **kw: Values to add to the function's dictionary.
:return: The expression result. This is usually an image object, but can
also be an integer, a floating point value, or a pixel tuple,
depending on the expression.
"""
+ if options:
+ deprecate(
+ "ImageMath.lambda_eval options",
+ 12,
+ "ImageMath.lambda_eval keyword arguments",
+ )
+
args: dict[str, Any] = ops.copy()
args.update(options)
args.update(kw)
for k, v in args.items():
- if hasattr(v, "im"):
+ if isinstance(v, Image.Image):
args[k] = _Operand(v)
out = expression(args)
@@ -287,14 +291,21 @@ def unsafe_eval(
:py:func:`~PIL.Image.merge` function.
:param expression: A string containing a Python-style expression.
- :param options: Values to add to the evaluation context. You
- can either use a dictionary, or one or more keyword
- arguments.
+ :param options: Values to add to the evaluation context. Deprecated.
+ You can instead use one or more keyword arguments.
+ :param **kw: Values to add to the evaluation context.
:return: The evaluated expression. This is usually an image object, but can
also be an integer, a floating point value, or a pixel tuple,
depending on the expression.
"""
+ if options:
+ deprecate(
+ "ImageMath.unsafe_eval options",
+ 12,
+ "ImageMath.unsafe_eval keyword arguments",
+ )
+
# build execution namespace
args: dict[str, Any] = ops.copy()
for k in list(options.keys()) + list(kw.keys()):
@@ -305,7 +316,7 @@ def unsafe_eval(
args.update(options)
args.update(kw)
for k, v in args.items():
- if hasattr(v, "im"):
+ if isinstance(v, Image.Image):
args[k] = _Operand(v)
compiled_code = compile(expression, "", "eval")
diff --git a/src/PIL/ImageMorph.py b/src/PIL/ImageMorph.py
index 6a43983d3..f0a066b5b 100644
--- a/src/PIL/ImageMorph.py
+++ b/src/PIL/ImageMorph.py
@@ -213,7 +213,7 @@ class MorphOp:
msg = "Image mode must be L"
raise ValueError(msg)
outimage = Image.new(image.mode, image.size, None)
- count = _imagingmorph.apply(bytes(self.lut), image.im.id, outimage.im.id)
+ count = _imagingmorph.apply(bytes(self.lut), image.getim(), outimage.getim())
return count, outimage
def match(self, image: Image.Image) -> list[tuple[int, int]]:
@@ -229,7 +229,7 @@ class MorphOp:
if image.mode != "L":
msg = "Image mode must be L"
raise ValueError(msg)
- return _imagingmorph.match(bytes(self.lut), image.im.id)
+ return _imagingmorph.match(bytes(self.lut), image.getim())
def get_on_pixels(self, image: Image.Image) -> list[tuple[int, int]]:
"""Get a list of all turned on pixels in a binary image
@@ -240,7 +240,7 @@ class MorphOp:
if image.mode != "L":
msg = "Image mode must be L"
raise ValueError(msg)
- return _imagingmorph.get_on_pixels(image.im.id)
+ return _imagingmorph.get_on_pixels(image.getim())
def load_lut(self, filename: str) -> None:
"""Load an operator from an mrl file"""
diff --git a/src/PIL/ImageOps.py b/src/PIL/ImageOps.py
index 33db8fa50..bb29cc0d3 100644
--- a/src/PIL/ImageOps.py
+++ b/src/PIL/ImageOps.py
@@ -21,7 +21,8 @@ from __future__ import annotations
import functools
import operator
import re
-from typing import Protocol, Sequence, cast
+from collections.abc import Sequence
+from typing import Protocol, cast
from . import ExifTags, Image, ImagePalette
@@ -361,7 +362,9 @@ def pad(
else:
out = Image.new(image.mode, size, color)
if resized.palette:
- out.putpalette(resized.getpalette())
+ palette = resized.getpalette()
+ if palette is not None:
+ out.putpalette(palette)
if resized.width != size[0]:
x = round((size[0] - resized.width) * max(0, min(centering[0], 1)))
out.paste(resized, (x, 0))
@@ -497,7 +500,7 @@ def expand(
color = _color(fill, image.mode)
if image.palette:
palette = ImagePalette.ImagePalette(palette=image.getpalette())
- if isinstance(color, tuple):
+ if isinstance(color, tuple) and (len(color) == 3 or len(color) == 4):
color = palette.getcolor(color)
else:
palette = None
@@ -695,11 +698,11 @@ def exif_transpose(image: Image.Image, *, in_place: bool = False) -> Image.Image
8: Image.Transpose.ROTATE_90,
}.get(orientation)
if method is not None:
- transposed_image = image.transpose(method)
if in_place:
- image.im = transposed_image.im
- image.pyaccess = None
- image._size = transposed_image._size
+ image.im = image.im.transpose(method)
+ image._size = image.im.size
+ else:
+ transposed_image = image.transpose(method)
exif_image = image if in_place else transposed_image
exif = exif_image.getexif()
@@ -709,14 +712,18 @@ def exif_transpose(image: Image.Image, *, in_place: bool = False) -> Image.Image
exif_image.info["exif"] = exif.tobytes()
elif "Raw profile type exif" in exif_image.info:
exif_image.info["Raw profile type exif"] = exif.tobytes().hex()
- elif "XML:com.adobe.xmp" in exif_image.info:
- for pattern in (
- r'tiff:Orientation="([0-9])"',
- r"([0-9])",
- ):
- exif_image.info["XML:com.adobe.xmp"] = re.sub(
- pattern, "", exif_image.info["XML:com.adobe.xmp"]
- )
+ for key in ("XML:com.adobe.xmp", "xmp"):
+ if key in exif_image.info:
+ for pattern in (
+ r'tiff:Orientation="([0-9])"',
+ r"([0-9])",
+ ):
+ value = exif_image.info[key]
+ exif_image.info[key] = (
+ re.sub(pattern, "", value)
+ if isinstance(value, str)
+ else re.sub(pattern.encode(), b"", value)
+ )
if not in_place:
return transposed_image
elif not in_place:
diff --git a/src/PIL/ImagePalette.py b/src/PIL/ImagePalette.py
index 057ccd1d7..183f85526 100644
--- a/src/PIL/ImagePalette.py
+++ b/src/PIL/ImagePalette.py
@@ -18,10 +18,14 @@
from __future__ import annotations
import array
-from typing import IO, Sequence
+from collections.abc import Sequence
+from typing import IO, TYPE_CHECKING
from . import GimpGradientFile, GimpPaletteFile, ImageColor, PaletteFile
+if TYPE_CHECKING:
+ from . import Image
+
class ImagePalette:
"""
@@ -35,23 +39,27 @@ class ImagePalette:
Defaults to an empty palette.
"""
- def __init__(self, mode: str = "RGB", palette: Sequence[int] | None = None) -> None:
+ def __init__(
+ self,
+ mode: str = "RGB",
+ palette: Sequence[int] | bytes | bytearray | None = None,
+ ) -> None:
self.mode = mode
- self.rawmode = None # if set, palette contains raw data
+ self.rawmode: str | None = None # if set, palette contains raw data
self.palette = palette or bytearray()
self.dirty: int | None = None
@property
- def palette(self):
+ def palette(self) -> Sequence[int] | bytes | bytearray:
return self._palette
@palette.setter
- def palette(self, palette):
- self._colors = None
+ def palette(self, palette: Sequence[int] | bytes | bytearray) -> None:
+ self._colors: dict[tuple[int, ...], int] | None = None
self._palette = palette
@property
- def colors(self):
+ def colors(self) -> dict[tuple[int, ...], int]:
if self._colors is None:
mode_len = len(self.mode)
self._colors = {}
@@ -63,7 +71,7 @@ class ImagePalette:
return self._colors
@colors.setter
- def colors(self, colors):
+ def colors(self, colors: dict[tuple[int, ...], int]) -> None:
self._colors = colors
def copy(self) -> ImagePalette:
@@ -77,7 +85,7 @@ class ImagePalette:
return new
- def getdata(self) -> tuple[str, bytes]:
+ def getdata(self) -> tuple[str, Sequence[int] | bytes | bytearray]:
"""
Get palette contents in format suitable for the low-level
``im.putpalette`` primitive.
@@ -104,11 +112,13 @@ class ImagePalette:
# Declare tostring as an alias for tobytes
tostring = tobytes
- def _new_color_index(self, image=None, e=None):
+ def _new_color_index(
+ self, image: Image.Image | None = None, e: Exception | None = None
+ ) -> int:
if not isinstance(self.palette, bytearray):
self._palette = bytearray(self.palette)
index = len(self.palette) // 3
- special_colors = ()
+ special_colors: tuple[int | tuple[int, ...] | None, ...] = ()
if image:
special_colors = (
image.info.get("background"),
@@ -128,7 +138,11 @@ class ImagePalette:
raise ValueError(msg) from e
return index
- def getcolor(self, color, image=None) -> int:
+ def getcolor(
+ self,
+ color: tuple[int, ...],
+ image: Image.Image | None = None,
+ ) -> int:
"""Given an rgb tuple, allocate palette entry.
.. warning:: This method is experimental.
@@ -151,19 +165,20 @@ class ImagePalette:
except KeyError as e:
# allocate new color slot
index = self._new_color_index(image, e)
+ assert isinstance(self._palette, bytearray)
self.colors[color] = index
if index * 3 < len(self.palette):
self._palette = (
- self.palette[: index * 3]
+ self._palette[: index * 3]
+ bytes(color)
- + self.palette[index * 3 + 3 :]
+ + self._palette[index * 3 + 3 :]
)
else:
self._palette += bytes(color)
self.dirty = 1
return index
else:
- msg = f"unknown color specifier: {repr(color)}"
+ msg = f"unknown color specifier: {repr(color)}" # type: ignore[unreachable]
raise ValueError(msg)
def save(self, fp: str | IO[str]) -> None:
@@ -193,7 +208,7 @@ class ImagePalette:
# Internal
-def raw(rawmode, data) -> ImagePalette:
+def raw(rawmode: str, data: Sequence[int] | bytes | bytearray) -> ImagePalette:
palette = ImagePalette()
palette.rawmode = rawmode
palette.palette = data
@@ -205,9 +220,9 @@ def raw(rawmode, data) -> ImagePalette:
# Factories
-def make_linear_lut(black, white):
+def make_linear_lut(black: int, white: float) -> list[int]:
if black == 0:
- return [white * i // 255 for i in range(256)]
+ return [int(white * i // 255) for i in range(256)]
msg = "unavailable when black is non-zero"
raise NotImplementedError(msg) # FIXME
@@ -240,15 +255,22 @@ def wedge(mode: str = "RGB") -> ImagePalette:
return ImagePalette(mode, [i // len(mode) for i in palette])
-def load(filename):
+def load(filename: str) -> tuple[bytes, str]:
# FIXME: supports GIMP gradients only
with open(filename, "rb") as fp:
- for paletteHandler in [
+ paletteHandlers: list[
+ type[
+ GimpPaletteFile.GimpPaletteFile
+ | GimpGradientFile.GimpGradientFile
+ | PaletteFile.PaletteFile
+ ]
+ ] = [
GimpPaletteFile.GimpPaletteFile,
GimpGradientFile.GimpGradientFile,
PaletteFile.PaletteFile,
- ]:
+ ]
+ for paletteHandler in paletteHandlers:
try:
fp.seek(0)
lut = paletteHandler(fp).getpalette()
diff --git a/src/PIL/ImageQt.py b/src/PIL/ImageQt.py
index 293ba4941..2cc40f855 100644
--- a/src/PIL/ImageQt.py
+++ b/src/PIL/ImageQt.py
@@ -19,11 +19,23 @@ from __future__ import annotations
import sys
from io import BytesIO
-from typing import Callable
+from typing import TYPE_CHECKING, Any, Callable, Union
from . import Image
from ._util import is_path
+if TYPE_CHECKING:
+ import PyQt6
+ import PySide6
+
+ from . import ImageFile
+
+ QBuffer: type
+ QByteArray = Union[PyQt6.QtCore.QByteArray, PySide6.QtCore.QByteArray]
+ QIODevice = Union[PyQt6.QtCore.QIODevice, PySide6.QtCore.QIODevice]
+ QImage = Union[PyQt6.QtGui.QImage, PySide6.QtGui.QImage]
+ QPixmap = Union[PyQt6.QtGui.QPixmap, PySide6.QtGui.QPixmap]
+
qt_version: str | None
qt_versions = [
["6", "PyQt6"],
@@ -34,10 +46,6 @@ qt_versions = [
qt_versions.sort(key=lambda version: version[1] in sys.modules, reverse=True)
for version, qt_module in qt_versions:
try:
- QBuffer: type
- QIODevice: type
- QImage: type
- QPixmap: type
qRgba: Callable[[int, int, int, int], int]
if qt_module == "PyQt6":
from PyQt6.QtCore import QBuffer, QIODevice
@@ -55,26 +63,27 @@ else:
qt_version = None
-def rgb(r, g, b, a=255):
+def rgb(r: int, g: int, b: int, a: int = 255) -> int:
"""(Internal) Turns an RGB color into a Qt compatible color integer."""
# use qRgb to pack the colors, and then turn the resulting long
# into a negative integer with the same bitpattern.
return qRgba(r, g, b, a) & 0xFFFFFFFF
-def fromqimage(im):
+def fromqimage(im: QImage | QPixmap) -> ImageFile.ImageFile:
"""
:param im: QImage or PIL ImageQt object
"""
buffer = QBuffer()
+ qt_openmode: object
if qt_version == "6":
try:
- qt_openmode = QIODevice.OpenModeFlag
+ qt_openmode = getattr(QIODevice, "OpenModeFlag")
except AttributeError:
- qt_openmode = QIODevice.OpenMode
+ qt_openmode = getattr(QIODevice, "OpenMode")
else:
qt_openmode = QIODevice
- buffer.open(qt_openmode.ReadWrite)
+ buffer.open(getattr(qt_openmode, "ReadWrite"))
# preserve alpha channel with png
# otherwise ppm is more friendly with Image.open
if im.hasAlphaChannel():
@@ -90,11 +99,11 @@ def fromqimage(im):
return Image.open(b)
-def fromqpixmap(im):
+def fromqpixmap(im: QPixmap) -> ImageFile.ImageFile:
return fromqimage(im)
-def align8to32(bytes, width, mode):
+def align8to32(bytes: bytes, width: int, mode: str) -> bytes:
"""
converts each scanline of data from 8 bit to 32 bit aligned
"""
@@ -120,7 +129,7 @@ def align8to32(bytes, width, mode):
return b"".join(new_data)
-def _toqclass_helper(im):
+def _toqclass_helper(im: Image.Image | str | QByteArray) -> dict[str, Any]:
data = None
colortable = None
exclusive_fp = False
@@ -132,30 +141,32 @@ def _toqclass_helper(im):
if is_path(im):
im = Image.open(im)
exclusive_fp = True
+ assert isinstance(im, Image.Image)
- qt_format = QImage.Format if qt_version == "6" else QImage
+ qt_format = getattr(QImage, "Format") if qt_version == "6" else QImage
if im.mode == "1":
- format = qt_format.Format_Mono
+ format = getattr(qt_format, "Format_Mono")
elif im.mode == "L":
- format = qt_format.Format_Indexed8
+ format = getattr(qt_format, "Format_Indexed8")
colortable = [rgb(i, i, i) for i in range(256)]
elif im.mode == "P":
- format = qt_format.Format_Indexed8
+ format = getattr(qt_format, "Format_Indexed8")
palette = im.getpalette()
+ assert palette is not None
colortable = [rgb(*palette[i : i + 3]) for i in range(0, len(palette), 3)]
elif im.mode == "RGB":
# Populate the 4th channel with 255
im = im.convert("RGBA")
data = im.tobytes("raw", "BGRA")
- format = qt_format.Format_RGB32
+ format = getattr(qt_format, "Format_RGB32")
elif im.mode == "RGBA":
data = im.tobytes("raw", "BGRA")
- format = qt_format.Format_ARGB32
- elif im.mode == "I;16" and hasattr(qt_format, "Format_Grayscale16"): # Qt 5.13+
+ format = getattr(qt_format, "Format_ARGB32")
+ elif im.mode == "I;16":
im = im.point(lambda i: i * 256)
- format = qt_format.Format_Grayscale16
+ format = getattr(qt_format, "Format_Grayscale16")
else:
if exclusive_fp:
im.close()
@@ -171,8 +182,8 @@ def _toqclass_helper(im):
if qt_is_installed:
- class ImageQt(QImage):
- def __init__(self, im):
+ class ImageQt(QImage): # type: ignore[misc]
+ def __init__(self, im: Image.Image | str | QByteArray) -> None:
"""
An PIL image wrapper for Qt. This is a subclass of PyQt's QImage
class.
@@ -196,10 +207,13 @@ if qt_is_installed:
self.setColorTable(im_data["colortable"])
-def toqimage(im):
+def toqimage(im: Image.Image | str | QByteArray) -> ImageQt:
return ImageQt(im)
-def toqpixmap(im):
+def toqpixmap(im: Image.Image | str | QByteArray) -> QPixmap:
qimage = toqimage(im)
- return QPixmap.fromImage(qimage)
+ pixmap = getattr(QPixmap, "fromImage")(qimage)
+ if qt_version == "6":
+ pixmap.detach()
+ return pixmap
diff --git a/src/PIL/ImageSequence.py b/src/PIL/ImageSequence.py
index 2c1850276..a6fc340d5 100644
--- a/src/PIL/ImageSequence.py
+++ b/src/PIL/ImageSequence.py
@@ -33,7 +33,7 @@ class Iterator:
:param im: An image object.
"""
- def __init__(self, im: Image.Image):
+ def __init__(self, im: Image.Image) -> None:
if not hasattr(im, "seek"):
msg = "im must have seek method"
raise AttributeError(msg)
diff --git a/src/PIL/ImageShow.py b/src/PIL/ImageShow.py
index f60b1e11e..d62893d9c 100644
--- a/src/PIL/ImageShow.py
+++ b/src/PIL/ImageShow.py
@@ -26,7 +26,7 @@ from . import Image
_viewers = []
-def register(viewer, order: int = 1) -> None:
+def register(viewer: type[Viewer] | Viewer, order: int = 1) -> None:
"""
The :py:func:`register` function is used to register additional viewers::
@@ -40,11 +40,8 @@ def register(viewer, order: int = 1) -> None:
Zero or a negative integer to prepend this viewer to the list,
a positive integer to append it.
"""
- try:
- if issubclass(viewer, Viewer):
- viewer = viewer()
- except TypeError:
- pass # raised if viewer wasn't a class
+ if isinstance(viewer, type) and issubclass(viewer, Viewer):
+ viewer = viewer()
if order > 0:
_viewers.append(viewer)
else:
@@ -118,6 +115,8 @@ class Viewer:
"""
Display given file.
"""
+ if not os.path.exists(path):
+ raise FileNotFoundError
os.system(self.get_command(path, **options)) # nosec
return 1
@@ -142,6 +141,8 @@ class WindowsViewer(Viewer):
"""
Display given file.
"""
+ if not os.path.exists(path):
+ raise FileNotFoundError
subprocess.Popen(
self.get_command(path, **options),
shell=True,
@@ -171,6 +172,8 @@ class MacViewer(Viewer):
"""
Display given file.
"""
+ if not os.path.exists(path):
+ raise FileNotFoundError
subprocess.call(["open", "-a", "Preview.app", path])
executable = sys.executable or shutil.which("python3")
if executable:
@@ -215,6 +218,8 @@ class XDGViewer(UnixViewer):
"""
Display given file.
"""
+ if not os.path.exists(path):
+ raise FileNotFoundError
subprocess.Popen(["xdg-open", path])
return 1
@@ -237,6 +242,8 @@ class DisplayViewer(UnixViewer):
"""
Display given file.
"""
+ if not os.path.exists(path):
+ raise FileNotFoundError
args = ["display"]
title = options.get("title")
if title:
@@ -259,6 +266,8 @@ class GmDisplayViewer(UnixViewer):
"""
Display given file.
"""
+ if not os.path.exists(path):
+ raise FileNotFoundError
subprocess.Popen(["gm", "display", path])
return 1
@@ -275,6 +284,8 @@ class EogViewer(UnixViewer):
"""
Display given file.
"""
+ if not os.path.exists(path):
+ raise FileNotFoundError
subprocess.Popen(["eog", "-n", path])
return 1
@@ -299,6 +310,8 @@ class XVViewer(UnixViewer):
"""
Display given file.
"""
+ if not os.path.exists(path):
+ raise FileNotFoundError
args = ["xv"]
title = options.get("title")
if title:
diff --git a/src/PIL/ImageTk.py b/src/PIL/ImageTk.py
index 90defdbbc..bf29fdba5 100644
--- a/src/PIL/ImageTk.py
+++ b/src/PIL/ImageTk.py
@@ -28,48 +28,41 @@ from __future__ import annotations
import tkinter
from io import BytesIO
+from typing import TYPE_CHECKING, Any, cast
-from . import Image
+from . import Image, ImageFile
+
+if TYPE_CHECKING:
+ from ._typing import CapsuleType
# --------------------------------------------------------------------
# Check for Tkinter interface hooks
-_pilbitmap_ok = None
-
-def _pilbitmap_check() -> int:
- global _pilbitmap_ok
- if _pilbitmap_ok is None:
- try:
- im = Image.new("1", (1, 1))
- tkinter.BitmapImage(data=f"PIL:{im.im.id}")
- _pilbitmap_ok = 1
- except tkinter.TclError:
- _pilbitmap_ok = 0
- return _pilbitmap_ok
-
-
-def _get_image_from_kw(kw):
+def _get_image_from_kw(kw: dict[str, Any]) -> ImageFile.ImageFile | None:
source = None
if "file" in kw:
source = kw.pop("file")
elif "data" in kw:
source = BytesIO(kw.pop("data"))
- if source:
- return Image.open(source)
+ if not source:
+ return None
+ return Image.open(source)
-def _pyimagingtkcall(command, photo, id):
+def _pyimagingtkcall(
+ command: str, photo: PhotoImage | tkinter.PhotoImage, ptr: CapsuleType
+) -> None:
tk = photo.tk
try:
- tk.call(command, photo, id)
+ tk.call(command, photo, repr(ptr))
except tkinter.TclError:
# activate Tkinter hook
# may raise an error if it cannot attach to Tkinter
from . import _imagingtk
_imagingtk.tkinit(tk.interpaddr())
- tk.call(command, photo, id)
+ tk.call(command, photo, repr(ptr))
# --------------------------------------------------------------------
@@ -96,27 +89,36 @@ class PhotoImage:
image file).
"""
- def __init__(self, image=None, size=None, **kw):
+ def __init__(
+ self,
+ image: Image.Image | str | None = None,
+ size: tuple[int, int] | None = None,
+ **kw: Any,
+ ) -> None:
# Tk compatibility: file or data
if image is None:
image = _get_image_from_kw(kw)
- if hasattr(image, "mode") and hasattr(image, "size"):
+ if image is None:
+ msg = "Image is required"
+ raise ValueError(msg)
+ elif isinstance(image, str):
+ mode = image
+ image = None
+
+ if size is None:
+ msg = "If first argument is mode, size is required"
+ raise ValueError(msg)
+ else:
# got an image instead of a mode
mode = image.mode
if mode == "P":
# palette mapped data
image.apply_transparency()
image.load()
- try:
- mode = image.palette.mode
- except AttributeError:
- mode = "RGB" # default
+ mode = image.palette.mode if image.palette else "RGB"
size = image.size
kw["width"], kw["height"] = size
- else:
- mode = image
- image = None
if mode not in ["1", "L", "RGB", "RGBA"]:
mode = Image.getmodebase(mode)
@@ -129,7 +131,10 @@ class PhotoImage:
self.paste(image)
def __del__(self) -> None:
- name = self.__photo.name
+ try:
+ name = self.__photo.name
+ except AttributeError:
+ return
self.__photo.name = None
try:
self.__photo.tk.call("image", "delete", name)
@@ -172,15 +177,14 @@ class PhotoImage:
the bitmap image.
"""
# convert to blittable
- im.load()
+ ptr = im.getim()
image = im.im
- if image.isblock() and im.mode == self.__mode:
- block = image
- else:
- block = image.new_block(self.__mode, im.size)
+ if not image.isblock() or im.mode != self.__mode:
+ block = Image.core.new_block(self.__mode, im.size)
image.convert2(block, image) # convert directly between buffers
+ ptr = block.ptr
- _pyimagingtkcall("PyImagingPhoto", self.__photo, block.id)
+ _pyimagingtkcall("PyImagingPhoto", self.__photo, ptr)
# --------------------------------------------------------------------
@@ -201,26 +205,24 @@ class BitmapImage:
:param image: A PIL image.
"""
- def __init__(self, image=None, **kw):
+ def __init__(self, image: Image.Image | None = None, **kw: Any) -> None:
# Tk compatibility: file or data
if image is None:
image = _get_image_from_kw(kw)
+ if image is None:
+ msg = "Image is required"
+ raise ValueError(msg)
self.__mode = image.mode
self.__size = image.size
- if _pilbitmap_check():
- # fast way (requires the pilbitmap booster patch)
- image.load()
- kw["data"] = f"PIL:{image.im.id}"
- self.__im = image # must keep a reference
- else:
- # slow but safe way
- kw["data"] = image.tobitmap()
- self.__photo = tkinter.BitmapImage(**kw)
+ self.__photo = tkinter.BitmapImage(data=image.tobitmap(), **kw)
def __del__(self) -> None:
- name = self.__photo.name
+ try:
+ name = self.__photo.name
+ except AttributeError:
+ return
self.__photo.name = None
try:
self.__photo.tk.call("image", "delete", name)
@@ -257,25 +259,29 @@ class BitmapImage:
def getimage(photo: PhotoImage) -> Image.Image:
"""Copies the contents of a PhotoImage to a PIL image memory."""
im = Image.new("RGBA", (photo.width(), photo.height()))
- block = im.im
- _pyimagingtkcall("PyImagingPhotoGet", photo, block.id)
+ _pyimagingtkcall("PyImagingPhotoGet", photo, im.getim())
return im
-def _show(image, title):
+def _show(image: Image.Image, title: str | None) -> None:
"""Helper for the Image.show method."""
class UI(tkinter.Label):
- def __init__(self, master, im):
+ def __init__(self, master: tkinter.Toplevel, im: Image.Image) -> None:
+ self.image: BitmapImage | PhotoImage
if im.mode == "1":
self.image = BitmapImage(im, foreground="white", master=master)
else:
self.image = PhotoImage(im, master=master)
- super().__init__(master, image=self.image, bg="black", bd=0)
+ if TYPE_CHECKING:
+ image = cast(tkinter._Image, self.image)
+ else:
+ image = self.image
+ super().__init__(master, image=image, bg="black", bd=0)
- if not tkinter._default_root:
+ if not getattr(tkinter, "_default_root"):
msg = "tkinter not initialized"
raise OSError(msg)
top = tkinter.Toplevel()
diff --git a/src/PIL/ImageTransform.py b/src/PIL/ImageTransform.py
index 80a6116b7..a3d8f441a 100644
--- a/src/PIL/ImageTransform.py
+++ b/src/PIL/ImageTransform.py
@@ -14,7 +14,8 @@
#
from __future__ import annotations
-from typing import Any, Sequence
+from collections.abc import Sequence
+from typing import Any
from . import Image
@@ -24,7 +25,7 @@ class Transform(Image.ImageTransformHandler):
method: Image.Transform
- def __init__(self, data: Sequence[int]) -> None:
+ def __init__(self, data: Sequence[Any]) -> None:
self.data = data
def getdata(self) -> tuple[Image.Transform, Sequence[int]]:
diff --git a/src/PIL/ImageWin.py b/src/PIL/ImageWin.py
index 6c29e2590..98c28f29f 100644
--- a/src/PIL/ImageWin.py
+++ b/src/PIL/ImageWin.py
@@ -69,22 +69,28 @@ class Dib:
defines the size of the image.
"""
- def __init__(self, image, size=None):
- if hasattr(image, "mode") and hasattr(image, "size"):
+ def __init__(
+ self, image: Image.Image | str, size: tuple[int, int] | None = None
+ ) -> None:
+ if isinstance(image, str):
+ mode = image
+ image = ""
+ if size is None:
+ msg = "If first argument is mode, size is required"
+ raise ValueError(msg)
+ else:
mode = image.mode
size = image.size
- else:
- mode = image
- image = None
if mode not in ["1", "L", "P", "RGB"]:
mode = Image.getmodebase(mode)
self.image = Image.core.display(mode, size)
self.mode = mode
self.size = size
if image:
+ assert not isinstance(image, str)
self.paste(image)
- def expose(self, handle):
+ def expose(self, handle: int | HDC | HWND) -> None:
"""
Copy the bitmap contents to a device context.
@@ -92,17 +98,22 @@ class Dib:
HDC or HWND instance. In PythonWin, you can use
``CDC.GetHandleAttrib()`` to get a suitable handle.
"""
+ handle_int = int(handle)
if isinstance(handle, HWND):
- dc = self.image.getdc(handle)
+ dc = self.image.getdc(handle_int)
try:
- result = self.image.expose(dc)
+ self.image.expose(dc)
finally:
- self.image.releasedc(handle, dc)
+ self.image.releasedc(handle_int, dc)
else:
- result = self.image.expose(handle)
- return result
+ self.image.expose(handle_int)
- def draw(self, handle, dst, src=None):
+ def draw(
+ self,
+ handle: int | HDC | HWND,
+ dst: tuple[int, int, int, int],
+ src: tuple[int, int, int, int] | None = None,
+ ) -> None:
"""
Same as expose, but allows you to specify where to draw the image, and
what part of it to draw.
@@ -112,19 +123,19 @@ class Dib:
the destination have different sizes, the image is resized as
necessary.
"""
- if not src:
+ if src is None:
src = (0, 0) + self.size
+ handle_int = int(handle)
if isinstance(handle, HWND):
- dc = self.image.getdc(handle)
+ dc = self.image.getdc(handle_int)
try:
- result = self.image.draw(dc, dst, src)
+ self.image.draw(dc, dst, src)
finally:
- self.image.releasedc(handle, dc)
+ self.image.releasedc(handle_int, dc)
else:
- result = self.image.draw(handle, dst, src)
- return result
+ self.image.draw(handle_int, dst, src)
- def query_palette(self, handle):
+ def query_palette(self, handle: int | HDC | HWND) -> int:
"""
Installs the palette associated with the image in the given device
context.
@@ -136,17 +147,18 @@ class Dib:
:param handle: Device context (HDC), cast to a Python integer, or an
HDC or HWND instance.
- :return: A true value if one or more entries were changed (this
- indicates that the image should be redrawn).
+ :return: The number of entries that were changed (if one or more entries,
+ this indicates that the image should be redrawn).
"""
+ handle_int = int(handle)
if isinstance(handle, HWND):
- handle = self.image.getdc(handle)
+ handle = self.image.getdc(handle_int)
try:
result = self.image.query_palette(handle)
finally:
self.image.releasedc(handle, handle)
else:
- result = self.image.query_palette(handle)
+ result = self.image.query_palette(handle_int)
return result
def paste(
@@ -199,22 +211,22 @@ class Window:
title, self.__dispatcher, width or 0, height or 0
)
- def __dispatcher(self, action, *args):
- return getattr(self, f"ui_handle_{action}")(*args)
+ def __dispatcher(self, action: str, *args: int) -> None:
+ getattr(self, f"ui_handle_{action}")(*args)
- def ui_handle_clear(self, dc, x0, y0, x1, y1):
+ def ui_handle_clear(self, dc: int, x0: int, y0: int, x1: int, y1: int) -> None:
pass
- def ui_handle_damage(self, x0, y0, x1, y1):
+ def ui_handle_damage(self, x0: int, y0: int, x1: int, y1: int) -> None:
pass
def ui_handle_destroy(self) -> None:
pass
- def ui_handle_repair(self, dc, x0, y0, x1, y1):
+ def ui_handle_repair(self, dc: int, x0: int, y0: int, x1: int, y1: int) -> None:
pass
- def ui_handle_resize(self, width, height):
+ def ui_handle_resize(self, width: int, height: int) -> None:
pass
def mainloop(self) -> None:
@@ -224,12 +236,12 @@ class Window:
class ImageWindow(Window):
"""Create an image window which displays the given image."""
- def __init__(self, image, title="PIL"):
+ def __init__(self, image: Image.Image | Dib, title: str = "PIL") -> None:
if not isinstance(image, Dib):
image = Dib(image)
self.image = image
width, height = image.size
super().__init__(title, width=width, height=height)
- def ui_handle_repair(self, dc, x0, y0, x1, y1):
+ def ui_handle_repair(self, dc: int, x0: int, y0: int, x1: int, y1: int) -> None:
self.image.draw(dc, (x0, y0, x1, y1))
diff --git a/src/PIL/ImtImagePlugin.py b/src/PIL/ImtImagePlugin.py
index abb3fb762..068cd5c33 100644
--- a/src/PIL/ImtImagePlugin.py
+++ b/src/PIL/ImtImagePlugin.py
@@ -58,11 +58,11 @@ class ImtImageFile(ImageFile.ImageFile):
if s == b"\x0C":
# image data begins
self.tile = [
- (
+ ImageFile._Tile(
"raw",
(0, 0) + self.size,
self.fp.tell() - len(buffer),
- (self.mode, 0, 1),
+ self.mode,
)
]
diff --git a/src/PIL/IptcImagePlugin.py b/src/PIL/IptcImagePlugin.py
index 73df83bfb..60ab7c83f 100644
--- a/src/PIL/IptcImagePlugin.py
+++ b/src/PIL/IptcImagePlugin.py
@@ -16,8 +16,9 @@
#
from __future__ import annotations
+from collections.abc import Sequence
from io import BytesIO
-from typing import Sequence
+from typing import cast
from . import Image, ImageFile
from ._binary import i16be as i16
@@ -146,9 +147,11 @@ class IptcImageFile(ImageFile.ImageFile):
# tile
if tag == (8, 10):
- self.tile = [("iptc", (0, 0) + self.size, offset, compression)]
+ self.tile = [
+ ImageFile._Tile("iptc", (0, 0) + self.size, offset, compression)
+ ]
- def load(self):
+ def load(self) -> Image.core.PixelAccess | None:
if len(self.tile) != 1 or self.tile[0][0] != "iptc":
return ImageFile.ImageFile.load(self)
@@ -176,6 +179,7 @@ class IptcImageFile(ImageFile.ImageFile):
with Image.open(o) as _im:
_im.load()
self.im = _im.im
+ return None
Image.register_open(IptcImageFile.format, IptcImageFile)
@@ -183,7 +187,9 @@ Image.register_open(IptcImageFile.format, IptcImageFile)
Image.register_extension(IptcImageFile.format, ".iim")
-def getiptcinfo(im):
+def getiptcinfo(
+ im: ImageFile.ImageFile,
+) -> dict[tuple[int, int], bytes | list[bytes]] | None:
"""
Get IPTC information from TIFF, JPEG, or IPTC file.
@@ -195,9 +201,13 @@ def getiptcinfo(im):
data = None
+ info: dict[tuple[int, int], bytes | list[bytes]] = {}
if isinstance(im, IptcImageFile):
# return info dictionary right away
- return im.info
+ for k, v in im.info.items():
+ if isinstance(k, tuple):
+ info[k] = v
+ return info
elif isinstance(im, JpegImagePlugin.JpegImageFile):
# extract the IPTC/NAA resource
@@ -209,8 +219,8 @@ def getiptcinfo(im):
# get raw data from the IPTC/NAA tag (PhotoShop tags the data
# as 4-byte integers, so we cannot use the get method...)
try:
- data = im.tag.tagdata[TiffImagePlugin.IPTC_NAA_CHUNK]
- except (AttributeError, KeyError):
+ data = im.tag_v2[TiffImagePlugin.IPTC_NAA_CHUNK]
+ except KeyError:
pass
if data is None:
@@ -220,16 +230,20 @@ def getiptcinfo(im):
class FakeImage:
pass
- im = FakeImage()
- im.__class__ = IptcImageFile
+ fake_im = FakeImage()
+ fake_im.__class__ = IptcImageFile # type: ignore[assignment]
+ iptc_im = cast(IptcImageFile, fake_im)
# parse the IPTC information chunk
- im.info = {}
- im.fp = BytesIO(data)
+ iptc_im.info = {}
+ iptc_im.fp = BytesIO(data)
try:
- im._open()
+ iptc_im._open()
except (IndexError, KeyError):
pass # expected failure
- return im.info
+ for k, v in iptc_im.info.items():
+ if isinstance(k, tuple):
+ info[k] = v
+ return info
diff --git a/src/PIL/Jpeg2KImagePlugin.py b/src/PIL/Jpeg2KImagePlugin.py
index 5a0ef0d01..b6ebd562b 100644
--- a/src/PIL/Jpeg2KImagePlugin.py
+++ b/src/PIL/Jpeg2KImagePlugin.py
@@ -18,7 +18,8 @@ from __future__ import annotations
import io
import os
import struct
-from typing import IO
+from collections.abc import Callable
+from typing import IO, cast
from . import Image, ImageFile, ImagePalette, _binary
@@ -29,7 +30,7 @@ class BoxReader:
and to easily step into and read sub-boxes.
"""
- def __init__(self, fp, length=-1):
+ def __init__(self, fp: IO[bytes], length: int = -1) -> None:
self.fp = fp
self.has_length = length >= 0
self.length = length
@@ -59,7 +60,7 @@ class BoxReader:
self.remaining_in_box -= num_bytes
return data
- def read_fields(self, field_format):
+ def read_fields(self, field_format: str) -> tuple[int | bytes, ...]:
size = struct.calcsize(field_format)
data = self._read_bytes(size)
return struct.unpack(field_format, data)
@@ -82,9 +83,9 @@ class BoxReader:
self.remaining_in_box = -1
# Read the length and type of the next box
- lbox, tbox = self.read_fields(">I4s")
+ lbox, tbox = cast(tuple[int, bytes], self.read_fields(">I4s"))
if lbox == 1:
- lbox = self.read_fields(">Q")[0]
+ lbox = cast(int, self.read_fields(">Q")[0])
hlen = 16
else:
hlen = 8
@@ -97,7 +98,7 @@ class BoxReader:
return tbox
-def _parse_codestream(fp):
+def _parse_codestream(fp: IO[bytes]) -> tuple[tuple[int, int], str]:
"""Parse the JPEG 2000 codestream to extract the size and component
count from the SIZ marker segment, returning a PIL (size, mode) tuple."""
@@ -122,20 +123,30 @@ def _parse_codestream(fp):
elif csiz == 4:
mode = "RGBA"
else:
- mode = None
+ msg = "unable to determine J2K image mode"
+ raise SyntaxError(msg)
return size, mode
-def _res_to_dpi(num, denom, exp):
+def _res_to_dpi(num: int, denom: int, exp: int) -> float | None:
"""Convert JPEG2000's (numerator, denominator, exponent-base-10) resolution,
calculated as (num / denom) * 10^exp and stored in dots per meter,
to floating-point dots per inch."""
- if denom != 0:
- return (254 * num * (10**exp)) / (10000 * denom)
+ if denom == 0:
+ return None
+ return (254 * num * (10**exp)) / (10000 * denom)
-def _parse_jp2_header(fp):
+def _parse_jp2_header(
+ fp: IO[bytes],
+) -> tuple[
+ tuple[int, int],
+ str,
+ str | None,
+ tuple[float, float] | None,
+ ImagePalette.ImagePalette | None,
+]:
"""Parse the JP2 header box to extract size, component count,
color space information, and optionally DPI information,
returning a (size, mode, mimetype, dpi) tuple."""
@@ -153,6 +164,7 @@ def _parse_jp2_header(fp):
elif tbox == b"ftyp":
if reader.read_fields(">4s")[0] == b"jpx ":
mimetype = "image/jpx"
+ assert header is not None
size = None
mode = None
@@ -166,6 +178,9 @@ def _parse_jp2_header(fp):
if tbox == b"ihdr":
height, width, nc, bpc = header.read_fields(">IIHB")
+ assert isinstance(height, int)
+ assert isinstance(width, int)
+ assert isinstance(bpc, int)
size = (width, height)
if nc == 1 and (bpc & 0x7F) > 8:
mode = "I;16"
@@ -183,11 +198,21 @@ def _parse_jp2_header(fp):
mode = "CMYK"
elif tbox == b"pclr" and mode in ("L", "LA"):
ne, npc = header.read_fields(">HB")
- bitdepths = header.read_fields(">" + ("B" * npc))
- if max(bitdepths) <= 8:
- palette = ImagePalette.ImagePalette()
+ assert isinstance(ne, int)
+ assert isinstance(npc, int)
+ max_bitdepth = 0
+ for bitdepth in header.read_fields(">" + ("B" * npc)):
+ assert isinstance(bitdepth, int)
+ if bitdepth > max_bitdepth:
+ max_bitdepth = bitdepth
+ if max_bitdepth <= 8:
+ palette = ImagePalette.ImagePalette("RGBA" if npc == 4 else "RGB")
for i in range(ne):
- palette.getcolor(header.read_fields(">" + ("B" * npc)))
+ color: list[int] = []
+ for value in header.read_fields(">" + ("B" * npc)):
+ assert isinstance(value, int)
+ color.append(value)
+ palette.getcolor(tuple(color))
mode = "P" if mode == "L" else "PA"
elif tbox == b"res ":
res = header.read_boxes()
@@ -195,6 +220,12 @@ def _parse_jp2_header(fp):
tres = res.next_box_type()
if tres == b"resc":
vrcn, vrcd, hrcn, hrcd, vrce, hrce = res.read_fields(">HHHHBB")
+ assert isinstance(vrcn, int)
+ assert isinstance(vrcd, int)
+ assert isinstance(hrcn, int)
+ assert isinstance(hrcd, int)
+ assert isinstance(vrce, int)
+ assert isinstance(hrce, int)
hres = _res_to_dpi(hrcn, hrcd, hrce)
vres = _res_to_dpi(vrcn, vrcd, vrce)
if hres is not None and vres is not None:
@@ -236,10 +267,6 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
msg = "not a JPEG 2000 file"
raise SyntaxError(msg)
- if self.size is None or self.mode is None:
- msg = "unable to determine size/mode"
- raise SyntaxError(msg)
-
self._reduce = 0
self.layers = 0
@@ -260,7 +287,7 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
length = -1
self.tile = [
- (
+ ImageFile._Tile(
"jpeg2k",
(0, 0) + self.size,
0,
@@ -290,18 +317,23 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
else:
self.fp.seek(length - 2, os.SEEK_CUR)
- @property
- def reduce(self):
+ @property # type: ignore[override]
+ def reduce(
+ self,
+ ) -> (
+ Callable[[int | tuple[int, int], tuple[int, int, int, int] | None], Image.Image]
+ | int
+ ):
# https://github.com/python-pillow/Pillow/issues/4343 found that the
# new Image 'reduce' method was shadowed by this plugin's 'reduce'
# property. This attempts to allow for both scenarios
return self._reduce or super().reduce
@reduce.setter
- def reduce(self, value):
+ def reduce(self, value: int) -> None:
self._reduce = value
- def load(self):
+ def load(self) -> Image.core.PixelAccess | None:
if self.tile and self._reduce:
power = 1 << self._reduce
adjust = power >> 1
@@ -312,8 +344,9 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
# Update the reduce and layers settings
t = self.tile[0]
+ assert isinstance(t[3], tuple)
t3 = (t[3][0], self._reduce, self.layers, t[3][3], t[3][4])
- self.tile = [(t[0], (0, 0) + self.size, t[2], t3)]
+ self.tile = [ImageFile._Tile(t[0], (0, 0) + self.size, t[2], t3)]
return ImageFile.ImageFile.load(self)
@@ -329,11 +362,13 @@ def _accept(prefix: bytes) -> bool:
# Save support
-def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None:
+def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
# Get the keyword arguments
info = im.encoderinfo
- if filename.endswith(".j2k") or info.get("no_jp2", False):
+ if isinstance(filename, str):
+ filename = filename.encode()
+ if filename.endswith(b".j2k") or info.get("no_jp2", False):
kind = "j2k"
else:
kind = "jp2"
@@ -391,7 +426,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None:
plt,
)
- ImageFile._save(im, fp, [("jpeg2k", (0, 0) + im.size, 0, kind)])
+ ImageFile._save(im, fp, [ImageFile._Tile("jpeg2k", (0, 0) + im.size, 0, kind)])
# ------------------------------------------------------------
diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py
index 1d088c288..97fc5747f 100644
--- a/src/PIL/JpegImagePlugin.py
+++ b/src/PIL/JpegImagePlugin.py
@@ -42,15 +42,19 @@ import subprocess
import sys
import tempfile
import warnings
-from typing import Any
+from typing import IO, TYPE_CHECKING, Any
from . import Image, ImageFile
from ._binary import i16be as i16
from ._binary import i32be as i32
from ._binary import o8
from ._binary import o16be as o16
+from ._deprecate import deprecate
from .JpegPresets import presets
+if TYPE_CHECKING:
+ from .MpoImagePlugin import MpoImageFile
+
#
# Parser
@@ -60,7 +64,7 @@ def Skip(self: JpegImageFile, marker: int) -> None:
ImageFile._safe_read(self.fp, n)
-def APP(self, marker):
+def APP(self: JpegImageFile, marker: int) -> None:
#
# Application marker. Store these in the APP dictionary.
# Also look for well-known application markers.
@@ -68,7 +72,7 @@ def APP(self, marker):
n = i16(self.fp.read(2)) - 2
s = ImageFile._safe_read(self.fp, n)
- app = "APP%d" % (marker & 15)
+ app = f"APP{marker & 15}"
self.app[app] = s # compatibility
self.applist.append((app, s))
@@ -95,6 +99,8 @@ def APP(self, marker):
else:
self.info["exif"] = s
self._exif_offset = self.fp.tell() - n + 6
+ elif marker == 0xFFE1 and s[:29] == b"http://ns.adobe.com/xap/1.0/\x00":
+ self.info["xmp"] = s.split(b"\x00", 1)[1]
elif marker == 0xFFE2 and s[:5] == b"FPXR\0":
# extract FlashPix information (incomplete)
self.info["flashpix"] = s # FIXME: value will change
@@ -131,13 +137,14 @@ def APP(self, marker):
offset += 4
data = s[offset : offset + size]
if code == 0x03ED: # ResolutionInfo
- data = {
+ photoshop[code] = {
"XResolution": i32(data, 0) / 65536,
"DisplayedUnitsX": i16(data, 4),
"YResolution": i32(data, 8) / 65536,
"DisplayedUnitsY": i16(data, 12),
}
- photoshop[code] = data
+ else:
+ photoshop[code] = data
offset += size
offset += offset & 1 # align
except struct.error:
@@ -159,38 +166,6 @@ def APP(self, marker):
# plus constant header size
self.info["mpoffset"] = self.fp.tell() - n + 4
- # If DPI isn't in JPEG header, fetch from EXIF
- if "dpi" not in self.info and "exif" in self.info:
- try:
- exif = self.getexif()
- resolution_unit = exif[0x0128]
- x_resolution = exif[0x011A]
- try:
- dpi = float(x_resolution[0]) / x_resolution[1]
- except TypeError:
- dpi = x_resolution
- if math.isnan(dpi):
- msg = "DPI is not a number"
- raise ValueError(msg)
- if resolution_unit == 3: # cm
- # 1 dpcm = 2.54 dpi
- dpi *= 2.54
- self.info["dpi"] = dpi, dpi
- except (
- struct.error,
- KeyError,
- SyntaxError,
- TypeError,
- ValueError,
- ZeroDivisionError,
- ):
- # struct.error for truncated EXIF
- # KeyError for dpi not included
- # SyntaxError for invalid/unreadable EXIF
- # ValueError or TypeError for dpi being an invalid float
- # ZeroDivisionError for invalid dpi rational value
- self.info["dpi"] = 72, 72
-
def COM(self: JpegImageFile, marker: int) -> None:
#
@@ -358,7 +333,7 @@ class JpegImageFile(ImageFile.ImageFile):
format = "JPEG"
format_description = "JPEG (ISO 10918)"
- def _open(self):
+ def _open(self) -> None:
s = self.fp.read(3)
if not _accept(s):
@@ -368,15 +343,16 @@ class JpegImageFile(ImageFile.ImageFile):
# Create attributes
self.bits = self.layers = 0
+ self._exif_offset = 0
# JPEG specifics (internal)
- self.layer = []
- self.huffman_dc = {}
- self.huffman_ac = {}
- self.quantization = {}
- self.app = {} # compatibility
- self.applist = []
- self.icclist = []
+ self.layer: list[tuple[int, int, int, int]] = []
+ self._huffman_dc: dict[Any, Any] = {}
+ self._huffman_ac: dict[Any, Any] = {}
+ self.quantization: dict[int, list[int]] = {}
+ self.app: dict[str, bytes] = {} # compatibility
+ self.applist: list[tuple[str, bytes]] = []
+ self.icclist: list[bytes] = []
while True:
i = s[0]
@@ -396,7 +372,9 @@ class JpegImageFile(ImageFile.ImageFile):
rawmode = self.mode
if self.mode == "CMYK":
rawmode = "CMYK;I" # assume adobe conventions
- self.tile = [("jpeg", (0, 0) + self.size, 0, (rawmode, ""))]
+ self.tile = [
+ ImageFile._Tile("jpeg", (0, 0) + self.size, 0, (rawmode, ""))
+ ]
# self.__offset = self.fp.tell()
break
s = self.fp.read(1)
@@ -409,6 +387,21 @@ class JpegImageFile(ImageFile.ImageFile):
msg = "no marker found"
raise SyntaxError(msg)
+ self._read_dpi_from_exif()
+
+ def __getattr__(self, name: str) -> Any:
+ if name in ("huffman_ac", "huffman_dc"):
+ deprecate(name, 12)
+ return getattr(self, "_" + name)
+ raise AttributeError(name)
+
+ def __getstate__(self) -> list[Any]:
+ return super().__getstate__() + [self.layers, self.layer]
+
+ def __setstate__(self, state: list[Any]) -> None:
+ super().__setstate__(state)
+ self.layers, self.layer = state[5:]
+
def load_read(self, read_bytes: int) -> bytes:
"""
internal: read more image data
@@ -426,7 +419,7 @@ class JpegImageFile(ImageFile.ImageFile):
return s
def draft(
- self, mode: str | None, size: tuple[int, int]
+ self, mode: str | None, size: tuple[int, int] | None
) -> tuple[str, tuple[int, int, float, float]] | None:
if len(self.tile) != 1:
return None
@@ -439,6 +432,7 @@ class JpegImageFile(ImageFile.ImageFile):
scale = 1
original_size = self.size
+ assert isinstance(a, tuple)
if a[0] == "RGB" and mode in ["L", "YCbCr"]:
self._mode = mode
a = mode, ""
@@ -448,6 +442,7 @@ class JpegImageFile(ImageFile.ImageFile):
for s in [8, 4, 2, 1]:
if scale >= s:
break
+ assert e is not None
e = (
e[0],
e[1],
@@ -457,7 +452,7 @@ class JpegImageFile(ImageFile.ImageFile):
self._size = ((self.size[0] + s - 1) // s, (self.size[1] + s - 1) // s)
scale = s
- self.tile = [(d, e, o, a)]
+ self.tile = [ImageFile._Tile(d, e, o, a)]
self.decoderconfig = (scale, 0)
box = (0, 0, original_size[0] / scale, original_size[1] / scale)
@@ -494,35 +489,49 @@ class JpegImageFile(ImageFile.ImageFile):
self.tile = []
- def _getexif(self) -> dict[str, Any] | None:
+ def _getexif(self) -> dict[int, Any] | None:
return _getexif(self)
- def _getmp(self):
+ def _read_dpi_from_exif(self) -> None:
+ # If DPI isn't in JPEG header, fetch from EXIF
+ if "dpi" in self.info or "exif" not in self.info:
+ return
+ try:
+ exif = self.getexif()
+ resolution_unit = exif[0x0128]
+ x_resolution = exif[0x011A]
+ try:
+ dpi = float(x_resolution[0]) / x_resolution[1]
+ except TypeError:
+ dpi = x_resolution
+ if math.isnan(dpi):
+ msg = "DPI is not a number"
+ raise ValueError(msg)
+ if resolution_unit == 3: # cm
+ # 1 dpcm = 2.54 dpi
+ dpi *= 2.54
+ self.info["dpi"] = dpi, dpi
+ except (
+ struct.error, # truncated EXIF
+ KeyError, # dpi not included
+ SyntaxError, # invalid/unreadable EXIF
+ TypeError, # dpi is an invalid float
+ ValueError, # dpi is an invalid float
+ ZeroDivisionError, # invalid dpi rational value
+ ):
+ self.info["dpi"] = 72, 72
+
+ def _getmp(self) -> dict[int, Any] | None:
return _getmp(self)
- def getxmp(self) -> dict[str, Any]:
- """
- Returns a dictionary containing the XMP tags.
- Requires defusedxml to be installed.
- :returns: XMP tags in a dictionary.
- """
-
- for segment, content in self.applist:
- if segment == "APP1":
- marker, xmp_tags = content.split(b"\x00")[:2]
- if marker == b"http://ns.adobe.com/xap/1.0/":
- return self._getxmp(xmp_tags)
- return {}
-
-
-def _getexif(self) -> dict[str, Any] | None:
+def _getexif(self: JpegImageFile) -> dict[int, Any] | None:
if "exif" not in self.info:
return None
return self.getexif()._get_merged_dict()
-def _getmp(self):
+def _getmp(self: JpegImageFile) -> dict[int, Any] | None:
# Extract MP information. This method was inspired by the "highly
# experimental" _getexif version that's been in use for years now,
# itself based on the ImageFileDirectory class in the TIFF plugin.
@@ -630,7 +639,7 @@ samplings = {
# fmt: on
-def get_sampling(im):
+def get_sampling(im: Image.Image) -> int:
# There's no subsampling when images have only 1 layer
# (grayscale images) or when they are CMYK (4 layers),
# so set subsampling to the default value.
@@ -638,13 +647,13 @@ def get_sampling(im):
# NOTE: currently Pillow can't encode JPEG to YCCK format.
# If YCCK support is added in the future, subsampling code will have
# to be updated (here and in JpegEncode.c) to deal with 4 layers.
- if not hasattr(im, "layers") or im.layers in (1, 4):
+ if not isinstance(im, JpegImageFile) or im.layers in (1, 4):
return -1
sampling = im.layer[0][1:3] + im.layer[1][1:3] + im.layer[2][1:3]
return samplings.get(sampling, -1)
-def _save(im, fp, filename):
+def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
if im.width == 0 or im.height == 0:
msg = "cannot write empty image as JPEG"
raise ValueError(msg)
@@ -697,7 +706,11 @@ def _save(im, fp, filename):
raise ValueError(msg)
subsampling = get_sampling(im)
- def validate_qtables(qtables):
+ def validate_qtables(
+ qtables: (
+ str | tuple[list[int], ...] | list[list[int]] | dict[int, list[int]] | None
+ )
+ ) -> list[list[int]] | None:
if qtables is None:
return qtables
if isinstance(qtables, str):
@@ -727,12 +740,12 @@ def _save(im, fp, filename):
if len(table) != 64:
msg = "Invalid quantization table"
raise TypeError(msg)
- table = array.array("H", table)
+ table_array = array.array("H", table)
except TypeError as e:
msg = "Invalid quantization table"
raise ValueError(msg) from e
else:
- qtables[idx] = list(table)
+ qtables[idx] = list(table_array)
return qtables
if qtables == "keep":
@@ -745,17 +758,27 @@ def _save(im, fp, filename):
extra = info.get("extra", b"")
MAX_BYTES_IN_MARKER = 65533
+ xmp = info.get("xmp")
+ if xmp:
+ overhead_len = 29 # b"http://ns.adobe.com/xap/1.0/\x00"
+ max_data_bytes_in_marker = MAX_BYTES_IN_MARKER - overhead_len
+ if len(xmp) > max_data_bytes_in_marker:
+ msg = "XMP data is too long"
+ raise ValueError(msg)
+ size = o16(2 + overhead_len + len(xmp))
+ extra += b"\xFF\xE1" + size + b"http://ns.adobe.com/xap/1.0/\x00" + xmp
+
icc_profile = info.get("icc_profile")
if icc_profile:
- ICC_OVERHEAD_LEN = 14
- MAX_DATA_BYTES_IN_MARKER = MAX_BYTES_IN_MARKER - ICC_OVERHEAD_LEN
+ overhead_len = 14 # b"ICC_PROFILE\0" + o8(i) + o8(len(markers))
+ max_data_bytes_in_marker = MAX_BYTES_IN_MARKER - overhead_len
markers = []
while icc_profile:
- markers.append(icc_profile[:MAX_DATA_BYTES_IN_MARKER])
- icc_profile = icc_profile[MAX_DATA_BYTES_IN_MARKER:]
+ markers.append(icc_profile[:max_data_bytes_in_marker])
+ icc_profile = icc_profile[max_data_bytes_in_marker:]
i = 1
for marker in markers:
- size = o16(2 + ICC_OVERHEAD_LEN + len(marker))
+ size = o16(2 + overhead_len + len(marker))
extra += (
b"\xFF\xE2"
+ size
@@ -824,10 +847,12 @@ def _save(im, fp, filename):
# Ensure that our buffer is big enough. Same with the icc_profile block.
bufsize = max(bufsize, len(exif) + 5, len(extra) + 1)
- ImageFile._save(im, fp, [("jpeg", (0, 0) + im.size, 0, rawmode)], bufsize)
+ ImageFile._save(
+ im, fp, [ImageFile._Tile("jpeg", (0, 0) + im.size, 0, rawmode)], bufsize
+ )
-def _save_cjpeg(im, fp, filename):
+def _save_cjpeg(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
# ALTERNATIVE: handle JPEGs via the IJG command line utilities.
tempfile = im._dump()
subprocess.check_call(["cjpeg", "-outfile", filename, tempfile])
@@ -839,11 +864,17 @@ def _save_cjpeg(im, fp, filename):
##
# Factory for making JPEG and MPO instances
-def jpeg_factory(fp=None, filename=None):
+def jpeg_factory(
+ fp: IO[bytes], filename: str | bytes | None = None
+) -> JpegImageFile | MpoImageFile:
im = JpegImageFile(fp, filename)
try:
mpheader = im._getmp()
- if mpheader[45057] > 1:
+ if mpheader is not None and mpheader[45057] > 1:
+ for segment, content in im.applist:
+ if segment == "APP1" and b' hdrgm:Version="' in content:
+ # Ultra HDR images are not yet supported
+ return im
# It's actually an MPO
from .MpoImagePlugin import MpoImageFile
diff --git a/src/PIL/JpegPresets.py b/src/PIL/JpegPresets.py
index 3aefa073c..d0e64a35e 100644
--- a/src/PIL/JpegPresets.py
+++ b/src/PIL/JpegPresets.py
@@ -37,7 +37,7 @@ You can get the subsampling of a JPEG with the
:func:`.JpegImagePlugin.get_sampling` function.
In JPEG compressed data a JPEG marker is used instead of an EXIF tag.
-(ref.: https://web.archive.org/web/20240227115053/https://exiv2.org/tags.html)
+(ref.: https://exiv2.org/tags.html)
Quantization tables
diff --git a/src/PIL/McIdasImagePlugin.py b/src/PIL/McIdasImagePlugin.py
index 27972236c..5dd031be3 100644
--- a/src/PIL/McIdasImagePlugin.py
+++ b/src/PIL/McIdasImagePlugin.py
@@ -67,7 +67,9 @@ class McIdasImageFile(ImageFile.ImageFile):
offset = w[34] + w[15]
stride = w[15] + w[10] * w[11] * w[14]
- self.tile = [("raw", (0, 0) + self.size, offset, (rawmode, stride, 1))]
+ self.tile = [
+ ImageFile._Tile("raw", (0, 0) + self.size, offset, (rawmode, stride, 1))
+ ]
# --------------------------------------------------------------------
diff --git a/src/PIL/MicImagePlugin.py b/src/PIL/MicImagePlugin.py
index 5aef94dfb..5f23a34b9 100644
--- a/src/PIL/MicImagePlugin.py
+++ b/src/PIL/MicImagePlugin.py
@@ -63,14 +63,14 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
msg = "not an MIC file; no image entries"
raise SyntaxError(msg)
- self.frame = None
+ self.frame = -1
self._n_frames = len(self.images)
self.is_animated = self._n_frames > 1
self.__fp = self.fp
self.seek(0)
- def seek(self, frame):
+ def seek(self, frame: int) -> None:
if not self._seek_check(frame):
return
try:
@@ -85,7 +85,7 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
self.frame = frame
- def tell(self):
+ def tell(self) -> int:
return self.frame
def close(self) -> None:
@@ -93,7 +93,7 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
self.ole.close()
super().close()
- def __exit__(self, *args):
+ def __exit__(self, *args: object) -> None:
self.__fp.close()
self.ole.close()
super().__exit__()
diff --git a/src/PIL/MpoImagePlugin.py b/src/PIL/MpoImagePlugin.py
index 6716722f2..71f89a09a 100644
--- a/src/PIL/MpoImagePlugin.py
+++ b/src/PIL/MpoImagePlugin.py
@@ -22,10 +22,11 @@ from __future__ import annotations
import itertools
import os
import struct
-from typing import IO
+from typing import IO, Any, cast
from . import (
Image,
+ ImageFile,
ImageSequence,
JpegImagePlugin,
TiffImagePlugin,
@@ -33,23 +34,18 @@ from . import (
from ._binary import o32le
-def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None:
+def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
JpegImagePlugin._save(im, fp, filename)
-def _save_all(im, fp, filename):
+def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
append_images = im.encoderinfo.get("append_images", [])
- if not append_images:
- try:
- animated = im.is_animated
- except AttributeError:
- animated = False
- if not animated:
- _save(im, fp, filename)
- return
+ if not append_images and not getattr(im, "is_animated", False):
+ _save(im, fp, filename)
+ return
mpf_offset = 28
- offsets = []
+ offsets: list[int] = []
for imSequence in itertools.chain([im], append_images):
for im_frame in ImageSequence.Iterator(imSequence):
if not offsets:
@@ -106,8 +102,11 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
JpegImagePlugin.JpegImageFile._open(self)
self._after_jpeg_open()
- def _after_jpeg_open(self, mpheader=None):
+ def _after_jpeg_open(self, mpheader: dict[int, Any] | None = None) -> None:
self.mpinfo = mpheader if mpheader is not None else self._getmp()
+ if self.mpinfo is None:
+ msg = "Image appears to be a malformed MPO file"
+ raise ValueError(msg)
self.n_frames = self.mpinfo[0xB001]
self.__mpoffsets = [
mpent["DataOffset"] + self.info["mpoffset"] for mpent in self.mpinfo[0xB002]
@@ -147,14 +146,19 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
if self.info.get("exif") != original_exif:
self._reload_exif()
- self.tile = [("jpeg", (0, 0) + self.size, self.offset, self.tile[0][-1])]
+ self.tile = [
+ ImageFile._Tile("jpeg", (0, 0) + self.size, self.offset, self.tile[0][-1])
+ ]
self.__frame = frame
def tell(self) -> int:
return self.__frame
@staticmethod
- def adopt(jpeg_instance, mpheader=None):
+ def adopt(
+ jpeg_instance: JpegImagePlugin.JpegImageFile,
+ mpheader: dict[int, Any] | None = None,
+ ) -> MpoImageFile:
"""
Transform the instance of JpegImageFile into
an instance of MpoImageFile.
@@ -166,8 +170,9 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
double call to _open.
"""
jpeg_instance.__class__ = MpoImageFile
- jpeg_instance._after_jpeg_open(mpheader)
- return jpeg_instance
+ mpo_instance = cast(MpoImageFile, jpeg_instance)
+ mpo_instance._after_jpeg_open(mpheader)
+ return mpo_instance
# ---------------------------------------------------------------------
diff --git a/src/PIL/MspImagePlugin.py b/src/PIL/MspImagePlugin.py
index 65cc70624..ef6ae87f8 100644
--- a/src/PIL/MspImagePlugin.py
+++ b/src/PIL/MspImagePlugin.py
@@ -70,9 +70,9 @@ class MspImageFile(ImageFile.ImageFile):
self._size = i16(s, 4), i16(s, 6)
if s[:4] == b"DanM":
- self.tile = [("raw", (0, 0) + self.size, 32, ("1", 0, 1))]
+ self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, 32, "1")]
else:
- self.tile = [("MSP", (0, 0) + self.size, 32, None)]
+ self.tile = [ImageFile._Tile("MSP", (0, 0) + self.size, 32)]
class MspDecoder(ImageFile.PyDecoder):
@@ -112,7 +112,7 @@ class MspDecoder(ImageFile.PyDecoder):
_pulls_fd = True
- def decode(self, buffer: bytes) -> tuple[int, int]:
+ def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int]:
assert self.fd is not None
img = io.BytesIO()
@@ -152,7 +152,7 @@ class MspDecoder(ImageFile.PyDecoder):
msg = f"Corrupted MSP file in row {x}"
raise OSError(msg) from e
- self.set_as_raw(img.getvalue(), ("1", 0, 1))
+ self.set_as_raw(img.getvalue(), "1")
return -1, 0
@@ -164,7 +164,7 @@ Image.register_decoder("MSP", MspDecoder)
# write MSP files (uncompressed only)
-def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None:
+def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
if im.mode != "1":
msg = f"cannot write mode {im.mode} as MSP"
raise OSError(msg)
@@ -188,7 +188,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None:
fp.write(o16(h))
# image body
- ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 32, ("1", 0, 1))])
+ ImageFile._save(im, fp, [ImageFile._Tile("raw", (0, 0) + im.size, 32, "1")])
#
diff --git a/src/PIL/PSDraw.py b/src/PIL/PSDraw.py
index 4e2b9788e..02939d26b 100644
--- a/src/PIL/PSDraw.py
+++ b/src/PIL/PSDraw.py
@@ -17,7 +17,7 @@
from __future__ import annotations
import sys
-from typing import TYPE_CHECKING
+from typing import IO, TYPE_CHECKING
from . import EpsImagePlugin
@@ -28,15 +28,12 @@ from . import EpsImagePlugin
class PSDraw:
"""
Sets up printing to the given file. If ``fp`` is omitted,
- ``sys.stdout.buffer`` or ``sys.stdout`` is assumed.
+ ``sys.stdout.buffer`` is assumed.
"""
- def __init__(self, fp=None):
+ def __init__(self, fp: IO[bytes] | None = None) -> None:
if not fp:
- try:
- fp = sys.stdout.buffer
- except AttributeError:
- fp = sys.stdout
+ fp = sys.stdout.buffer
self.fp = fp
def begin_document(self, id: str | None = None) -> None:
@@ -138,7 +135,7 @@ class PSDraw:
sx = x / im.size[0]
sy = y / im.size[1]
self.fp.write(b"%f %f scale\n" % (sx, sy))
- EpsImagePlugin._save(im, self.fp, None, 0)
+ EpsImagePlugin._save(im, self.fp, "", 0)
self.fp.write(b"\ngrestore\n")
diff --git a/src/PIL/PalmImagePlugin.py b/src/PIL/PalmImagePlugin.py
index 85f9fe1bf..b33245376 100644
--- a/src/PIL/PalmImagePlugin.py
+++ b/src/PIL/PalmImagePlugin.py
@@ -8,6 +8,8 @@
##
from __future__ import annotations
+from typing import IO
+
from . import Image, ImageFile
from ._binary import o8
from ._binary import o16be as o16b
@@ -82,10 +84,10 @@ _Palm8BitColormapValues = (
# so build a prototype image to be used for palette resampling
-def build_prototype_image():
+def build_prototype_image() -> Image.Image:
image = Image.new("L", (1, len(_Palm8BitColormapValues)))
image.putdata(list(range(len(_Palm8BitColormapValues))))
- palettedata = ()
+ palettedata: tuple[int, ...] = ()
for colormapValue in _Palm8BitColormapValues:
palettedata += colormapValue
palettedata += (0, 0, 0) * (256 - len(_Palm8BitColormapValues))
@@ -112,7 +114,7 @@ _COMPRESSION_TYPES = {"none": 0xFF, "rle": 0x01, "scanline": 0x00}
# (Internal) Image save plugin for the Palm format.
-def _save(im, fp, filename):
+def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
if im.mode == "P":
# we assume this is a color Palm image with the standard colormap,
# unless the "info" dict has a "custom-colormap" field
@@ -127,21 +129,22 @@ def _save(im, fp, filename):
# and invert it because
# 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)
- )
+ maxval = (1 << bpp) - 1
+ shift = 8 - bpp
+ im = im.point(lambda x: maxval - (x >> shift))
elif im.info.get("bpp") in (1, 2, 4):
# here we assume that even though the inherent mode is 8-bit grayscale,
# only the lower bpp bits are significant.
# We invert them to match the Palm.
bpp = im.info["bpp"]
- im = im.point(lambda x, maxval=(1 << bpp) - 1: maxval - (x & maxval))
+ maxval = (1 << bpp) - 1
+ im = im.point(lambda x: maxval - (x & maxval))
else:
msg = f"cannot write mode {im.mode} as Palm"
raise OSError(msg)
# we ignore the palette here
- im.mode = "P"
+ im._mode = "P"
rawmode = f"P;{bpp}"
version = 1
@@ -170,6 +173,7 @@ def _save(im, fp, filename):
flags = 0
if im.mode == "P" and "custom-colormap" in im.info:
+ assert im.palette is not None
flags = flags & _FLAGS["custom-colormap"]
colormapsize = 4 * 256 + 2
colormapmode = im.palette.mode
@@ -210,7 +214,9 @@ def _save(im, fp, filename):
)
# now convert data to raw form
- ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, rowbytes, 1))])
+ ImageFile._save(
+ im, fp, [ImageFile._Tile("raw", (0, 0) + im.size, 0, (rawmode, rowbytes, 1))]
+ )
if hasattr(fp, "flush"):
fp.flush()
diff --git a/src/PIL/PcdImagePlugin.py b/src/PIL/PcdImagePlugin.py
index 1cd5c4a9d..ac40383f9 100644
--- a/src/PIL/PcdImagePlugin.py
+++ b/src/PIL/PcdImagePlugin.py
@@ -47,13 +47,11 @@ class PcdImageFile(ImageFile.ImageFile):
self._mode = "RGB"
self._size = 768, 512 # FIXME: not correct for rotated images!
- self.tile = [("pcd", (0, 0) + self.size, 96 * 2048, None)]
+ self.tile = [ImageFile._Tile("pcd", (0, 0) + self.size, 96 * 2048)]
def load_end(self) -> None:
if self.tile_post_rotate:
# Handle rotated PCDs
- assert self.im is not None
-
self.im = self.im.rotate(self.tile_post_rotate)
self._size = self.im.size
diff --git a/src/PIL/PcxImagePlugin.py b/src/PIL/PcxImagePlugin.py
index 026bfd9a0..32436cea3 100644
--- a/src/PIL/PcxImagePlugin.py
+++ b/src/PIL/PcxImagePlugin.py
@@ -86,7 +86,7 @@ class PcxImageFile(ImageFile.ImageFile):
elif bits == 1 and planes in (2, 4):
mode = "P"
- rawmode = "P;%dL" % planes
+ rawmode = f"P;{planes}L"
self.palette = ImagePalette.raw("RGB", s[16:64])
elif version == 5 and bits == 8 and planes == 1:
@@ -128,7 +128,9 @@ class PcxImageFile(ImageFile.ImageFile):
bbox = (0, 0) + self.size
logger.debug("size: %sx%s", *self.size)
- self.tile = [("pcx", bbox, self.fp.tell(), (rawmode, planes * stride))]
+ self.tile = [
+ ImageFile._Tile("pcx", bbox, self.fp.tell(), (rawmode, planes * stride))
+ ]
# --------------------------------------------------------------------
@@ -144,7 +146,7 @@ SAVE = {
}
-def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None:
+def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
try:
version, bits, planes, rawmode = SAVE[im.mode]
except KeyError as e:
@@ -198,12 +200,12 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None:
assert fp.tell() == 128
- ImageFile._save(im, fp, [("pcx", (0, 0) + im.size, 0, (rawmode, bits * planes))])
+ ImageFile._save(
+ im, fp, [ImageFile._Tile("pcx", (0, 0) + im.size, 0, (rawmode, bits * planes))]
+ )
if im.mode == "P":
# colour palette
- assert im.im is not None
-
fp.write(o8(12))
palette = im.im.getpalette("RGB", "RGB")
palette += b"\x00" * (768 - len(palette))
diff --git a/src/PIL/PdfImagePlugin.py b/src/PIL/PdfImagePlugin.py
index ccd28f343..e9c20ddc1 100644
--- a/src/PIL/PdfImagePlugin.py
+++ b/src/PIL/PdfImagePlugin.py
@@ -25,7 +25,7 @@ import io
import math
import os
import time
-from typing import IO
+from typing import IO, Any
from . import Image, ImageFile, ImageSequence, PdfParser, __version__, features
@@ -40,7 +40,7 @@ from . import Image, ImageFile, ImageSequence, PdfParser, __version__, features
# 5. page contents
-def _save_all(im: Image.Image, fp: IO[bytes], filename: str) -> None:
+def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
_save(im, fp, filename, save_all=True)
@@ -48,7 +48,12 @@ def _save_all(im: Image.Image, fp: IO[bytes], filename: str) -> None:
# (Internal) Image save plugin for the PDF format.
-def _write_image(im, filename, existing_pdf, image_refs):
+def _write_image(
+ im: Image.Image,
+ filename: str | bytes,
+ existing_pdf: PdfParser.PdfParser,
+ image_refs: list[PdfParser.IndirectReference],
+) -> tuple[PdfParser.IndirectReference, str]:
# FIXME: Should replace ASCIIHexDecode with RunLengthDecode
# (packbits) or LZWDecode (tiff/lzw compression). Note that
# PDF 1.2 also supports Flatedecode (zip compression).
@@ -61,10 +66,10 @@ def _write_image(im, filename, existing_pdf, image_refs):
width, height = im.size
- dict_obj = {"BitsPerComponent": 8}
+ dict_obj: dict[str, Any] = {"BitsPerComponent": 8}
if im.mode == "1":
if features.check("libtiff"):
- filter = "CCITTFaxDecode"
+ decode_filter = "CCITTFaxDecode"
dict_obj["BitsPerComponent"] = 1
params = PdfParser.PdfArray(
[
@@ -79,22 +84,23 @@ def _write_image(im, filename, existing_pdf, image_refs):
]
)
else:
- filter = "DCTDecode"
+ decode_filter = "DCTDecode"
dict_obj["ColorSpace"] = PdfParser.PdfName("DeviceGray")
procset = "ImageB" # grayscale
elif im.mode == "L":
- filter = "DCTDecode"
+ decode_filter = "DCTDecode"
# params = f"<< /Predictor 15 /Columns {width-2} >>"
dict_obj["ColorSpace"] = PdfParser.PdfName("DeviceGray")
procset = "ImageB" # grayscale
elif im.mode == "LA":
- filter = "JPXDecode"
+ decode_filter = "JPXDecode"
# params = f"<< /Predictor 15 /Columns {width-2} >>"
procset = "ImageB" # grayscale
dict_obj["SMaskInData"] = 1
elif im.mode == "P":
- filter = "ASCIIHexDecode"
+ decode_filter = "ASCIIHexDecode"
palette = im.getpalette()
+ assert palette is not None
dict_obj["ColorSpace"] = [
PdfParser.PdfName("Indexed"),
PdfParser.PdfName("DeviceRGB"),
@@ -110,15 +116,15 @@ def _write_image(im, filename, existing_pdf, image_refs):
image_ref = _write_image(smask, filename, existing_pdf, image_refs)[0]
dict_obj["SMask"] = image_ref
elif im.mode == "RGB":
- filter = "DCTDecode"
+ decode_filter = "DCTDecode"
dict_obj["ColorSpace"] = PdfParser.PdfName("DeviceRGB")
procset = "ImageC" # color images
elif im.mode == "RGBA":
- filter = "JPXDecode"
+ decode_filter = "JPXDecode"
procset = "ImageC" # color images
dict_obj["SMaskInData"] = 1
elif im.mode == "CMYK":
- filter = "DCTDecode"
+ decode_filter = "DCTDecode"
dict_obj["ColorSpace"] = PdfParser.PdfName("DeviceCMYK")
procset = "ImageC" # color images
decode = [1, 0, 1, 0, 1, 0, 1, 0]
@@ -131,9 +137,9 @@ def _write_image(im, filename, existing_pdf, image_refs):
op = io.BytesIO()
- if filter == "ASCIIHexDecode":
- ImageFile._save(im, op, [("hex", (0, 0) + im.size, 0, im.mode)])
- elif filter == "CCITTFaxDecode":
+ if decode_filter == "ASCIIHexDecode":
+ ImageFile._save(im, op, [ImageFile._Tile("hex", (0, 0) + im.size, 0, im.mode)])
+ elif decode_filter == "CCITTFaxDecode":
im.save(
op,
"TIFF",
@@ -141,21 +147,22 @@ def _write_image(im, filename, existing_pdf, image_refs):
# use a single strip
strip_size=math.ceil(width / 8) * height,
)
- elif filter == "DCTDecode":
+ elif decode_filter == "DCTDecode":
Image.SAVE["JPEG"](im, op, filename)
- elif filter == "JPXDecode":
+ elif decode_filter == "JPXDecode":
del dict_obj["BitsPerComponent"]
Image.SAVE["JPEG2000"](im, op, filename)
else:
- msg = f"unsupported PDF filter ({filter})"
+ msg = f"unsupported PDF filter ({decode_filter})"
raise ValueError(msg)
stream = op.getvalue()
- if filter == "CCITTFaxDecode":
+ filter: PdfParser.PdfArray | PdfParser.PdfName
+ if decode_filter == "CCITTFaxDecode":
stream = stream[8:]
- filter = PdfParser.PdfArray([PdfParser.PdfName(filter)])
+ filter = PdfParser.PdfArray([PdfParser.PdfName(decode_filter)])
else:
- filter = PdfParser.PdfName(filter)
+ filter = PdfParser.PdfName(decode_filter)
image_ref = image_refs.pop(0)
existing_pdf.write_obj(
@@ -174,12 +181,15 @@ def _write_image(im, filename, existing_pdf, image_refs):
return image_ref, procset
-def _save(im, fp, filename, save_all=False):
+def _save(
+ im: Image.Image, fp: IO[bytes], filename: str | bytes, save_all: bool = False
+) -> None:
is_appending = im.encoderinfo.get("append", False)
+ filename_str = filename.decode() if isinstance(filename, bytes) else filename
if is_appending:
- existing_pdf = PdfParser.PdfParser(f=fp, filename=filename, mode="r+b")
+ existing_pdf = PdfParser.PdfParser(f=fp, filename=filename_str, mode="r+b")
else:
- existing_pdf = PdfParser.PdfParser(f=fp, filename=filename, mode="w+b")
+ existing_pdf = PdfParser.PdfParser(f=fp, filename=filename_str, mode="w+b")
dpi = im.encoderinfo.get("dpi")
if dpi:
@@ -228,12 +238,7 @@ def _save(im, fp, filename, save_all=False):
for im in ims:
im_number_of_pages = 1
if save_all:
- try:
- im_number_of_pages = im.n_frames
- except AttributeError:
- # Image format does not have n_frames.
- # It is a single frame image
- pass
+ im_number_of_pages = getattr(im, "n_frames", 1)
number_of_pages += im_number_of_pages
for i in range(im_number_of_pages):
image_refs.append(existing_pdf.next_object_id(0))
@@ -250,7 +255,9 @@ def _save(im, fp, filename, save_all=False):
page_number = 0
for im_sequence in ims:
- im_pages = ImageSequence.Iterator(im_sequence) if save_all else [im_sequence]
+ im_pages: ImageSequence.Iterator | list[Image.Image] = (
+ ImageSequence.Iterator(im_sequence) if save_all else [im_sequence]
+ )
for im in im_pages:
image_ref, procset = _write_image(im, filename, existing_pdf, image_refs)
diff --git a/src/PIL/PdfParser.py b/src/PIL/PdfParser.py
index a6c24e671..7cb2d241b 100644
--- a/src/PIL/PdfParser.py
+++ b/src/PIL/PdfParser.py
@@ -8,7 +8,7 @@ import os
import re
import time
import zlib
-from typing import TYPE_CHECKING, Any, List, NamedTuple, Union
+from typing import IO, TYPE_CHECKING, Any, NamedTuple, Union
# see 7.9.2.2 Text String Type on page 86 and D.3 PDFDocEncoding Character Set
@@ -62,7 +62,7 @@ PDFDocEncoding = {
}
-def decode_text(b):
+def decode_text(b: bytes) -> str:
if b[: len(codecs.BOM_UTF16_BE)] == codecs.BOM_UTF16_BE:
return b[len(codecs.BOM_UTF16_BE) :].decode("utf_16_be")
else:
@@ -76,7 +76,7 @@ class PdfFormatError(RuntimeError):
pass
-def check_format_condition(condition, error_message):
+def check_format_condition(condition: bool, error_message: str) -> None:
if not condition:
raise PdfFormatError(error_message)
@@ -93,14 +93,13 @@ class IndirectReference(IndirectReferenceTuple):
def __bytes__(self) -> bytes:
return self.__str__().encode("us-ascii")
- def __eq__(self, other):
- return (
- other.__class__ is self.__class__
- and other.object_id == self.object_id
- and other.generation == self.generation
- )
+ def __eq__(self, other: object) -> bool:
+ if self.__class__ is not other.__class__:
+ return False
+ assert isinstance(other, IndirectReference)
+ return other.object_id == self.object_id and other.generation == self.generation
- def __ne__(self, other):
+ def __ne__(self, other: object) -> bool:
return not (self == other)
def __hash__(self) -> int:
@@ -113,13 +112,17 @@ class IndirectObjectDef(IndirectReference):
class XrefTable:
- def __init__(self):
- self.existing_entries = {} # object ID => (offset, generation)
- self.new_entries = {} # object ID => (offset, generation)
+ def __init__(self) -> None:
+ self.existing_entries: dict[int, tuple[int, int]] = (
+ {}
+ ) # object ID => (offset, generation)
+ self.new_entries: dict[int, tuple[int, int]] = (
+ {}
+ ) # object ID => (offset, generation)
self.deleted_entries = {0: 65536} # object ID => generation
self.reading_finished = False
- def __setitem__(self, key, value):
+ def __setitem__(self, key: int, value: tuple[int, int]) -> None:
if self.reading_finished:
self.new_entries[key] = value
else:
@@ -127,13 +130,13 @@ class XrefTable:
if key in self.deleted_entries:
del self.deleted_entries[key]
- def __getitem__(self, key):
+ def __getitem__(self, key: int) -> tuple[int, int]:
try:
return self.new_entries[key]
except KeyError:
return self.existing_entries[key]
- def __delitem__(self, key):
+ def __delitem__(self, key: int) -> None:
if key in self.new_entries:
generation = self.new_entries[key][1] + 1
del self.new_entries[key]
@@ -147,7 +150,7 @@ class XrefTable:
msg = f"object ID {key} cannot be deleted because it doesn't exist"
raise IndexError(msg)
- def __contains__(self, key):
+ def __contains__(self, key: int) -> bool:
return key in self.existing_entries or key in self.new_entries
def __len__(self) -> int:
@@ -157,19 +160,19 @@ class XrefTable:
| set(self.deleted_entries.keys())
)
- def keys(self):
+ def keys(self) -> set[int]:
return (
set(self.existing_entries.keys()) - set(self.deleted_entries.keys())
) | set(self.new_entries.keys())
- def write(self, f):
+ def write(self, f: IO[bytes]) -> int:
keys = sorted(set(self.new_entries.keys()) | set(self.deleted_entries.keys()))
deleted_keys = sorted(set(self.deleted_entries.keys()))
startxref = f.tell()
f.write(b"xref\n")
while keys:
# find a contiguous sequence of object IDs
- prev = None
+ prev: int | None = None
for index, key in enumerate(keys):
if prev is None or prev + 1 == key:
prev = key
@@ -179,7 +182,7 @@ class XrefTable:
break
else:
contiguous_keys = keys
- keys = None
+ keys = []
f.write(b"%d %d\n" % (contiguous_keys[0], len(contiguous_keys)))
for object_id in contiguous_keys:
if object_id in self.new_entries:
@@ -203,7 +206,9 @@ class XrefTable:
class PdfName:
- def __init__(self, name):
+ name: bytes
+
+ def __init__(self, name: PdfName | bytes | str) -> None:
if isinstance(name, PdfName):
self.name = name.name
elif isinstance(name, bytes):
@@ -214,7 +219,7 @@ class PdfName:
def name_as_str(self) -> str:
return self.name.decode("us-ascii")
- def __eq__(self, other):
+ def __eq__(self, other: object) -> bool:
return (
isinstance(other, PdfName) and other.name == self.name
) or other == self.name
@@ -226,7 +231,7 @@ class PdfName:
return f"{self.__class__.__name__}({repr(self.name)})"
@classmethod
- def from_pdf_stream(cls, data):
+ def from_pdf_stream(cls, data: bytes) -> PdfName:
return cls(PdfParser.interpret_name(data))
allowed_chars = set(range(33, 127)) - {ord(c) for c in "#%/()<>[]{}"}
@@ -241,7 +246,7 @@ class PdfName:
return bytes(result)
-class PdfArray(List[Any]):
+class PdfArray(list[Any]):
def __bytes__(self) -> bytes:
return b"[ " + b" ".join(pdf_repr(x) for x in self) + b" ]"
@@ -253,13 +258,13 @@ else:
class PdfDict(_DictBase):
- def __setattr__(self, key, value):
+ def __setattr__(self, key: str, value: Any) -> None:
if key == "data":
collections.UserDict.__setattr__(self, key, value)
else:
self[key.encode("us-ascii")] = value
- def __getattr__(self, key):
+ def __getattr__(self, key: str) -> str | time.struct_time:
try:
value = self[key.encode("us-ascii")]
except KeyError as e:
@@ -301,7 +306,7 @@ class PdfDict(_DictBase):
class PdfBinary:
- def __init__(self, data):
+ def __init__(self, data: list[int] | bytes) -> None:
self.data = data
def __bytes__(self) -> bytes:
@@ -309,27 +314,27 @@ class PdfBinary:
class PdfStream:
- def __init__(self, dictionary, buf):
+ def __init__(self, dictionary: PdfDict, buf: bytes) -> None:
self.dictionary = dictionary
self.buf = buf
- def decode(self):
+ def decode(self) -> bytes:
try:
- filter = self.dictionary.Filter
- except AttributeError:
+ filter = self.dictionary[b"Filter"]
+ except KeyError:
return self.buf
if filter == b"FlateDecode":
try:
- expected_length = self.dictionary.DL
- except AttributeError:
- expected_length = self.dictionary.Length
+ expected_length = self.dictionary[b"DL"]
+ except KeyError:
+ expected_length = self.dictionary[b"Length"]
return zlib.decompress(self.buf, bufsize=int(expected_length))
else:
- msg = f"stream filter {repr(self.dictionary.Filter)} unknown/unsupported"
+ msg = f"stream filter {repr(filter)} unknown/unsupported"
raise NotImplementedError(msg)
-def pdf_repr(x):
+def pdf_repr(x: Any) -> bytes:
if x is True:
return b"true"
elif x is False:
@@ -364,12 +369,19 @@ class PdfParser:
Supports PDF up to 1.4
"""
- def __init__(self, filename=None, f=None, buf=None, start_offset=0, mode="rb"):
+ def __init__(
+ self,
+ filename: str | None = None,
+ f: IO[bytes] | None = None,
+ buf: bytes | bytearray | None = None,
+ start_offset: int = 0,
+ mode: str = "rb",
+ ) -> None:
if buf and f:
msg = "specify buf or f or filename, but not both buf and f"
raise RuntimeError(msg)
self.filename = filename
- self.buf = buf
+ self.buf: bytes | bytearray | mmap.mmap | None = buf
self.f = f
self.start_offset = start_offset
self.should_close_buf = False
@@ -378,12 +390,16 @@ class PdfParser:
self.f = f = open(filename, mode)
self.should_close_file = True
if f is not None:
- self.buf = buf = self.get_buf_from_file(f)
+ self.buf = self.get_buf_from_file(f)
self.should_close_buf = True
if not filename and hasattr(f, "name"):
self.filename = f.name
- self.cached_objects = {}
- if buf:
+ self.cached_objects: dict[IndirectReference, Any] = {}
+ self.root_ref: IndirectReference | None
+ self.info_ref: IndirectReference | None
+ self.pages_ref: IndirectReference | None
+ self.last_xref_section_offset: int | None
+ if self.buf:
self.read_pdf_info()
else:
self.file_size_total = self.file_size_this = 0
@@ -391,12 +407,12 @@ class PdfParser:
self.root_ref = None
self.info = PdfDict()
self.info_ref = None
- self.page_tree_root = {}
- self.pages = []
- self.orig_pages = []
+ self.page_tree_root = PdfDict()
+ self.pages: list[IndirectReference] = []
+ self.orig_pages: list[IndirectReference] = []
self.pages_ref = None
self.last_xref_section_offset = None
- self.trailer_dict = {}
+ self.trailer_dict: dict[bytes, Any] = {}
self.xref_table = XrefTable()
self.xref_table.reading_finished = True
if f:
@@ -405,19 +421,16 @@ class PdfParser:
def __enter__(self) -> PdfParser:
return self
- def __exit__(self, exc_type, exc_value, traceback):
+ def __exit__(self, *args: object) -> None:
self.close()
- return False # do not suppress exceptions
def start_writing(self) -> None:
self.close_buf()
self.seek_end()
def close_buf(self) -> None:
- try:
+ if isinstance(self.buf, mmap.mmap):
self.buf.close()
- except AttributeError:
- pass
self.buf = None
def close(self) -> None:
@@ -428,15 +441,19 @@ class PdfParser:
self.f = None
def seek_end(self) -> None:
+ assert self.f is not None
self.f.seek(0, os.SEEK_END)
def write_header(self) -> None:
+ assert self.f is not None
self.f.write(b"%PDF-1.4\n")
- def write_comment(self, s):
+ def write_comment(self, s: str) -> None:
+ assert self.f is not None
self.f.write(f"% {s}\n".encode())
def write_catalog(self) -> IndirectReference:
+ assert self.f is not None
self.del_root()
self.root_ref = self.next_object_id(self.f.tell())
self.pages_ref = self.next_object_id(0)
@@ -479,7 +496,10 @@ class PdfParser:
pages_tree_node_ref = pages_tree_node.get(b"Parent", None)
self.orig_pages = []
- def write_xref_and_trailer(self, new_root_ref=None):
+ def write_xref_and_trailer(
+ self, new_root_ref: IndirectReference | None = None
+ ) -> None:
+ assert self.f is not None
if new_root_ref:
self.del_root()
self.root_ref = new_root_ref
@@ -487,7 +507,10 @@ class PdfParser:
self.info_ref = self.write_obj(None, self.info)
start_xref = self.xref_table.write(self.f)
num_entries = len(self.xref_table)
- trailer_dict = {b"Root": self.root_ref, b"Size": num_entries}
+ trailer_dict: dict[str | bytes, Any] = {
+ b"Root": self.root_ref,
+ b"Size": num_entries,
+ }
if self.last_xref_section_offset is not None:
trailer_dict[b"Prev"] = self.last_xref_section_offset
if self.info:
@@ -499,16 +522,20 @@ class PdfParser:
+ b"\nstartxref\n%d\n%%%%EOF" % start_xref
)
- def write_page(self, ref, *objs, **dict_obj):
- if isinstance(ref, int):
- ref = self.pages[ref]
+ def write_page(
+ self, ref: int | IndirectReference | None, *objs: Any, **dict_obj: Any
+ ) -> IndirectReference:
+ obj_ref = self.pages[ref] if isinstance(ref, int) else ref
if "Type" not in dict_obj:
dict_obj["Type"] = PdfName(b"Page")
if "Parent" not in dict_obj:
dict_obj["Parent"] = self.pages_ref
- return self.write_obj(ref, *objs, **dict_obj)
+ return self.write_obj(obj_ref, *objs, **dict_obj)
- def write_obj(self, ref, *objs, **dict_obj):
+ def write_obj(
+ self, ref: IndirectReference | None, *objs: Any, **dict_obj: Any
+ ) -> IndirectReference:
+ assert self.f is not None
f = self.f
if ref is None:
ref = self.next_object_id(f.tell())
@@ -536,7 +563,7 @@ class PdfParser:
del self.xref_table[self.root[b"Pages"].object_id]
@staticmethod
- def get_buf_from_file(f):
+ def get_buf_from_file(f: IO[bytes]) -> bytes | mmap.mmap:
if hasattr(f, "getbuffer"):
return f.getbuffer()
elif hasattr(f, "getvalue"):
@@ -548,10 +575,15 @@ class PdfParser:
return b""
def read_pdf_info(self) -> None:
+ assert self.buf is not None
self.file_size_total = len(self.buf)
self.file_size_this = self.file_size_total - self.start_offset
self.read_trailer()
+ check_format_condition(
+ self.trailer_dict.get(b"Root") is not None, "Root is missing"
+ )
self.root_ref = self.trailer_dict[b"Root"]
+ assert self.root_ref is not None
self.info_ref = self.trailer_dict.get(b"Info", None)
self.root = PdfDict(self.read_indirect(self.root_ref))
if self.info_ref is None:
@@ -562,12 +594,15 @@ class PdfParser:
check_format_condition(
self.root[b"Type"] == b"Catalog", "/Type in Root is not /Catalog"
)
- check_format_condition(b"Pages" in self.root, "/Pages missing in Root")
+ check_format_condition(
+ self.root.get(b"Pages") is not None, "/Pages missing in Root"
+ )
check_format_condition(
isinstance(self.root[b"Pages"], IndirectReference),
"/Pages in Root is not an indirect reference",
)
self.pages_ref = self.root[b"Pages"]
+ assert self.pages_ref is not None
self.page_tree_root = self.read_indirect(self.pages_ref)
self.pages = self.linearize_page_tree(self.page_tree_root)
# save the original list of page references
@@ -575,7 +610,7 @@ class PdfParser:
# and we need to rewrite the pages and their list
self.orig_pages = self.pages[:]
- def next_object_id(self, offset=None):
+ def next_object_id(self, offset: int | None = None) -> IndirectReference:
try:
# TODO: support reuse of deleted objects
reference = IndirectReference(max(self.xref_table.keys()) + 1, 0)
@@ -625,12 +660,13 @@ class PdfParser:
re.DOTALL,
)
- def read_trailer(self):
+ def read_trailer(self) -> None:
+ assert self.buf is not None
search_start_offset = len(self.buf) - 16384
if search_start_offset < self.start_offset:
search_start_offset = self.start_offset
m = self.re_trailer_end.search(self.buf, search_start_offset)
- check_format_condition(m, "trailer end not found")
+ check_format_condition(m is not None, "trailer end not found")
# make sure we found the LAST trailer
last_match = m
while m:
@@ -638,6 +674,7 @@ class PdfParser:
m = self.re_trailer_end.search(self.buf, m.start() + 16)
if not m:
m = last_match
+ assert m is not None
trailer_data = m.group(1)
self.last_xref_section_offset = int(m.group(2))
self.trailer_dict = self.interpret_trailer(trailer_data)
@@ -646,12 +683,14 @@ class PdfParser:
if b"Prev" in self.trailer_dict:
self.read_prev_trailer(self.trailer_dict[b"Prev"])
- def read_prev_trailer(self, xref_section_offset):
+ def read_prev_trailer(self, xref_section_offset: int) -> None:
+ assert self.buf is not None
trailer_offset = self.read_xref_table(xref_section_offset=xref_section_offset)
m = self.re_trailer_prev.search(
self.buf[trailer_offset : trailer_offset + 16384]
)
- check_format_condition(m, "previous trailer not found")
+ check_format_condition(m is not None, "previous trailer not found")
+ assert m is not None
trailer_data = m.group(1)
check_format_condition(
int(m.group(2)) == xref_section_offset,
@@ -672,7 +711,7 @@ class PdfParser:
re_dict_end = re.compile(whitespace_optional + rb">>" + whitespace_optional)
@classmethod
- def interpret_trailer(cls, trailer_data):
+ def interpret_trailer(cls, trailer_data: bytes) -> dict[bytes, Any]:
trailer = {}
offset = 0
while True:
@@ -680,14 +719,18 @@ class PdfParser:
if not m:
m = cls.re_dict_end.match(trailer_data, offset)
check_format_condition(
- m and m.end() == len(trailer_data),
+ m is not None and m.end() == len(trailer_data),
"name not found in trailer, remaining data: "
+ repr(trailer_data[offset:]),
)
break
key = cls.interpret_name(m.group(1))
- value, offset = cls.get_value(trailer_data, m.end())
+ assert isinstance(key, bytes)
+ value, value_offset = cls.get_value(trailer_data, m.end())
trailer[key] = value
+ if value_offset is None:
+ break
+ offset = value_offset
check_format_condition(
b"Size" in trailer and isinstance(trailer[b"Size"], int),
"/Size not in trailer or not an integer",
@@ -701,7 +744,7 @@ class PdfParser:
re_hashes_in_name = re.compile(rb"([^#]*)(#([0-9a-fA-F]{2}))?")
@classmethod
- def interpret_name(cls, raw, as_text=False):
+ def interpret_name(cls, raw: bytes, as_text: bool = False) -> str | bytes:
name = b""
for m in cls.re_hashes_in_name.finditer(raw):
if m.group(3):
@@ -763,7 +806,13 @@ class PdfParser:
)
@classmethod
- def get_value(cls, data, offset, expect_indirect=None, max_nesting=-1):
+ def get_value(
+ cls,
+ data: bytes | bytearray | mmap.mmap,
+ offset: int,
+ expect_indirect: IndirectReference | None = None,
+ max_nesting: int = -1,
+ ) -> tuple[Any, int | None]:
if max_nesting == 0:
return None, None
m = cls.re_comment.match(data, offset)
@@ -785,11 +834,16 @@ class PdfParser:
== IndirectReference(int(m.group(1)), int(m.group(2))),
"indirect object definition different than expected",
)
- object, offset = cls.get_value(data, m.end(), max_nesting=max_nesting - 1)
- if offset is None:
+ object, object_offset = cls.get_value(
+ data, m.end(), max_nesting=max_nesting - 1
+ )
+ if object_offset is None:
return object, None
- m = cls.re_indirect_def_end.match(data, offset)
- check_format_condition(m, "indirect object definition end not found")
+ m = cls.re_indirect_def_end.match(data, object_offset)
+ check_format_condition(
+ m is not None, "indirect object definition end not found"
+ )
+ assert m is not None
return object, m.end()
check_format_condition(
not expect_indirect, "indirect object definition not found"
@@ -808,46 +862,53 @@ class PdfParser:
m = cls.re_dict_start.match(data, offset)
if m:
offset = m.end()
- result = {}
+ result: dict[Any, Any] = {}
m = cls.re_dict_end.match(data, offset)
+ current_offset: int | None = offset
while not m:
- key, offset = cls.get_value(data, offset, max_nesting=max_nesting - 1)
- if offset is None:
+ assert current_offset is not None
+ key, current_offset = cls.get_value(
+ data, current_offset, max_nesting=max_nesting - 1
+ )
+ if current_offset is None:
return result, None
- value, offset = cls.get_value(data, offset, max_nesting=max_nesting - 1)
+ value, current_offset = cls.get_value(
+ data, current_offset, max_nesting=max_nesting - 1
+ )
result[key] = value
- if offset is None:
+ if current_offset is None:
return result, None
- m = cls.re_dict_end.match(data, offset)
- offset = m.end()
- m = cls.re_stream_start.match(data, offset)
+ m = cls.re_dict_end.match(data, current_offset)
+ current_offset = m.end()
+ m = cls.re_stream_start.match(data, current_offset)
if m:
- try:
- stream_len_str = result.get(b"Length")
- stream_len = int(stream_len_str)
- except (TypeError, ValueError) as e:
- msg = f"bad or missing Length in stream dict ({stream_len_str})"
- raise PdfFormatError(msg) from e
+ stream_len = result.get(b"Length")
+ if stream_len is None or not isinstance(stream_len, int):
+ msg = f"bad or missing Length in stream dict ({stream_len})"
+ raise PdfFormatError(msg)
stream_data = data[m.end() : m.end() + stream_len]
m = cls.re_stream_end.match(data, m.end() + stream_len)
- check_format_condition(m, "stream end not found")
- offset = m.end()
- result = PdfStream(PdfDict(result), stream_data)
- else:
- result = PdfDict(result)
- return result, offset
+ check_format_condition(m is not None, "stream end not found")
+ assert m is not None
+ current_offset = m.end()
+ return PdfStream(PdfDict(result), stream_data), current_offset
+ return PdfDict(result), current_offset
m = cls.re_array_start.match(data, offset)
if m:
offset = m.end()
- result = []
+ results = []
m = cls.re_array_end.match(data, offset)
+ current_offset = offset
while not m:
- value, offset = cls.get_value(data, offset, max_nesting=max_nesting - 1)
- result.append(value)
- if offset is None:
- return result, None
- m = cls.re_array_end.match(data, offset)
- return result, m.end()
+ assert current_offset is not None
+ value, current_offset = cls.get_value(
+ data, current_offset, max_nesting=max_nesting - 1
+ )
+ results.append(value)
+ if current_offset is None:
+ return results, None
+ m = cls.re_array_end.match(data, current_offset)
+ return results, m.end()
m = cls.re_null.match(data, offset)
if m:
return None, m.end()
@@ -907,7 +968,9 @@ class PdfParser:
}
@classmethod
- def get_literal_string(cls, data, offset):
+ def get_literal_string(
+ cls, data: bytes | bytearray | mmap.mmap, offset: int
+ ) -> tuple[bytes, int]:
nesting_depth = 0
result = bytearray()
for m in cls.re_lit_str_token.finditer(data, offset):
@@ -943,12 +1006,14 @@ class PdfParser:
)
re_xref_entry = re.compile(rb"([0-9]{10}) ([0-9]{5}) ([fn])( \r| \n|\r\n)")
- def read_xref_table(self, xref_section_offset):
+ def read_xref_table(self, xref_section_offset: int) -> int:
+ assert self.buf is not None
subsection_found = False
m = self.re_xref_section_start.match(
self.buf, xref_section_offset + self.start_offset
)
- check_format_condition(m, "xref section start not found")
+ check_format_condition(m is not None, "xref section start not found")
+ assert m is not None
offset = m.end()
while True:
m = self.re_xref_subsection_start.match(self.buf, offset)
@@ -963,7 +1028,8 @@ class PdfParser:
num_objects = int(m.group(2))
for i in range(first_object, first_object + num_objects):
m = self.re_xref_entry.match(self.buf, offset)
- check_format_condition(m, "xref entry not found")
+ check_format_condition(m is not None, "xref entry not found")
+ assert m is not None
offset = m.end()
is_free = m.group(3) == b"f"
if not is_free:
@@ -973,13 +1039,14 @@ class PdfParser:
self.xref_table[i] = new_entry
return offset
- def read_indirect(self, ref, max_nesting=-1):
+ def read_indirect(self, ref: IndirectReference, max_nesting: int = -1) -> Any:
offset, generation = self.xref_table[ref[0]]
check_format_condition(
generation == ref[1],
f"expected to find generation {ref[1]} for object ID {ref[0]} in xref "
f"table, instead found generation {generation} at offset {offset}",
)
+ assert self.buf is not None
value = self.get_value(
self.buf,
offset + self.start_offset,
@@ -989,14 +1056,15 @@ class PdfParser:
self.cached_objects[ref] = value
return value
- def linearize_page_tree(self, node=None):
- if node is None:
- node = self.page_tree_root
+ def linearize_page_tree(
+ self, node: PdfDict | None = None
+ ) -> list[IndirectReference]:
+ page_node = node if node is not None else self.page_tree_root
check_format_condition(
- node[b"Type"] == b"Pages", "/Type of page tree node is not /Pages"
+ page_node[b"Type"] == b"Pages", "/Type of page tree node is not /Pages"
)
pages = []
- for kid in node[b"Kids"]:
+ for kid in page_node[b"Kids"]:
kid_object = self.read_indirect(kid)
if kid_object[b"Type"] == b"Page":
pages.append(kid)
diff --git a/src/PIL/PixarImagePlugin.py b/src/PIL/PixarImagePlugin.py
index 887b6568b..5c465bbdc 100644
--- a/src/PIL/PixarImagePlugin.py
+++ b/src/PIL/PixarImagePlugin.py
@@ -61,7 +61,7 @@ class PixarImageFile(ImageFile.ImageFile):
# FIXME: to be continued...
# create tile descriptor (assuming "dumped")
- self.tile = [("raw", (0, 0) + self.size, 1024, (self.mode, 0, 1))]
+ self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, 1024, self.mode)]
#
diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py
index 3a09cb0b3..244db4545 100644
--- a/src/PIL/PngImagePlugin.py
+++ b/src/PIL/PngImagePlugin.py
@@ -38,8 +38,9 @@ import re
import struct
import warnings
import zlib
+from collections.abc import Callable
from enum import IntEnum
-from typing import IO, Any
+from typing import IO, TYPE_CHECKING, Any, NamedTuple, NoReturn, cast
from . import Image, ImageChops, ImageFile, ImagePalette, ImageSequence
from ._binary import i16be as i16
@@ -48,6 +49,9 @@ from ._binary import o8
from ._binary import o16be as o16
from ._binary import o32be as o32
+if TYPE_CHECKING:
+ from . import _imaging
+
logger = logging.getLogger(__name__)
is_cid = re.compile(rb"\w\w\w\w").match
@@ -132,16 +136,16 @@ class Blend(IntEnum):
"""
-def _safe_zlib_decompress(s):
+def _safe_zlib_decompress(s: bytes) -> bytes:
dobj = zlib.decompressobj()
plaintext = dobj.decompress(s, MAX_TEXT_CHUNK)
if dobj.unconsumed_tail:
- msg = "Decompressed Data Too Large"
+ msg = "Decompressed data too large for PngImagePlugin.MAX_TEXT_CHUNK"
raise ValueError(msg)
return plaintext
-def _crc32(data, seed=0):
+def _crc32(data: bytes, seed: int = 0) -> int:
return zlib.crc32(data, seed) & 0xFFFFFFFF
@@ -178,7 +182,7 @@ class ChunkStream:
def __enter__(self) -> ChunkStream:
return self
- def __exit__(self, *args):
+ def __exit__(self, *args: object) -> None:
self.close()
def close(self) -> None:
@@ -188,7 +192,7 @@ class ChunkStream:
assert self.queue is not None
self.queue.append((cid, pos, length))
- def call(self, cid, pos, length):
+ def call(self, cid: bytes, pos: int, length: int) -> bytes:
"""Call the appropriate chunk handler"""
logger.debug("STREAM %r %s %s", cid, pos, length)
@@ -227,6 +231,7 @@ class ChunkStream:
cids = []
+ assert self.fp is not None
while True:
try:
cid, pos, length = self.read()
@@ -249,8 +254,13 @@ class iTXt(str):
"""
+ lang: str | bytes | None
+ tkey: str | bytes | None
+
@staticmethod
- def __new__(cls, text, lang=None, tkey=None):
+ def __new__(
+ cls, text: str, lang: str | None = None, tkey: str | None = None
+ ) -> iTXt:
"""
:param cls: the class to use when creating the instance
:param text: value for this key
@@ -270,10 +280,10 @@ class PngInfo:
"""
- def __init__(self):
- self.chunks = []
+ def __init__(self) -> None:
+ self.chunks: list[tuple[bytes, bytes, bool]] = []
- def add(self, cid, data, after_idat=False):
+ def add(self, cid: bytes, data: bytes, after_idat: bool = False) -> None:
"""Appends an arbitrary chunk. Use with caution.
:param cid: a byte string, 4 bytes long.
@@ -283,12 +293,16 @@ class PngInfo:
"""
- chunk = [cid, data]
- if after_idat:
- chunk.append(True)
- self.chunks.append(tuple(chunk))
+ self.chunks.append((cid, data, after_idat))
- def add_itxt(self, key, value, lang="", tkey="", zip=False):
+ def add_itxt(
+ self,
+ key: str | bytes,
+ value: str | bytes,
+ lang: str | bytes = "",
+ tkey: str | bytes = "",
+ zip: bool = False,
+ ) -> None:
"""Appends an iTXt chunk.
:param key: latin-1 encodable text key name
@@ -316,7 +330,9 @@ class PngInfo:
else:
self.add(b"iTXt", key + b"\0\0\0" + lang + b"\0" + tkey + b"\0" + value)
- def add_text(self, key, value, zip=False):
+ def add_text(
+ self, key: str | bytes, value: str | bytes | iTXt, zip: bool = False
+ ) -> None:
"""Appends a text chunk.
:param key: latin-1 encodable text key name
@@ -326,7 +342,13 @@ class PngInfo:
"""
if isinstance(value, iTXt):
- return self.add_itxt(key, value, value.lang, value.tkey, zip=zip)
+ return self.add_itxt(
+ key,
+ value,
+ value.lang if value.lang is not None else b"",
+ value.tkey if value.tkey is not None else b"",
+ zip=zip,
+ )
# The tEXt chunk stores latin-1 text
if not isinstance(value, bytes):
@@ -348,21 +370,27 @@ class PngInfo:
# PNG image stream (IHDR/IEND)
+class _RewindState(NamedTuple):
+ info: dict[str | tuple[int, int], Any]
+ tile: list[ImageFile._Tile]
+ seq_num: int | None
+
+
class PngStream(ChunkStream):
- def __init__(self, fp):
+ def __init__(self, fp: IO[bytes]) -> None:
super().__init__(fp)
# local copies of Image attributes
- self.im_info = {}
- self.im_text = {}
+ self.im_info: dict[str | tuple[int, int], Any] = {}
+ self.im_text: dict[str, str | iTXt] = {}
self.im_size = (0, 0)
- self.im_mode = None
- self.im_tile = None
- self.im_palette = None
- self.im_custom_mimetype = None
- self.im_n_frames = None
- self._seq_num = None
- self.rewind_state = None
+ self.im_mode = ""
+ self.im_tile: list[ImageFile._Tile] = []
+ self.im_palette: tuple[str, bytes] | None = None
+ self.im_custom_mimetype: str | None = None
+ self.im_n_frames: int | None = None
+ self._seq_num: int | None = None
+ self.rewind_state = _RewindState({}, [], None)
self.text_memory = 0
@@ -376,19 +404,20 @@ class PngStream(ChunkStream):
raise ValueError(msg)
def save_rewind(self) -> None:
- self.rewind_state = {
- "info": self.im_info.copy(),
- "tile": self.im_tile,
- "seq_num": self._seq_num,
- }
+ self.rewind_state = _RewindState(
+ self.im_info.copy(),
+ self.im_tile,
+ self._seq_num,
+ )
def rewind(self) -> None:
- self.im_info = self.rewind_state["info"].copy()
- self.im_tile = self.rewind_state["tile"]
- self._seq_num = self.rewind_state["seq_num"]
+ self.im_info = self.rewind_state.info.copy()
+ self.im_tile = self.rewind_state.tile
+ self._seq_num = self.rewind_state.seq_num
def chunk_iCCP(self, pos: int, length: int) -> bytes:
# ICC profile
+ assert self.fp is not None
s = ImageFile._safe_read(self.fp, length)
# according to PNG spec, the iCCP chunk contains:
# Profile name 1-79 bytes (character string)
@@ -416,6 +445,7 @@ class PngStream(ChunkStream):
def chunk_IHDR(self, pos: int, length: int) -> bytes:
# image header
+ assert self.fp is not None
s = ImageFile._safe_read(self.fp, length)
if length < 13:
if ImageFile.LOAD_TRUNCATED_IMAGES:
@@ -434,25 +464,26 @@ class PngStream(ChunkStream):
raise SyntaxError(msg)
return s
- def chunk_IDAT(self, pos, length):
+ def chunk_IDAT(self, pos: int, length: int) -> NoReturn:
# image data
if "bbox" in self.im_info:
- tile = [("zip", self.im_info["bbox"], pos, self.im_rawmode)]
+ tile = [ImageFile._Tile("zip", self.im_info["bbox"], pos, self.im_rawmode)]
else:
if self.im_n_frames is not None:
self.im_info["default_image"] = True
- tile = [("zip", (0, 0) + self.im_size, pos, self.im_rawmode)]
+ tile = [ImageFile._Tile("zip", (0, 0) + self.im_size, pos, self.im_rawmode)]
self.im_tile = tile
self.im_idat = length
msg = "image data found"
raise EOFError(msg)
- def chunk_IEND(self, pos, length):
+ def chunk_IEND(self, pos: int, length: int) -> NoReturn:
msg = "end of PNG image"
raise EOFError(msg)
def chunk_PLTE(self, pos: int, length: int) -> bytes:
# palette
+ assert self.fp is not None
s = ImageFile._safe_read(self.fp, length)
if self.im_mode == "P":
self.im_palette = "RGB", s
@@ -460,6 +491,7 @@ class PngStream(ChunkStream):
def chunk_tRNS(self, pos: int, length: int) -> bytes:
# transparency
+ assert self.fp is not None
s = ImageFile._safe_read(self.fp, length)
if self.im_mode == "P":
if _simple_palette.match(s):
@@ -480,6 +512,7 @@ class PngStream(ChunkStream):
def chunk_gAMA(self, pos: int, length: int) -> bytes:
# gamma setting
+ assert self.fp is not None
s = ImageFile._safe_read(self.fp, length)
self.im_info["gamma"] = i32(s) / 100000.0
return s
@@ -488,8 +521,9 @@ class PngStream(ChunkStream):
# chromaticity, 8 unsigned ints, actual value is scaled by 100,000
# WP x,y, Red x,y, Green x,y Blue x,y
+ assert self.fp is not None
s = ImageFile._safe_read(self.fp, length)
- raw_vals = struct.unpack(">%dI" % (len(s) // 4), s)
+ raw_vals = struct.unpack(f">{len(s) // 4}I", s)
self.im_info["chromaticity"] = tuple(elt / 100000.0 for elt in raw_vals)
return s
@@ -500,6 +534,7 @@ class PngStream(ChunkStream):
# 2 saturation
# 3 absolute colorimetric
+ assert self.fp is not None
s = ImageFile._safe_read(self.fp, length)
if length < 1:
if ImageFile.LOAD_TRUNCATED_IMAGES:
@@ -511,6 +546,7 @@ class PngStream(ChunkStream):
def chunk_pHYs(self, pos: int, length: int) -> bytes:
# pixels per unit
+ assert self.fp is not None
s = ImageFile._safe_read(self.fp, length)
if length < 9:
if ImageFile.LOAD_TRUNCATED_IMAGES:
@@ -528,6 +564,7 @@ class PngStream(ChunkStream):
def chunk_tEXt(self, pos: int, length: int) -> bytes:
# text
+ assert self.fp is not None
s = ImageFile._safe_read(self.fp, length)
try:
k, v = s.split(b"\0", 1)
@@ -536,17 +573,18 @@ class PngStream(ChunkStream):
k = s
v = b""
if k:
- k = k.decode("latin-1", "strict")
+ k_str = k.decode("latin-1", "strict")
v_str = v.decode("latin-1", "replace")
- self.im_info[k] = v if k == "exif" else v_str
- self.im_text[k] = v_str
+ self.im_info[k_str] = v if k == b"exif" else v_str
+ self.im_text[k_str] = v_str
self.check_text_memory(len(v_str))
return s
def chunk_zTXt(self, pos: int, length: int) -> bytes:
# compressed text
+ assert self.fp is not None
s = ImageFile._safe_read(self.fp, length)
try:
k, v = s.split(b"\0", 1)
@@ -571,16 +609,17 @@ class PngStream(ChunkStream):
v = b""
if k:
- k = k.decode("latin-1", "strict")
- v = v.decode("latin-1", "replace")
+ k_str = k.decode("latin-1", "strict")
+ v_str = v.decode("latin-1", "replace")
- self.im_info[k] = self.im_text[k] = v
- self.check_text_memory(len(v))
+ self.im_info[k_str] = self.im_text[k_str] = v_str
+ self.check_text_memory(len(v_str))
return s
def chunk_iTXt(self, pos: int, length: int) -> bytes:
# international text
+ assert self.fp is not None
r = s = ImageFile._safe_read(self.fp, length)
try:
k, r = r.split(b"\0", 1)
@@ -606,26 +645,30 @@ class PngStream(ChunkStream):
return s
else:
return s
+ if k == b"XML:com.adobe.xmp":
+ self.im_info["xmp"] = v
try:
- k = k.decode("latin-1", "strict")
- lang = lang.decode("utf-8", "strict")
- tk = tk.decode("utf-8", "strict")
- v = v.decode("utf-8", "strict")
+ k_str = k.decode("latin-1", "strict")
+ lang_str = lang.decode("utf-8", "strict")
+ tk_str = tk.decode("utf-8", "strict")
+ v_str = v.decode("utf-8", "strict")
except UnicodeError:
return s
- self.im_info[k] = self.im_text[k] = iTXt(v, lang, tk)
- self.check_text_memory(len(v))
+ self.im_info[k_str] = self.im_text[k_str] = iTXt(v_str, lang_str, tk_str)
+ self.check_text_memory(len(v_str))
return s
def chunk_eXIf(self, pos: int, length: int) -> bytes:
+ assert self.fp is not None
s = ImageFile._safe_read(self.fp, length)
self.im_info["exif"] = b"Exif\x00\x00" + s
return s
# APNG chunks
def chunk_acTL(self, pos: int, length: int) -> bytes:
+ assert self.fp is not None
s = ImageFile._safe_read(self.fp, length)
if length < 8:
if ImageFile.LOAD_TRUNCATED_IMAGES:
@@ -646,6 +689,7 @@ class PngStream(ChunkStream):
return s
def chunk_fcTL(self, pos: int, length: int) -> bytes:
+ assert self.fp is not None
s = ImageFile._safe_read(self.fp, length)
if length < 26:
if ImageFile.LOAD_TRUNCATED_IMAGES:
@@ -675,6 +719,7 @@ class PngStream(ChunkStream):
return s
def chunk_fdAT(self, pos: int, length: int) -> bytes:
+ assert self.fp is not None
if length < 4:
if ImageFile.LOAD_TRUNCATED_IMAGES:
s = ImageFile._safe_read(self.fp, length)
@@ -747,7 +792,7 @@ class PngImageFile(ImageFile.ImageFile):
self._mode = self.png.im_mode
self._size = self.png.im_size
self.info = self.png.im_info
- self._text = None
+ self._text: dict[str, str | iTXt] | None = None
self.tile = self.png.im_tile
self.custom_mimetype = self.png.im_custom_mimetype
self.n_frames = self.png.im_n_frames or 1
@@ -774,7 +819,7 @@ class PngImageFile(ImageFile.ImageFile):
self.is_animated = self.n_frames > 1
@property
- def text(self):
+ def text(self) -> dict[str, str | iTXt]:
# experimental
if self._text is None:
# iTxt, tEXt and zTXt chunks may appear at the end of the file
@@ -786,6 +831,7 @@ class PngImageFile(ImageFile.ImageFile):
self.load()
if self.is_animated:
self.seek(frame)
+ assert self._text is not None
return self._text
def verify(self) -> None:
@@ -821,15 +867,17 @@ class PngImageFile(ImageFile.ImageFile):
msg = "no more images in APNG file"
raise EOFError(msg) from e
- def _seek(self, frame, rewind=False):
+ def _seek(self, frame: int, rewind: bool = False) -> None:
+ assert self.png is not None
+
+ self.dispose: _imaging.ImagingCore | None
+ dispose_extent = None
if frame == 0:
if rewind:
self._fp.seek(self.__rewind)
self.png.rewind()
self.__prepare_idat = self.__rewind_idat
- self.im = None
- if self.pyaccess:
- self.pyaccess = None
+ self._im = None
self.info = self.png.im_info
self.tile = self.png.im_tile
self.fp = self._fp
@@ -838,7 +886,7 @@ class PngImageFile(ImageFile.ImageFile):
self.default_image = self.info.get("default_image", False)
self.dispose_op = self.info.get("disposal")
self.blend_op = self.info.get("blend")
- self.dispose_extent = self.info.get("bbox")
+ dispose_extent = self.info.get("bbox")
self.__frame = 0
else:
if frame != self.__frame + 1:
@@ -896,24 +944,26 @@ class PngImageFile(ImageFile.ImageFile):
self.tile = self.png.im_tile
self.dispose_op = self.info.get("disposal")
self.blend_op = self.info.get("blend")
- self.dispose_extent = self.info.get("bbox")
+ dispose_extent = self.info.get("bbox")
if not self.tile:
msg = "image not found in APNG frame"
raise EOFError(msg)
+ if dispose_extent:
+ self.dispose_extent: tuple[float, float, float, float] = dispose_extent
# 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:
self.dispose_op = Disposal.OP_BACKGROUND
+ self.dispose = None
if self.dispose_op == Disposal.OP_PREVIOUS:
- self.dispose = self._prev_im.copy()
- self.dispose = self._crop(self.dispose, self.dispose_extent)
+ if self._prev_im:
+ self.dispose = self._prev_im.copy()
+ self.dispose = self._crop(self.dispose, self.dispose_extent)
elif self.dispose_op == Disposal.OP_BACKGROUND:
self.dispose = Image.core.fill(self.mode, self.size)
self.dispose = self._crop(self.dispose, self.dispose_extent)
- else:
- self.dispose = None
def tell(self) -> int:
return self.__frame
@@ -1013,38 +1063,29 @@ class PngImageFile(ImageFile.ImageFile):
"RGBA", self.info["transparency"]
)
else:
+ if self.im.mode == "P" and "transparency" in self.info:
+ t = self.info["transparency"]
+ if isinstance(t, bytes):
+ updated.putpalettealphas(t)
+ elif isinstance(t, int):
+ updated.putpalettealpha(t)
mask = updated.convert("RGBA")
self._prev_im.paste(updated, self.dispose_extent, mask)
self.im = self._prev_im
- if self.pyaccess:
- self.pyaccess = None
- def _getexif(self) -> dict[str, Any] | None:
+ def _getexif(self) -> dict[int, Any] | None:
if "exif" not in self.info:
self.load()
if "exif" not in self.info and "Raw profile type exif" not in self.info:
return None
return self.getexif()._get_merged_dict()
- def getexif(self):
+ def getexif(self) -> Image.Exif:
if "exif" not in self.info:
self.load()
return super().getexif()
- def getxmp(self) -> dict[str, Any]:
- """
- Returns a dictionary containing the XMP tags.
- Requires defusedxml to be installed.
-
- :returns: XMP tags in a dictionary.
- """
- return (
- self._getxmp(self.info["XML:com.adobe.xmp"])
- if "XML:com.adobe.xmp" in self.info
- else {}
- )
-
# --------------------------------------------------------------------
# PNG writer
@@ -1069,21 +1110,21 @@ _OUTMODES = {
}
-def putchunk(fp, cid, *data):
+def putchunk(fp: IO[bytes], cid: bytes, *data: bytes) -> None:
"""Write a PNG chunk (including CRC field)"""
- data = b"".join(data)
+ byte_data = b"".join(data)
- fp.write(o32(len(data)) + cid)
- fp.write(data)
- crc = _crc32(data, _crc32(cid))
+ fp.write(o32(len(byte_data)) + cid)
+ fp.write(byte_data)
+ crc = _crc32(byte_data, _crc32(cid))
fp.write(o32(crc))
class _idat:
# wrap output from the encoder in IDAT chunks
- def __init__(self, fp, chunk):
+ def __init__(self, fp: IO[bytes], chunk: Callable[..., None]) -> None:
self.fp = fp
self.chunk = chunk
@@ -1094,7 +1135,7 @@ class _idat:
class _fdat:
# wrap encoder output in fdAT chunks
- def __init__(self, fp, chunk, seq_num):
+ def __init__(self, fp: IO[bytes], chunk: Callable[..., None], seq_num: int) -> None:
self.fp = fp
self.chunk = chunk
self.seq_num = seq_num
@@ -1104,8 +1145,22 @@ class _fdat:
self.seq_num += 1
-def _write_multiple_frames(im, fp, chunk, rawmode, default_image, append_images):
- duration = im.encoderinfo.get("duration", im.info.get("duration", 0))
+class _Frame(NamedTuple):
+ im: Image.Image
+ bbox: tuple[int, int, int, int] | None
+ encoderinfo: dict[str, Any]
+
+
+def _write_multiple_frames(
+ im: Image.Image,
+ fp: IO[bytes],
+ chunk: Callable[..., None],
+ mode: str,
+ rawmode: str,
+ default_image: Image.Image | None,
+ append_images: list[Image.Image],
+) -> Image.Image | None:
+ duration = im.encoderinfo.get("duration")
loop = im.encoderinfo.get("loop", im.info.get("loop", 0))
disposal = im.encoderinfo.get("disposal", im.info.get("disposal", Disposal.OP_NONE))
blend = im.encoderinfo.get("blend", im.info.get("blend", Blend.OP_SOURCE))
@@ -1115,17 +1170,19 @@ def _write_multiple_frames(im, fp, chunk, rawmode, default_image, append_images)
else:
chain = itertools.chain([im], append_images)
- im_frames = []
+ im_frames: list[_Frame] = []
frame_count = 0
for im_seq in chain:
for im_frame in ImageSequence.Iterator(im_seq):
- if im_frame.mode == rawmode:
+ if im_frame.mode == mode:
im_frame = im_frame.copy()
else:
- im_frame = im_frame.convert(rawmode)
+ im_frame = im_frame.convert(mode)
encoderinfo = im.encoderinfo.copy()
if isinstance(duration, (list, tuple)):
encoderinfo["duration"] = duration[frame_count]
+ elif duration is None and "duration" in im_frame.info:
+ encoderinfo["duration"] = im_frame.info["duration"]
if isinstance(disposal, (list, tuple)):
encoderinfo["disposal"] = disposal[frame_count]
if isinstance(blend, (list, tuple)):
@@ -1134,24 +1191,24 @@ def _write_multiple_frames(im, fp, chunk, rawmode, default_image, append_images)
if im_frames:
previous = im_frames[-1]
- prev_disposal = previous["encoderinfo"].get("disposal")
- prev_blend = previous["encoderinfo"].get("blend")
+ prev_disposal = previous.encoderinfo.get("disposal")
+ prev_blend = previous.encoderinfo.get("blend")
if prev_disposal == Disposal.OP_PREVIOUS and len(im_frames) < 2:
prev_disposal = Disposal.OP_BACKGROUND
if prev_disposal == Disposal.OP_BACKGROUND:
- base_im = previous["im"].copy()
+ base_im = previous.im.copy()
dispose = Image.core.fill("RGBA", im.size, (0, 0, 0, 0))
- bbox = previous["bbox"]
+ bbox = previous.bbox
if bbox:
dispose = dispose.crop(bbox)
else:
bbox = (0, 0) + im.size
base_im.paste(dispose, bbox)
elif prev_disposal == Disposal.OP_PREVIOUS:
- base_im = im_frames[-2]["im"]
+ base_im = im_frames[-2].im
else:
- base_im = previous["im"]
+ base_im = previous.im
delta = ImageChops.subtract_modulo(
im_frame.convert("RGBA"), base_im.convert("RGBA")
)
@@ -1160,19 +1217,16 @@ def _write_multiple_frames(im, fp, chunk, rawmode, default_image, append_images)
not bbox
and prev_disposal == encoderinfo.get("disposal")
and prev_blend == encoderinfo.get("blend")
+ and "duration" in encoderinfo
):
- previous["encoderinfo"]["duration"] += encoderinfo.get(
- "duration", duration
- )
+ previous.encoderinfo["duration"] += encoderinfo["duration"]
continue
else:
bbox = None
- if "duration" not in encoderinfo:
- encoderinfo["duration"] = duration
- im_frames.append({"im": im_frame, "bbox": bbox, "encoderinfo": encoderinfo})
+ im_frames.append(_Frame(im_frame, bbox, encoderinfo))
if len(im_frames) == 1 and not default_image:
- return im_frames[0]["im"]
+ return im_frames[0].im
# animation control
chunk(
@@ -1184,21 +1238,25 @@ def _write_multiple_frames(im, fp, chunk, rawmode, default_image, append_images)
# default image IDAT (if it exists)
if default_image:
- if im.mode != rawmode:
- im = im.convert(rawmode)
- ImageFile._save(im, _idat(fp, chunk), [("zip", (0, 0) + im.size, 0, rawmode)])
+ if im.mode != mode:
+ im = im.convert(mode)
+ ImageFile._save(
+ im,
+ cast(IO[bytes], _idat(fp, chunk)),
+ [ImageFile._Tile("zip", (0, 0) + im.size, 0, rawmode)],
+ )
seq_num = 0
for frame, frame_data in enumerate(im_frames):
- im_frame = frame_data["im"]
- if not frame_data["bbox"]:
+ im_frame = frame_data.im
+ if not frame_data.bbox:
bbox = (0, 0) + im_frame.size
else:
- bbox = frame_data["bbox"]
+ bbox = frame_data.bbox
im_frame = im_frame.crop(bbox)
size = im_frame.size
- encoderinfo = frame_data["encoderinfo"]
- frame_duration = int(round(encoderinfo["duration"]))
+ encoderinfo = frame_data.encoderinfo
+ frame_duration = int(round(encoderinfo.get("duration", 0)))
frame_disposal = encoderinfo.get("disposal", disposal)
frame_blend = encoderinfo.get("blend", blend)
# frame control
@@ -1221,24 +1279,31 @@ def _write_multiple_frames(im, fp, chunk, rawmode, default_image, append_images)
# first frame must be in IDAT chunks for backwards compatibility
ImageFile._save(
im_frame,
- _idat(fp, chunk),
- [("zip", (0, 0) + im_frame.size, 0, rawmode)],
+ cast(IO[bytes], _idat(fp, chunk)),
+ [ImageFile._Tile("zip", (0, 0) + im_frame.size, 0, rawmode)],
)
else:
fdat_chunks = _fdat(fp, chunk, seq_num)
ImageFile._save(
im_frame,
- fdat_chunks,
- [("zip", (0, 0) + im_frame.size, 0, rawmode)],
+ cast(IO[bytes], fdat_chunks),
+ [ImageFile._Tile("zip", (0, 0) + im_frame.size, 0, rawmode)],
)
seq_num = fdat_chunks.seq_num
+ return None
-def _save_all(im: Image.Image, fp: IO[bytes], filename: str) -> None:
+def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
_save(im, fp, filename, save_all=True)
-def _save(im, fp, filename, chunk=putchunk, save_all=False):
+def _save(
+ im: Image.Image,
+ fp: IO[bytes],
+ filename: str | bytes,
+ chunk: Callable[..., None] = putchunk,
+ save_all: bool = False,
+) -> None:
# save an image to disk (called by the save method)
if save_all:
@@ -1262,6 +1327,7 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False):
size = im.size
mode = im.mode
+ outmode = mode
if mode == "P":
#
# attempt to minimize storage requirements for palette images
@@ -1282,7 +1348,7 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False):
bits = 2
else:
bits = 4
- mode = f"{mode};{bits}"
+ outmode += f";{bits}"
# encoder options
im.encoderconfig = (
@@ -1294,7 +1360,7 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False):
# get the corresponding PNG mode
try:
- rawmode, bit_depth, color_type = _OUTMODES[mode]
+ rawmode, bit_depth, color_type = _OUTMODES[outmode]
except KeyError as e:
msg = f"cannot write mode {mode} as PNG"
raise OSError(msg) from e
@@ -1346,7 +1412,7 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False):
chunk(fp, cid, data)
elif cid[1:2].islower():
# Private chunk
- after_idat = info_chunk[2:3]
+ after_idat = len(info_chunk) == 3 and info_chunk[2]
if not after_idat:
chunk(fp, cid, data)
@@ -1413,19 +1479,24 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False):
exif = exif[6:]
chunk(fp, b"eXIf", exif)
+ single_im: Image.Image | None = im
if save_all:
- im = _write_multiple_frames(
- im, fp, chunk, rawmode, default_image, append_images
+ single_im = _write_multiple_frames(
+ im, fp, chunk, mode, rawmode, default_image, append_images
+ )
+ if single_im:
+ ImageFile._save(
+ single_im,
+ cast(IO[bytes], _idat(fp, chunk)),
+ [ImageFile._Tile("zip", (0, 0) + single_im.size, 0, rawmode)],
)
- if im:
- ImageFile._save(im, _idat(fp, chunk), [("zip", (0, 0) + im.size, 0, rawmode)])
if info:
for info_chunk in info.chunks:
cid, data = info_chunk[:2]
if cid[1:2].islower():
# Private chunk
- after_idat = info_chunk[2:3]
+ after_idat = len(info_chunk) == 3 and info_chunk[2]
if after_idat:
chunk(fp, cid, data)
@@ -1439,32 +1510,26 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False):
# PNG chunk converter
-def getchunks(im, **params):
+def getchunks(im: Image.Image, **params: Any) -> list[tuple[bytes, bytes, bytes]]:
"""Return a list of PNG chunks representing this image."""
+ from io import BytesIO
- class collector:
- data = []
+ chunks = []
- def write(self, data: bytes) -> None:
- pass
+ def append(fp: IO[bytes], cid: bytes, *data: bytes) -> None:
+ byte_data = b"".join(data)
+ crc = o32(_crc32(byte_data, _crc32(cid)))
+ chunks.append((cid, byte_data, crc))
- def append(self, chunk: bytes) -> None:
- self.data.append(chunk)
-
- def append(fp, cid, *data):
- data = b"".join(data)
- crc = o32(_crc32(data, _crc32(cid)))
- fp.append((cid, data, crc))
-
- fp = collector()
+ fp = BytesIO()
try:
im.encoderinfo = params
- _save(im, fp, None, append)
+ _save(im, fp, "", append)
finally:
del im.encoderinfo
- return fp.data
+ return chunks
def _supported_modes():
diff --git a/src/PIL/PpmImagePlugin.py b/src/PIL/PpmImagePlugin.py
index 94bf430b8..4e779df17 100644
--- a/src/PIL/PpmImagePlugin.py
+++ b/src/PIL/PpmImagePlugin.py
@@ -151,7 +151,9 @@ class PpmImageFile(ImageFile.ImageFile):
decoder_name = "ppm"
args = rawmode if decoder_name == "raw" else (rawmode, maxval)
- self.tile = [(decoder_name, (0, 0) + self.size, self.fp.tell(), args)]
+ self.tile = [
+ ImageFile._Tile(decoder_name, (0, 0) + self.size, self.fp.tell(), args)
+ ]
#
@@ -282,7 +284,7 @@ class PpmPlainDecoder(ImageFile.PyDecoder):
break
return data
- def decode(self, buffer: bytes) -> tuple[int, int]:
+ def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int]:
self._comment_spans = False
if self.mode == "1":
data = self._decode_bitonal()
@@ -298,7 +300,7 @@ class PpmPlainDecoder(ImageFile.PyDecoder):
class PpmDecoder(ImageFile.PyDecoder):
_pulls_fd = True
- def decode(self, buffer: bytes) -> tuple[int, int]:
+ def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int]:
assert self.fd is not None
data = bytearray()
@@ -328,12 +330,12 @@ class PpmDecoder(ImageFile.PyDecoder):
# --------------------------------------------------------------------
-def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None:
+def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
if im.mode == "1":
rawmode, head = "1;I", b"P4"
elif im.mode == "L":
rawmode, head = "L", b"P5"
- elif im.mode == "I":
+ elif im.mode in ("I", "I;16"):
rawmode, head = "I;16B", b"P5"
elif im.mode in ("RGB", "RGBA"):
rawmode, head = "RGB", b"P6"
@@ -353,7 +355,9 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None:
elif head == b"Pf":
fp.write(b"-1.0\n")
row_order = -1 if im.mode == "F" else 1
- ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, row_order))])
+ ImageFile._save(
+ im, fp, [ImageFile._Tile("raw", (0, 0) + im.size, 0, (rawmode, 0, row_order))]
+ )
#
diff --git a/src/PIL/PsdImagePlugin.py b/src/PIL/PsdImagePlugin.py
index 86c1a6763..8ff5e3908 100644
--- a/src/PIL/PsdImagePlugin.py
+++ b/src/PIL/PsdImagePlugin.py
@@ -18,6 +18,8 @@
from __future__ import annotations
import io
+from functools import cached_property
+from typing import IO
from . import Image, ImageFile, ImagePalette
from ._binary import i8
@@ -118,18 +120,17 @@ class PsdImageFile(ImageFile.ImageFile):
#
# layer and mask information
- self.layers = []
+ self._layers_position = None
size = i32(read(4))
if size:
end = self.fp.tell() + size
size = i32(read(4))
if size:
- _layer_data = io.BytesIO(ImageFile._safe_read(self.fp, size))
- self.layers = _layerinfo(_layer_data, size)
+ self._layers_position = self.fp.tell()
+ self._layers_size = size
self.fp.seek(end)
- self.n_frames = len(self.layers)
- self.is_animated = self.n_frames > 1
+ self._n_frames: int | None = None
#
# image descriptor
@@ -141,6 +142,28 @@ class PsdImageFile(ImageFile.ImageFile):
self.frame = 1
self._min_frame = 1
+ @cached_property
+ def layers(
+ self,
+ ) -> list[tuple[str, str, tuple[int, int, int, int], list[ImageFile._Tile]]]:
+ layers = []
+ if self._layers_position is not None:
+ self._fp.seek(self._layers_position)
+ _layer_data = io.BytesIO(ImageFile._safe_read(self._fp, self._layers_size))
+ layers = _layerinfo(_layer_data, self._layers_size)
+ self._n_frames = len(layers)
+ return layers
+
+ @property
+ def n_frames(self) -> int:
+ if self._n_frames is None:
+ self._n_frames = len(self.layers)
+ return self._n_frames
+
+ @property
+ def is_animated(self) -> bool:
+ return len(self.layers) > 1
+
def seek(self, layer: int) -> None:
if not self._seek_check(layer):
return
@@ -161,11 +184,13 @@ class PsdImageFile(ImageFile.ImageFile):
return self.frame
-def _layerinfo(fp, ct_bytes):
+def _layerinfo(
+ fp: IO[bytes], ct_bytes: int
+) -> list[tuple[str, str, tuple[int, int, int, int], list[ImageFile._Tile]]]:
# read layerinfo block
layers = []
- def read(size):
+ def read(size: int) -> bytes:
return ImageFile._safe_read(fp, size)
ct = si16(read(2))
@@ -183,7 +208,7 @@ def _layerinfo(fp, ct_bytes):
x1 = si32(read(4))
# image info
- mode = []
+ bands = []
ct_types = i16(read(2))
if ct_types > 4:
fp.seek(ct_types * 6 + 12, io.SEEK_CUR)
@@ -195,23 +220,23 @@ def _layerinfo(fp, ct_bytes):
type = i16(read(2))
if type == 65535:
- m = "A"
+ b = "A"
else:
- m = "RGBA"[type]
+ b = "RGBA"[type]
- mode.append(m)
+ bands.append(b)
read(4) # size
# figure out the image mode
- mode.sort()
- if mode == ["R"]:
+ bands.sort()
+ if bands == ["R"]:
mode = "L"
- elif mode == ["B", "G", "R"]:
+ elif bands == ["B", "G", "R"]:
mode = "RGB"
- elif mode == ["A", "B", "G", "R"]:
+ elif bands == ["A", "B", "G", "R"]:
mode = "RGBA"
else:
- mode = None # unknown
+ mode = "" # unknown
# skip over blend flags and extra information
read(12) # filler
@@ -238,19 +263,22 @@ def _layerinfo(fp, ct_bytes):
layers.append((name, mode, (x0, y0, x1, y1)))
# get tiles
+ layerinfo = []
for i, (name, mode, bbox) in enumerate(layers):
tile = []
for m in mode:
t = _maketile(fp, m, bbox, 1)
if t:
tile.extend(t)
- layers[i] = name, mode, bbox, tile
+ layerinfo.append((name, mode, bbox, tile))
- return layers
+ return layerinfo
-def _maketile(file, mode, bbox, channels):
- tile = None
+def _maketile(
+ file: IO[bytes], mode: str, bbox: tuple[int, int, int, int], channels: int
+) -> list[ImageFile._Tile]:
+ tiles = []
read = file.read
compression = i16(read(2))
@@ -263,26 +291,24 @@ def _maketile(file, mode, bbox, channels):
if compression == 0:
#
# raw compression
- tile = []
for channel in range(channels):
layer = mode[channel]
if mode == "CMYK":
layer += ";I"
- tile.append(("raw", bbox, offset, layer))
+ tiles.append(ImageFile._Tile("raw", bbox, offset, layer))
offset = offset + xsize * ysize
elif compression == 1:
#
# packbits compression
i = 0
- tile = []
bytecount = read(channels * ysize * 2)
offset = file.tell()
for channel in range(channels):
layer = mode[channel]
if mode == "CMYK":
layer += ";I"
- tile.append(("packbits", bbox, offset, layer))
+ tiles.append(ImageFile._Tile("packbits", bbox, offset, layer))
for y in range(ysize):
offset = offset + i16(bytecount, i)
i += 2
@@ -292,7 +318,7 @@ def _maketile(file, mode, bbox, channels):
if offset & 1:
read(1) # padding
- return tile
+ return tiles
# --------------------------------------------------------------------
diff --git a/src/PIL/PyAccess.py b/src/PIL/PyAccess.py
deleted file mode 100644
index fe12cb641..000000000
--- a/src/PIL/PyAccess.py
+++ /dev/null
@@ -1,376 +0,0 @@
-#
-# The Python Imaging Library
-# Pillow fork
-#
-# Python implementation of the PixelAccess Object
-#
-# Copyright (c) 1997-2009 by Secret Labs AB. All rights reserved.
-# Copyright (c) 1995-2009 by Fredrik Lundh.
-# Copyright (c) 2013 Eric Soroos
-#
-# See the README file for information on usage and redistribution
-#
-
-# Notes:
-#
-# * Implements the pixel access object following Access.c
-# * Taking only the tuple form, which is used from python.
-# * 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
-from typing import TYPE_CHECKING
-
-from ._deprecate import deprecate
-
-FFI: type
-try:
- from cffi import FFI
-
- defs = """
- struct Pixel_RGBA {
- unsigned char r,g,b,a;
- };
- struct Pixel_I16 {
- unsigned char l,r;
- };
- """
- ffi = FFI()
- ffi.cdef(defs)
-except ImportError as ex:
- # Allow error import for doc purposes, but error out when accessing
- # anything in core.
- from ._util import DeferredError
-
- FFI = ffi = DeferredError.new(ex)
-
-logger = logging.getLogger(__name__)
-
-if TYPE_CHECKING:
- from . import Image
-
-
-class PyAccess:
- def __init__(self, img: Image.Image, readonly: bool = False) -> None:
- deprecate("PyAccess", 11)
- vals = dict(img.im.unsafe_ptrs)
- self.readonly = readonly
- self.image8 = ffi.cast("unsigned char **", vals["image8"])
- self.image32 = ffi.cast("int **", vals["image32"])
- self.image = ffi.cast("unsigned char **", vals["image"])
- self.xsize, self.ysize = img.im.size
- self._img = img
-
- # Keep pointer to im object to prevent dereferencing.
- self._im = img.im
- if self._im.mode in ("P", "PA"):
- self._palette = img.palette
-
- # Debugging is polluting test traces, only useful here
- # when hacking on PyAccess
- # logger.debug("%s", vals)
- self._post_init()
-
- def _post_init(self) -> None:
- pass
-
- def __setitem__(self, xy, color):
- """
- Modifies the pixel at x,y. The color is given as a single
- numerical value for single band images, and a tuple for
- multi-band images. In addition to this, RGB and RGBA tuples
- are accepted for P and PA images.
-
- :param xy: The pixel coordinate, given as (x, y). See
- :ref:`coordinate-system`.
- :param color: The pixel value.
- """
- if self.readonly:
- msg = "Attempt to putpixel a read only image"
- raise ValueError(msg)
- (x, y) = xy
- if x < 0:
- x = self.xsize + x
- if y < 0:
- y = self.ysize + y
- (x, y) = self.check_xy((x, y))
-
- if (
- self._im.mode in ("P", "PA")
- and isinstance(color, (list, tuple))
- and len(color) in [3, 4]
- ):
- # RGB or RGBA value for a P or PA image
- if self._im.mode == "PA":
- alpha = color[3] if len(color) == 4 else 255
- color = color[:3]
- color = self._palette.getcolor(color, self._img)
- if self._im.mode == "PA":
- color = (color, alpha)
-
- return self.set_pixel(x, y, color)
-
- def __getitem__(self, xy: tuple[int, int]) -> float | tuple[int, ...]:
- """
- Returns the pixel at x,y. The pixel is returned as a single
- value for single band images or a tuple for multiple band
- images
-
- :param xy: The pixel coordinate, given as (x, y). See
- :ref:`coordinate-system`.
- :returns: a pixel value for single band images, a tuple of
- pixel values for multiband images.
- """
- (x, y) = xy
- if x < 0:
- x = self.xsize + x
- if y < 0:
- y = self.ysize + y
- (x, y) = self.check_xy((x, y))
- return self.get_pixel(x, y)
-
- putpixel = __setitem__
- getpixel = __getitem__
-
- def check_xy(self, xy: tuple[int, int]) -> tuple[int, int]:
- (x, y) = xy
- if not (0 <= x < self.xsize and 0 <= y < self.ysize):
- msg = "pixel location out of range"
- raise ValueError(msg)
- return xy
-
- def get_pixel(self, x: int, y: int) -> float | tuple[int, ...]:
- raise NotImplementedError()
-
- def set_pixel(self, x: int, y: int, color: float | tuple[int, ...]) -> None:
- raise NotImplementedError()
-
-
-class _PyAccess32_2(PyAccess):
- """PA, LA, stored in first and last bytes of a 32 bit word"""
-
- def _post_init(self, *args, **kwargs):
- self.pixels = ffi.cast("struct Pixel_RGBA **", self.image32)
-
- def get_pixel(self, x: int, y: int) -> tuple[int, int]:
- pixel = self.pixels[y][x]
- return pixel.r, pixel.a
-
- def set_pixel(self, x, y, color):
- pixel = self.pixels[y][x]
- # tuple
- pixel.r = min(color[0], 255)
- pixel.a = min(color[1], 255)
-
-
-class _PyAccess32_3(PyAccess):
- """RGB and friends, stored in the first three bytes of a 32 bit word"""
-
- def _post_init(self, *args, **kwargs):
- self.pixels = ffi.cast("struct Pixel_RGBA **", self.image32)
-
- def get_pixel(self, x: int, y: int) -> tuple[int, int, int]:
- pixel = self.pixels[y][x]
- return pixel.r, pixel.g, pixel.b
-
- def set_pixel(self, x, y, color):
- pixel = self.pixels[y][x]
- # tuple
- pixel.r = min(color[0], 255)
- pixel.g = min(color[1], 255)
- pixel.b = min(color[2], 255)
- pixel.a = 255
-
-
-class _PyAccess32_4(PyAccess):
- """RGBA etc, all 4 bytes of a 32 bit word"""
-
- def _post_init(self, *args, **kwargs):
- self.pixels = ffi.cast("struct Pixel_RGBA **", self.image32)
-
- def get_pixel(self, x: int, y: int) -> tuple[int, int, int, int]:
- pixel = self.pixels[y][x]
- return pixel.r, pixel.g, pixel.b, pixel.a
-
- def set_pixel(self, x, y, color):
- pixel = self.pixels[y][x]
- # tuple
- pixel.r = min(color[0], 255)
- pixel.g = min(color[1], 255)
- pixel.b = min(color[2], 255)
- pixel.a = min(color[3], 255)
-
-
-class _PyAccess8(PyAccess):
- """1, L, P, 8 bit images stored as uint8"""
-
- def _post_init(self, *args, **kwargs):
- self.pixels = self.image8
-
- def get_pixel(self, x: int, y: int) -> int:
- return self.pixels[y][x]
-
- def set_pixel(self, x, y, color):
- try:
- # integer
- self.pixels[y][x] = min(color, 255)
- except TypeError:
- # tuple
- self.pixels[y][x] = min(color[0], 255)
-
-
-class _PyAccessI16_N(PyAccess):
- """I;16 access, native bitendian without conversion"""
-
- def _post_init(self, *args, **kwargs):
- self.pixels = ffi.cast("unsigned short **", self.image)
-
- def get_pixel(self, x: int, y: int) -> int:
- return self.pixels[y][x]
-
- def set_pixel(self, x, y, color):
- try:
- # integer
- self.pixels[y][x] = min(color, 65535)
- except TypeError:
- # tuple
- self.pixels[y][x] = min(color[0], 65535)
-
-
-class _PyAccessI16_L(PyAccess):
- """I;16L access, with conversion"""
-
- def _post_init(self, *args, **kwargs):
- self.pixels = ffi.cast("struct Pixel_I16 **", self.image)
-
- def get_pixel(self, x: int, y: int) -> int:
- pixel = self.pixels[y][x]
- return pixel.l + pixel.r * 256
-
- def set_pixel(self, x, y, color):
- pixel = self.pixels[y][x]
- try:
- color = min(color, 65535)
- except TypeError:
- color = min(color[0], 65535)
-
- pixel.l = color & 0xFF
- pixel.r = color >> 8
-
-
-class _PyAccessI16_B(PyAccess):
- """I;16B access, with conversion"""
-
- def _post_init(self, *args, **kwargs):
- self.pixels = ffi.cast("struct Pixel_I16 **", self.image)
-
- def get_pixel(self, x: int, y: int) -> int:
- pixel = self.pixels[y][x]
- return pixel.l * 256 + pixel.r
-
- def set_pixel(self, x, y, color):
- pixel = self.pixels[y][x]
- try:
- color = min(color, 65535)
- except Exception:
- color = min(color[0], 65535)
-
- pixel.l = color >> 8
- pixel.r = color & 0xFF
-
-
-class _PyAccessI32_N(PyAccess):
- """Signed Int32 access, native endian"""
-
- def _post_init(self, *args, **kwargs):
- self.pixels = self.image32
-
- def get_pixel(self, x: int, y: int) -> int:
- return self.pixels[y][x]
-
- def set_pixel(self, x, y, color):
- self.pixels[y][x] = color
-
-
-class _PyAccessI32_Swap(PyAccess):
- """I;32L/B access, with byteswapping conversion"""
-
- def _post_init(self, *args, **kwargs):
- self.pixels = self.image32
-
- def reverse(self, i):
- orig = ffi.new("int *", i)
- chars = ffi.cast("unsigned char *", orig)
- chars[0], chars[1], chars[2], chars[3] = chars[3], chars[2], chars[1], chars[0]
- return ffi.cast("int *", chars)[0]
-
- def get_pixel(self, x: int, y: int) -> int:
- return self.reverse(self.pixels[y][x])
-
- def set_pixel(self, x, y, color):
- self.pixels[y][x] = self.reverse(color)
-
-
-class _PyAccessF(PyAccess):
- """32 bit float access"""
-
- def _post_init(self, *args, **kwargs):
- self.pixels = ffi.cast("float **", self.image32)
-
- def get_pixel(self, x: int, y: int) -> float:
- return self.pixels[y][x]
-
- def set_pixel(self, x, y, color):
- try:
- # not a tuple
- self.pixels[y][x] = color
- except TypeError:
- # tuple
- self.pixels[y][x] = color[0]
-
-
-mode_map = {
- "1": _PyAccess8,
- "L": _PyAccess8,
- "P": _PyAccess8,
- "I;16N": _PyAccessI16_N,
- "LA": _PyAccess32_2,
- "La": _PyAccess32_2,
- "PA": _PyAccess32_2,
- "RGB": _PyAccess32_3,
- "LAB": _PyAccess32_3,
- "HSV": _PyAccess32_3,
- "YCbCr": _PyAccess32_3,
- "RGBA": _PyAccess32_4,
- "RGBa": _PyAccess32_4,
- "RGBX": _PyAccess32_4,
- "CMYK": _PyAccess32_4,
- "F": _PyAccessF,
- "I": _PyAccessI32_N,
-}
-
-if sys.byteorder == "little":
- mode_map["I;16"] = _PyAccessI16_N
- mode_map["I;16L"] = _PyAccessI16_N
- mode_map["I;16B"] = _PyAccessI16_B
-
- mode_map["I;32L"] = _PyAccessI32_N
- mode_map["I;32B"] = _PyAccessI32_Swap
-else:
- mode_map["I;16"] = _PyAccessI16_L
- mode_map["I;16L"] = _PyAccessI16_L
- mode_map["I;16B"] = _PyAccessI16_N
-
- mode_map["I;32L"] = _PyAccessI32_Swap
- mode_map["I;32B"] = _PyAccessI32_N
-
-
-def new(img: Image.Image, readonly: bool = False) -> PyAccess | None:
- access_type = mode_map.get(img.mode, None)
- if not access_type:
- logger.debug("PyAccess Not Implemented: %s", img.mode)
- return None
- return access_type(img, readonly)
diff --git a/src/PIL/QoiImagePlugin.py b/src/PIL/QoiImagePlugin.py
index f2cf06d0d..01cc868b2 100644
--- a/src/PIL/QoiImagePlugin.py
+++ b/src/PIL/QoiImagePlugin.py
@@ -26,17 +26,19 @@ class QoiImageFile(ImageFile.ImageFile):
msg = "not a QOI file"
raise SyntaxError(msg)
- self._size = tuple(i32(self.fp.read(4)) for i in range(2))
+ self._size = i32(self.fp.read(4)), i32(self.fp.read(4))
channels = self.fp.read(1)[0]
self._mode = "RGB" if channels == 3 else "RGBA"
self.fp.seek(1, os.SEEK_CUR) # colorspace
- self.tile = [("qoi", (0, 0) + self._size, self.fp.tell(), None)]
+ self.tile = [ImageFile._Tile("qoi", (0, 0) + self._size, self.fp.tell())]
class QoiDecoder(ImageFile.PyDecoder):
_pulls_fd = True
+ _previous_pixel: bytes | bytearray | None = None
+ _previously_seen_pixels: dict[int, bytes | bytearray] = {}
def _add_to_previous_pixels(self, value: bytes | bytearray) -> None:
self._previous_pixel = value
@@ -45,9 +47,10 @@ class QoiDecoder(ImageFile.PyDecoder):
hash_value = (r * 3 + g * 5 + b * 7 + a * 11) % 64
self._previously_seen_pixels[hash_value] = value
- def decode(self, buffer):
+ def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int]:
+ assert self.fd is not None
+
self._previously_seen_pixels = {}
- self._previous_pixel = None
self._add_to_previous_pixels(bytearray((0, 0, 0, 255)))
data = bytearray()
@@ -55,7 +58,8 @@ class QoiDecoder(ImageFile.PyDecoder):
dest_length = self.state.xsize * self.state.ysize * bands
while len(data) < dest_length:
byte = self.fd.read(1)[0]
- if byte == 0b11111110: # QOI_OP_RGB
+ value: bytes | bytearray
+ if byte == 0b11111110 and self._previous_pixel: # QOI_OP_RGB
value = bytearray(self.fd.read(3)) + self._previous_pixel[3:]
elif byte == 0b11111111: # QOI_OP_RGBA
value = self.fd.read(4)
@@ -66,7 +70,7 @@ class QoiDecoder(ImageFile.PyDecoder):
value = self._previously_seen_pixels.get(
op_index, bytearray((0, 0, 0, 0))
)
- elif op == 1: # QOI_OP_DIFF
+ elif op == 1 and self._previous_pixel: # QOI_OP_DIFF
value = bytearray(
(
(self._previous_pixel[0] + ((byte & 0b00110000) >> 4) - 2)
@@ -77,7 +81,7 @@ class QoiDecoder(ImageFile.PyDecoder):
self._previous_pixel[3],
)
)
- elif op == 2: # QOI_OP_LUMA
+ elif op == 2 and self._previous_pixel: # QOI_OP_LUMA
second_byte = self.fd.read(1)[0]
diff_green = (byte & 0b00111111) - 32
diff_red = ((second_byte & 0b11110000) >> 4) - 8
@@ -90,7 +94,7 @@ class QoiDecoder(ImageFile.PyDecoder):
)
)
value += self._previous_pixel[3:]
- elif op == 3: # QOI_OP_RUN
+ elif op == 3 and self._previous_pixel: # QOI_OP_RUN
run_length = (byte & 0b00111111) + 1
value = self._previous_pixel
if bands == 3:
diff --git a/src/PIL/SgiImagePlugin.py b/src/PIL/SgiImagePlugin.py
index 7bd84ebd4..44254b7a4 100644
--- a/src/PIL/SgiImagePlugin.py
+++ b/src/PIL/SgiImagePlugin.py
@@ -109,23 +109,32 @@ class SgiImageFile(ImageFile.ImageFile):
pagesize = xsize * ysize * bpc
if bpc == 2:
self.tile = [
- ("SGI16", (0, 0) + self.size, headlen, (self.mode, 0, orientation))
+ ImageFile._Tile(
+ "SGI16",
+ (0, 0) + self.size,
+ headlen,
+ (self.mode, 0, orientation),
+ )
]
else:
self.tile = []
offset = headlen
for layer in self.mode:
self.tile.append(
- ("raw", (0, 0) + self.size, offset, (layer, 0, orientation))
+ ImageFile._Tile(
+ "raw", (0, 0) + self.size, offset, (layer, 0, orientation)
+ )
)
offset += pagesize
elif compression == 1:
self.tile = [
- ("sgi_rle", (0, 0) + self.size, headlen, (rawmode, orientation, bpc))
+ ImageFile._Tile(
+ "sgi_rle", (0, 0) + self.size, headlen, (rawmode, orientation, bpc)
+ )
]
-def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None:
+def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
if im.mode not in {"RGB", "RGBA", "L"}:
msg = "Unsupported SGI image mode"
raise ValueError(msg)
@@ -171,8 +180,9 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None:
# Maximum Byte value (255 = 8bits per pixel)
pinmax = 255
# Image name (79 characters max, truncated below in write)
- filename = os.path.basename(filename)
- img_name = os.path.splitext(filename)[0].encode("ascii", "ignore")
+ img_name = os.path.splitext(os.path.basename(filename))[0]
+ if isinstance(img_name, str):
+ img_name = img_name.encode("ascii", "ignore")
# Standard representation of pixel in the file
colormap = 0
fp.write(struct.pack(">h", magic_number))
@@ -204,7 +214,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None:
class SGI16Decoder(ImageFile.PyDecoder):
_pulls_fd = True
- def decode(self, buffer: bytes) -> tuple[int, int]:
+ def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int]:
assert self.fd is not None
assert self.im is not None
diff --git a/src/PIL/SpiderImagePlugin.py b/src/PIL/SpiderImagePlugin.py
index 98dd91c0e..3a87d009a 100644
--- a/src/PIL/SpiderImagePlugin.py
+++ b/src/PIL/SpiderImagePlugin.py
@@ -37,12 +37,12 @@ from __future__ import annotations
import os
import struct
import sys
-from typing import IO, TYPE_CHECKING
+from typing import IO, TYPE_CHECKING, Any, cast
from . import Image, ImageFile
-def isInt(f):
+def isInt(f: Any) -> int:
try:
i = int(f)
if f - i == 0:
@@ -62,7 +62,7 @@ iforms = [1, 3, -11, -12, -21, -22]
# otherwise returns 0
-def isSpiderHeader(t):
+def isSpiderHeader(t: tuple[float, ...]) -> int:
h = (99,) + t # add 1 value so can use spider header index start=1
# header values 1,2,5,12,13,22,23 should be integers
for i in [1, 2, 5, 12, 13, 22, 23]:
@@ -82,7 +82,7 @@ def isSpiderHeader(t):
return labbyt
-def isSpiderImage(filename):
+def isSpiderImage(filename: str) -> int:
with open(filename, "rb") as fp:
f = fp.read(92) # read 23 * 4 bytes
t = struct.unpack(">23f", f) # try big-endian first
@@ -154,7 +154,7 @@ class SpiderImageFile(ImageFile.ImageFile):
self.rawmode = "F;32F"
self._mode = "F"
- self.tile = [("raw", (0, 0) + self.size, offset, (self.rawmode, 0, 1))]
+ self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, offset, self.rawmode)]
self._fp = self.fp # FIXME: hack
@property
@@ -184,13 +184,15 @@ class SpiderImageFile(ImageFile.ImageFile):
self._open()
# returns a byte image after rescaling to 0..255
- def convert2byte(self, depth=255):
- (minimum, maximum) = self.getextrema()
- m = 1
+ def convert2byte(self, depth: int = 255) -> Image.Image:
+ extrema = self.getextrema()
+ assert isinstance(extrema[0], float)
+ minimum, maximum = cast(tuple[float, float], extrema)
+ m: float = 1
if maximum != minimum:
m = depth / (maximum - minimum)
b = -m * minimum
- return self.point(lambda i, m=m, b=b: i * m + b).convert("L")
+ return self.point(lambda i: i * m + b).convert("L")
if TYPE_CHECKING:
from . import ImageTk
@@ -207,26 +209,27 @@ class SpiderImageFile(ImageFile.ImageFile):
# given a list of filenames, return a list of images
-def loadImageSeries(filelist=None):
+def loadImageSeries(filelist: list[str] | None = None) -> list[Image.Image] | None:
"""create a list of :py:class:`~PIL.Image.Image` objects for use in a montage"""
if filelist is None or len(filelist) < 1:
- return
+ return None
- imglist = []
+ byte_imgs = []
for img in filelist:
if not os.path.exists(img):
print(f"unable to find {img}")
continue
try:
with Image.open(img) as im:
- im = im.convert2byte()
+ assert isinstance(im, SpiderImageFile)
+ byte_im = im.convert2byte()
except Exception:
if not isSpiderImage(img):
print(f"{img} is not a Spider image file")
continue
- im.info["filename"] = img
- imglist.append(im)
- return imglist
+ byte_im.info["filename"] = img
+ byte_imgs.append(byte_im)
+ return byte_imgs
# --------------------------------------------------------------------
@@ -263,7 +266,7 @@ def makeSpiderHeader(im: Image.Image) -> list[bytes]:
return [struct.pack("f", v) for v in hdr]
-def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None:
+def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
if im.mode[0] != "F":
im = im.convert("F")
@@ -276,12 +279,13 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None:
fp.writelines(hdr)
rawmode = "F;32NF" # 32-bit native floating point
- ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, 1))])
+ ImageFile._save(im, fp, [ImageFile._Tile("raw", (0, 0) + im.size, 0, rawmode)])
-def _save_spider(im: Image.Image, fp: IO[bytes], filename: str) -> None:
+def _save_spider(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
# get the filename extension and register it with Image
- ext = os.path.splitext(filename)[1]
+ filename_ext = os.path.splitext(filename)[1]
+ ext = filename_ext.decode() if isinstance(filename_ext, bytes) else filename_ext
Image.register_extension(SpiderImageFile.format, ext)
_save(im, fp, filename)
diff --git a/src/PIL/SunImagePlugin.py b/src/PIL/SunImagePlugin.py
index 4e098474a..8912379ea 100644
--- a/src/PIL/SunImagePlugin.py
+++ b/src/PIL/SunImagePlugin.py
@@ -124,9 +124,13 @@ class SunImageFile(ImageFile.ImageFile):
# (https://www.fileformat.info/format/sunraster/egff.htm)
if file_type in (0, 1, 3, 4, 5):
- self.tile = [("raw", (0, 0) + self.size, offset, (rawmode, stride))]
+ self.tile = [
+ ImageFile._Tile("raw", (0, 0) + self.size, offset, (rawmode, stride))
+ ]
elif file_type == 2:
- self.tile = [("sun_rle", (0, 0) + self.size, offset, rawmode)]
+ self.tile = [
+ ImageFile._Tile("sun_rle", (0, 0) + self.size, offset, rawmode)
+ ]
else:
msg = "Unsupported Sun Raster file type"
raise SyntaxError(msg)
diff --git a/src/PIL/TarIO.py b/src/PIL/TarIO.py
index 7470663b4..779288b1c 100644
--- a/src/PIL/TarIO.py
+++ b/src/PIL/TarIO.py
@@ -16,7 +16,6 @@
from __future__ import annotations
import io
-from types import TracebackType
from . import ContainerIO
@@ -56,18 +55,3 @@ class TarIO(ContainerIO.ContainerIO[bytes]):
# Open region
super().__init__(self.fh, self.fh.tell(), size)
-
- # Context manager support
- def __enter__(self) -> TarIO:
- return self
-
- def __exit__(
- self,
- exc_type: type[BaseException] | None,
- exc_val: BaseException | None,
- exc_tb: TracebackType | None,
- ) -> None:
- self.close()
-
- def close(self) -> None:
- self.fh.close()
diff --git a/src/PIL/TgaImagePlugin.py b/src/PIL/TgaImagePlugin.py
index 401a83f9f..90d5b5cf4 100644
--- a/src/PIL/TgaImagePlugin.py
+++ b/src/PIL/TgaImagePlugin.py
@@ -36,7 +36,7 @@ MODES = {
(3, 1): "1",
(3, 8): "L",
(3, 16): "LA",
- (2, 16): "BGR;5",
+ (2, 16): "BGRA;15Z",
(2, 24): "BGR",
(2, 32): "BGRA",
}
@@ -87,9 +87,7 @@ class TgaImageFile(ImageFile.ImageFile):
elif imagetype in (1, 9):
self._mode = "P" if colormaptype else "L"
elif imagetype in (2, 10):
- self._mode = "RGB"
- if depth == 32:
- self._mode = "RGBA"
+ self._mode = "RGB" if depth == 24 else "RGBA"
else:
msg = "unknown TGA mode"
raise SyntaxError(msg)
@@ -118,15 +116,16 @@ class TgaImageFile(ImageFile.ImageFile):
start, size, mapdepth = i16(s, 3), i16(s, 5), s[7]
if mapdepth == 16:
self.palette = ImagePalette.raw(
- "BGR;15", b"\0" * 2 * start + self.fp.read(2 * size)
+ "BGRA;15Z", bytes(2 * start) + self.fp.read(2 * size)
)
+ self.palette.mode = "RGBA"
elif mapdepth == 24:
self.palette = ImagePalette.raw(
- "BGR", b"\0" * 3 * start + self.fp.read(3 * size)
+ "BGR", bytes(3 * start) + self.fp.read(3 * size)
)
elif mapdepth == 32:
self.palette = ImagePalette.raw(
- "BGRA", b"\0" * 4 * start + self.fp.read(4 * size)
+ "BGRA", bytes(4 * start) + self.fp.read(4 * size)
)
else:
msg = "unknown TGA map depth"
@@ -138,7 +137,7 @@ class TgaImageFile(ImageFile.ImageFile):
if imagetype & 8:
# compressed
self.tile = [
- (
+ ImageFile._Tile(
"tga_rle",
(0, 0) + self.size,
self.fp.tell(),
@@ -147,7 +146,7 @@ class TgaImageFile(ImageFile.ImageFile):
]
else:
self.tile = [
- (
+ ImageFile._Tile(
"raw",
(0, 0) + self.size,
self.fp.tell(),
@@ -159,7 +158,6 @@ class TgaImageFile(ImageFile.ImageFile):
def load_end(self) -> None:
if self._flip_horizontally:
- assert self.im is not None
self.im = self.im.transpose(Image.Transpose.FLIP_LEFT_RIGHT)
@@ -178,7 +176,7 @@ SAVE = {
}
-def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None:
+def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
try:
rawmode, bits, colormaptype, imagetype = SAVE[im.mode]
except KeyError as e:
@@ -201,7 +199,6 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None:
warnings.warn("id_section has been trimmed to 255 characters")
if colormaptype:
- assert im.im is not None
palette = im.im.getpalette("RGB", "BGR")
colormaplength, colormapentry = len(palette) // 3, 24
else:
@@ -239,11 +236,15 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None:
if rle:
ImageFile._save(
- im, fp, [("tga_rle", (0, 0) + im.size, 0, (rawmode, orientation))]
+ im,
+ fp,
+ [ImageFile._Tile("tga_rle", (0, 0) + im.size, 0, (rawmode, orientation))],
)
else:
ImageFile._save(
- im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, orientation))]
+ im,
+ fp,
+ [ImageFile._Tile("raw", (0, 0) + im.size, 0, (rawmode, 0, orientation))],
)
# write targa version 2 footer
diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py
index 0b9601755..16c521bea 100644
--- a/src/PIL/TiffImagePlugin.py
+++ b/src/PIL/TiffImagePlugin.py
@@ -47,24 +47,28 @@ import math
import os
import struct
import warnings
-from collections.abc import MutableMapping
+from collections.abc import Iterator, MutableMapping
from fractions import Fraction
from numbers import Number, Rational
-from typing import IO, TYPE_CHECKING, Any, Callable
+from typing import IO, TYPE_CHECKING, Any, Callable, NoReturn, cast
from . import ExifTags, Image, ImageFile, ImageOps, ImagePalette, TiffTags
from ._binary import i16be as i16
from ._binary import i32be as i32
from ._binary import o8
from ._deprecate import deprecate
+from ._typing import StrOrBytesPath
+from ._util import is_path
from .TiffTags import TYPES
+if TYPE_CHECKING:
+ from ._typing import Buffer, IntegralLike
+
logger = logging.getLogger(__name__)
# Set these to true to force use of libtiff for reading or writing.
READ_LIBTIFF = False
WRITE_LIBTIFF = False
-IFD_LEGACY_API = True
STRIP_SIZE = 65536
II = b"II" # little-endian (Intel style)
@@ -201,12 +205,12 @@ OPEN_INFO = {
(MM, 2, (1,), 2, (8, 8, 8), ()): ("RGB", "RGB;R"),
(II, 2, (1,), 1, (8, 8, 8, 8), ()): ("RGBA", "RGBA"), # missing ExtraSamples
(MM, 2, (1,), 1, (8, 8, 8, 8), ()): ("RGBA", "RGBA"), # missing ExtraSamples
- (II, 2, (1,), 1, (8, 8, 8, 8), (0,)): ("RGBX", "RGBX"),
- (MM, 2, (1,), 1, (8, 8, 8, 8), (0,)): ("RGBX", "RGBX"),
- (II, 2, (1,), 1, (8, 8, 8, 8, 8), (0, 0)): ("RGBX", "RGBXX"),
- (MM, 2, (1,), 1, (8, 8, 8, 8, 8), (0, 0)): ("RGBX", "RGBXX"),
- (II, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0, 0)): ("RGBX", "RGBXXX"),
- (MM, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0, 0)): ("RGBX", "RGBXXX"),
+ (II, 2, (1,), 1, (8, 8, 8, 8), (0,)): ("RGB", "RGBX"),
+ (MM, 2, (1,), 1, (8, 8, 8, 8), (0,)): ("RGB", "RGBX"),
+ (II, 2, (1,), 1, (8, 8, 8, 8, 8), (0, 0)): ("RGB", "RGBXX"),
+ (MM, 2, (1,), 1, (8, 8, 8, 8, 8), (0, 0)): ("RGB", "RGBXX"),
+ (II, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0, 0)): ("RGB", "RGBXXX"),
+ (MM, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0, 0)): ("RGB", "RGBXXX"),
(II, 2, (1,), 1, (8, 8, 8, 8), (1,)): ("RGBA", "RGBa"),
(MM, 2, (1,), 1, (8, 8, 8, 8), (1,)): ("RGBA", "RGBa"),
(II, 2, (1,), 1, (8, 8, 8, 8, 8), (1, 0)): ("RGBA", "RGBaX"),
@@ -225,8 +229,8 @@ OPEN_INFO = {
(MM, 2, (1,), 1, (16, 16, 16), ()): ("RGB", "RGB;16B"),
(II, 2, (1,), 1, (16, 16, 16, 16), ()): ("RGBA", "RGBA;16L"),
(MM, 2, (1,), 1, (16, 16, 16, 16), ()): ("RGBA", "RGBA;16B"),
- (II, 2, (1,), 1, (16, 16, 16, 16), (0,)): ("RGBX", "RGBX;16L"),
- (MM, 2, (1,), 1, (16, 16, 16, 16), (0,)): ("RGBX", "RGBX;16B"),
+ (II, 2, (1,), 1, (16, 16, 16, 16), (0,)): ("RGB", "RGBX;16L"),
+ (MM, 2, (1,), 1, (16, 16, 16, 16), (0,)): ("RGB", "RGBX;16B"),
(II, 2, (1,), 1, (16, 16, 16, 16), (1,)): ("RGBA", "RGBa;16L"),
(MM, 2, (1,), 1, (16, 16, 16, 16), (1,)): ("RGBA", "RGBa;16B"),
(II, 2, (1,), 1, (16, 16, 16, 16), (2,)): ("RGBA", "RGBA;16L"),
@@ -257,6 +261,7 @@ OPEN_INFO = {
(II, 5, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0)): ("CMYK", "CMYKXX"),
(MM, 5, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0)): ("CMYK", "CMYKXX"),
(II, 5, (1,), 1, (16, 16, 16, 16), ()): ("CMYK", "CMYK;16L"),
+ (MM, 5, (1,), 1, (16, 16, 16, 16), ()): ("CMYK", "CMYK;16B"),
(II, 6, (1,), 1, (8,), ()): ("L", "L"),
(MM, 6, (1,), 1, (8,), ()): ("L", "L"),
# JPEG compressed images handled by LibTiff and auto-converted to RGBX
@@ -286,22 +291,26 @@ def _accept(prefix: bytes) -> bool:
return prefix[:4] in PREFIXES
-def _limit_rational(val, max_val):
+def _limit_rational(
+ val: float | Fraction | IFDRational, max_val: int
+) -> tuple[IntegralLike, IntegralLike]:
inv = abs(val) > 1
n_d = IFDRational(1 / val if inv else val).limit_rational(max_val)
return n_d[::-1] if inv else n_d
-def _limit_signed_rational(val, max_val, min_val):
+def _limit_signed_rational(
+ val: IFDRational, max_val: int, min_val: int
+) -> tuple[IntegralLike, IntegralLike]:
frac = Fraction(val)
- n_d = frac.numerator, frac.denominator
+ n_d: tuple[IntegralLike, IntegralLike] = frac.numerator, frac.denominator
- if min(n_d) < min_val:
+ if min(float(i) for i in n_d) < min_val:
n_d = _limit_rational(val, abs(min_val))
- if max(n_d) > max_val:
- val = Fraction(*n_d)
- n_d = _limit_rational(val, max_val)
+ n_d_float = tuple(float(i) for i in n_d)
+ if max(n_d_float) > max_val:
+ n_d = _limit_rational(n_d_float[0] / n_d_float[1], max_val)
return n_d
@@ -313,8 +322,10 @@ _load_dispatch = {}
_write_dispatch = {}
-def _delegate(op):
- def delegate(self, *args):
+def _delegate(op: str) -> Any:
+ def delegate(
+ self: IFDRational, *args: tuple[float, ...]
+ ) -> bool | float | Fraction:
return getattr(self._val, op)(*args)
return delegate
@@ -334,12 +345,15 @@ class IFDRational(Rational):
__slots__ = ("_numerator", "_denominator", "_val")
- def __init__(self, value, denominator=1):
+ def __init__(
+ self, value: float | Fraction | IFDRational, denominator: int = 1
+ ) -> None:
"""
:param value: either an integer numerator, a
float/rational/other number, or an IFDRational
:param denominator: Optional integer denominator
"""
+ self._val: Fraction | float
if isinstance(value, IFDRational):
self._numerator = value.numerator
self._denominator = value.denominator
@@ -350,25 +364,30 @@ class IFDRational(Rational):
self._numerator = value.numerator
self._denominator = value.denominator
else:
- self._numerator = value
+ if TYPE_CHECKING:
+ self._numerator = cast(IntegralLike, value)
+ else:
+ self._numerator = value
self._denominator = denominator
if denominator == 0:
self._val = float("nan")
elif denominator == 1:
self._val = Fraction(value)
+ elif int(value) == value:
+ self._val = Fraction(int(value), denominator)
else:
- self._val = Fraction(value, denominator)
+ self._val = Fraction(value / denominator)
@property
- def numerator(self):
+ def numerator(self) -> IntegralLike:
return self._numerator
@property
- def denominator(self):
+ def denominator(self) -> int:
return self._denominator
- def limit_rational(self, max_denominator):
+ def limit_rational(self, max_denominator: int) -> tuple[IntegralLike, int]:
"""
:param max_denominator: Integer, the maximum denominator value
@@ -378,16 +397,17 @@ class IFDRational(Rational):
if self.denominator == 0:
return self.numerator, self.denominator
+ assert isinstance(self._val, Fraction)
f = self._val.limit_denominator(max_denominator)
return f.numerator, f.denominator
def __repr__(self) -> str:
return str(float(self._val))
- def __hash__(self):
+ def __hash__(self) -> int:
return self._val.__hash__()
- def __eq__(self, other):
+ def __eq__(self, other: object) -> bool:
val = self._val
if isinstance(other, IFDRational):
other = other._val
@@ -395,14 +415,19 @@ class IFDRational(Rational):
val = float(val)
return val == other
- def __getstate__(self):
+ def __getstate__(self) -> list[float | Fraction | IntegralLike]:
return [self._val, self._numerator, self._denominator]
- def __setstate__(self, state):
+ def __setstate__(self, state: list[float | Fraction | IntegralLike]) -> None:
IFDRational.__init__(self, 0)
_val, _numerator, _denominator = state
+ assert isinstance(_val, (float, Fraction))
self._val = _val
- self._numerator = _numerator
+ if TYPE_CHECKING:
+ self._numerator = cast(IntegralLike, _numerator)
+ else:
+ self._numerator = _numerator
+ assert isinstance(_denominator, int)
self._denominator = _denominator
""" a = ['add','radd', 'sub', 'rsub', 'mul', 'rmul',
@@ -444,8 +469,11 @@ class IFDRational(Rational):
__int__ = _delegate("__int__")
-def _register_loader(idx, size):
- def decorator(func):
+_LoaderFunc = Callable[["ImageFileDirectory_v2", bytes, bool], Any]
+
+
+def _register_loader(idx: int, size: int) -> Callable[[_LoaderFunc], _LoaderFunc]:
+ def decorator(func: _LoaderFunc) -> _LoaderFunc:
from .TiffTags import TYPES
if func.__name__.startswith("load_"):
@@ -456,26 +484,27 @@ def _register_loader(idx, size):
return decorator
-def _register_writer(idx):
- def decorator(func):
+def _register_writer(idx: int) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
+ def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
_write_dispatch[idx] = func # noqa: F821
return func
return decorator
-def _register_basic(idx_fmt_name):
+def _register_basic(idx_fmt_name: tuple[int, str, str]) -> None:
from .TiffTags import TYPES
idx, fmt, name = idx_fmt_name
TYPES[idx] = name
size = struct.calcsize(f"={fmt}")
- _load_dispatch[idx] = ( # noqa: F821
- size,
- lambda self, data, legacy_api=True: (
- self._unpack(f"{len(data) // size}{fmt}", data)
- ),
- )
+
+ def basic_handler(
+ self: ImageFileDirectory_v2, data: bytes, legacy_api: bool = True
+ ) -> tuple[Any, ...]:
+ return self._unpack(f"{len(data) // size}{fmt}", data)
+
+ _load_dispatch[idx] = size, basic_handler # noqa: F821
_write_dispatch[idx] = lambda self, *values: ( # noqa: F821
b"".join(self._pack(fmt, value) for value in values)
)
@@ -548,10 +577,15 @@ class ImageFileDirectory_v2(_IFDv2Base):
"""
- _load_dispatch: dict[int, Callable[[ImageFileDirectory_v2, bytes, bool], Any]] = {}
+ _load_dispatch: dict[int, tuple[int, _LoaderFunc]] = {}
_write_dispatch: dict[int, Callable[..., Any]] = {}
- def __init__(self, ifh=b"II\052\0\0\0\0\0", prefix=None, group=None):
+ def __init__(
+ self,
+ ifh: bytes = b"II\052\0\0\0\0\0",
+ prefix: bytes | None = None,
+ group: int | None = None,
+ ) -> None:
"""Initialize an ImageFileDirectory.
To construct an ImageFileDirectory from a real file, pass the 8-byte
@@ -575,11 +609,13 @@ class ImageFileDirectory_v2(_IFDv2Base):
raise SyntaxError(msg)
self._bigtiff = ifh[2] == 43
self.group = group
- self.tagtype = {}
+ self.tagtype: dict[int, int] = {}
""" Dictionary of tag types """
self.reset()
- (self.next,) = (
- self._unpack("Q", ifh[8:]) if self._bigtiff else self._unpack("L", ifh[4:])
+ self.next = (
+ self._unpack("Q", ifh[8:])[0]
+ if self._bigtiff
+ else self._unpack("L", ifh[4:])[0]
)
self._legacy_api = False
@@ -587,26 +623,26 @@ class ImageFileDirectory_v2(_IFDv2Base):
offset = property(lambda self: self._offset)
@property
- def legacy_api(self):
+ def legacy_api(self) -> bool:
return self._legacy_api
@legacy_api.setter
- def legacy_api(self, value):
+ def legacy_api(self, value: bool) -> NoReturn:
msg = "Not allowing setting of legacy api"
raise Exception(msg)
- def reset(self):
- self._tags_v1 = {} # will remain empty if legacy_api is false
- self._tags_v2 = {} # main tag storage
- self._tagdata = {}
+ def reset(self) -> None:
+ self._tags_v1: dict[int, Any] = {} # will remain empty if legacy_api is false
+ self._tags_v2: dict[int, Any] = {} # main tag storage
+ self._tagdata: dict[int, bytes] = {}
self.tagtype = {} # added 2008-06-05 by Florian Hoech
self._next = None
- self._offset = None
+ self._offset: int | None = None
def __str__(self) -> str:
return str(dict(self))
- def named(self):
+ def named(self) -> dict[str, Any]:
"""
:returns: dict of name|key: value
@@ -620,7 +656,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
def __len__(self) -> int:
return len(set(self._tagdata) | set(self._tags_v2))
- def __getitem__(self, tag):
+ def __getitem__(self, tag: int) -> Any:
if tag not in self._tags_v2: # unpack on the fly
data = self._tagdata[tag]
typ = self.tagtype[tag]
@@ -631,13 +667,13 @@ class ImageFileDirectory_v2(_IFDv2Base):
val = (val,)
return val
- def __contains__(self, tag):
+ def __contains__(self, tag: object) -> bool:
return tag in self._tags_v2 or tag in self._tagdata
- def __setitem__(self, tag, value):
+ def __setitem__(self, tag: int, value: Any) -> None:
self._setitem(tag, value, self.legacy_api)
- def _setitem(self, tag, value, legacy_api):
+ def _setitem(self, tag: int, value: Any, legacy_api: bool) -> None:
basetypes = (Number, bytes, str)
info = TiffTags.lookup(tag, self.group)
@@ -649,22 +685,33 @@ class ImageFileDirectory_v2(_IFDv2Base):
else:
self.tagtype[tag] = TiffTags.UNDEFINED
if all(isinstance(v, IFDRational) for v in values):
- self.tagtype[tag] = (
- TiffTags.RATIONAL
- if all(v >= 0 for v in values)
- else TiffTags.SIGNED_RATIONAL
- )
- elif all(isinstance(v, int) for v in values):
- if all(0 <= v < 2**16 for v in values):
- self.tagtype[tag] = TiffTags.SHORT
- elif all(-(2**15) < v < 2**15 for v in values):
- self.tagtype[tag] = TiffTags.SIGNED_SHORT
+ for v in values:
+ assert isinstance(v, IFDRational)
+ if v < 0:
+ self.tagtype[tag] = TiffTags.SIGNED_RATIONAL
+ break
else:
- self.tagtype[tag] = (
- TiffTags.LONG
- if all(v >= 0 for v in values)
- else TiffTags.SIGNED_LONG
- )
+ self.tagtype[tag] = TiffTags.RATIONAL
+ elif all(isinstance(v, int) for v in values):
+ short = True
+ signed_short = True
+ long = True
+ for v in values:
+ assert isinstance(v, int)
+ if short and not (0 <= v < 2**16):
+ short = False
+ if signed_short and not (-(2**15) < v < 2**15):
+ signed_short = False
+ if long and v < 0:
+ long = False
+ if short:
+ self.tagtype[tag] = TiffTags.SHORT
+ elif signed_short:
+ self.tagtype[tag] = TiffTags.SIGNED_SHORT
+ elif long:
+ self.tagtype[tag] = TiffTags.LONG
+ else:
+ self.tagtype[tag] = TiffTags.SIGNED_LONG
elif all(isinstance(v, float) for v in values):
self.tagtype[tag] = TiffTags.DOUBLE
elif all(isinstance(v, str) for v in values):
@@ -682,7 +729,10 @@ class ImageFileDirectory_v2(_IFDv2Base):
is_ifd = self.tagtype[tag] == TiffTags.LONG and isinstance(values, dict)
if not is_ifd:
- values = tuple(info.cvt_enum(value) for value in values)
+ values = tuple(
+ info.cvt_enum(value) if isinstance(value, str) else value
+ for value in values
+ )
dest = self._tags_v1 if legacy_api else self._tags_v2
@@ -717,18 +767,18 @@ class ImageFileDirectory_v2(_IFDv2Base):
# Unspec'd, and length > 1
dest[tag] = values
- def __delitem__(self, tag):
+ def __delitem__(self, tag: int) -> None:
self._tags_v2.pop(tag, None)
self._tags_v1.pop(tag, None)
self._tagdata.pop(tag, None)
- def __iter__(self):
+ def __iter__(self) -> Iterator[int]:
return iter(set(self._tagdata) | set(self._tags_v2))
- def _unpack(self, fmt, data):
+ def _unpack(self, fmt: str, data: bytes) -> tuple[Any, ...]:
return struct.unpack(self._endian + fmt, data)
- def _pack(self, fmt, *values):
+ def _pack(self, fmt: str, *values: Any) -> bytes:
return struct.pack(self._endian + fmt, *values)
list(
@@ -749,11 +799,11 @@ class ImageFileDirectory_v2(_IFDv2Base):
)
@_register_loader(1, 1) # Basic type, except for the legacy API.
- def load_byte(self, data, legacy_api=True):
+ def load_byte(self, data: bytes, legacy_api: bool = True) -> bytes:
return data
@_register_writer(1) # Basic type, except for the legacy API.
- def write_byte(self, data):
+ def write_byte(self, data: bytes | int | IFDRational) -> bytes:
if isinstance(data, IFDRational):
data = int(data)
if isinstance(data, int):
@@ -761,13 +811,13 @@ class ImageFileDirectory_v2(_IFDv2Base):
return data
@_register_loader(2, 1)
- def load_string(self, data, legacy_api=True):
+ def load_string(self, data: bytes, legacy_api: bool = True) -> str:
if data.endswith(b"\0"):
data = data[:-1]
return data.decode("latin-1", "replace")
@_register_writer(2)
- def write_string(self, value):
+ def write_string(self, value: str | bytes | int) -> bytes:
# remerge of https://github.com/python-pillow/Pillow/pull/1416
if isinstance(value, int):
value = str(value)
@@ -776,26 +826,28 @@ class ImageFileDirectory_v2(_IFDv2Base):
return value + b"\0"
@_register_loader(5, 8)
- def load_rational(self, data, legacy_api=True):
+ def load_rational(
+ self, data: bytes, legacy_api: bool = True
+ ) -> tuple[tuple[int, int] | IFDRational, ...]:
vals = self._unpack(f"{len(data) // 4}L", data)
- def combine(a, b):
+ def combine(a: int, b: int) -> tuple[int, int] | IFDRational:
return (a, b) if legacy_api else IFDRational(a, b)
return tuple(combine(num, denom) for num, denom in zip(vals[::2], vals[1::2]))
@_register_writer(5)
- def write_rational(self, *values):
+ def write_rational(self, *values: IFDRational) -> bytes:
return b"".join(
self._pack("2L", *_limit_rational(frac, 2**32 - 1)) for frac in values
)
@_register_loader(7, 1)
- def load_undefined(self, data, legacy_api=True):
+ def load_undefined(self, data: bytes, legacy_api: bool = True) -> bytes:
return data
@_register_writer(7)
- def write_undefined(self, value):
+ def write_undefined(self, value: bytes | int | IFDRational) -> bytes:
if isinstance(value, IFDRational):
value = int(value)
if isinstance(value, int):
@@ -803,22 +855,24 @@ class ImageFileDirectory_v2(_IFDv2Base):
return value
@_register_loader(10, 8)
- def load_signed_rational(self, data, legacy_api=True):
+ def load_signed_rational(
+ self, data: bytes, legacy_api: bool = True
+ ) -> tuple[tuple[int, int] | IFDRational, ...]:
vals = self._unpack(f"{len(data) // 4}l", data)
- def combine(a, b):
+ def combine(a: int, b: int) -> tuple[int, int] | IFDRational:
return (a, b) if legacy_api else IFDRational(a, b)
return tuple(combine(num, denom) for num, denom in zip(vals[::2], vals[1::2]))
@_register_writer(10)
- def write_signed_rational(self, *values):
+ def write_signed_rational(self, *values: IFDRational) -> bytes:
return b"".join(
self._pack("2l", *_limit_signed_rational(frac, 2**31 - 1, -(2**31)))
for frac in values
)
- def _ensure_read(self, fp, size):
+ def _ensure_read(self, fp: IO[bytes], size: int) -> bytes:
ret = fp.read(size)
if len(ret) != size:
msg = (
@@ -828,7 +882,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
raise OSError(msg)
return ret
- def load(self, fp):
+ def load(self, fp: IO[bytes]) -> None:
self.reset()
self._offset = fp.tell()
@@ -881,9 +935,9 @@ class ImageFileDirectory_v2(_IFDv2Base):
self._tagdata[tag] = data
self.tagtype[tag] = typ
- msg += " - value: " + (
- "" % size if size > 32 else repr(data)
- )
+ msg += " - value: "
+ msg += f"" if size > 32 else repr(data)
+
logger.debug(msg)
(self.next,) = (
@@ -895,11 +949,11 @@ class ImageFileDirectory_v2(_IFDv2Base):
warnings.warn(str(msg))
return
- def tobytes(self, offset=0):
+ def tobytes(self, offset: int = 0) -> bytes:
# FIXME What about tagdata?
result = self._pack("H", len(self._tags_v2))
- entries = []
+ entries: list[tuple[int, int, int, bytes, bytes]] = []
offset = offset + len(result) + len(self._tags_v2) * 12 + 4
stripoffsets = None
@@ -908,7 +962,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
for tag, value in sorted(self._tags_v2.items()):
if tag == STRIPOFFSETS:
stripoffsets = len(entries)
- typ = self.tagtype.get(tag)
+ typ = self.tagtype[tag]
logger.debug("Tag %s, Type: %s, Value: %s", tag, typ, repr(value))
is_ifd = typ == TiffTags.LONG and isinstance(value, dict)
if is_ifd:
@@ -927,10 +981,8 @@ class ImageFileDirectory_v2(_IFDv2Base):
tagname = TiffTags.lookup(tag, self.group).name
typname = "ifd" if is_ifd else TYPES.get(typ, "unknown")
- msg = f"save: {tagname} ({tag}) - type: {typname} ({typ})"
- msg += " - value: " + (
- "" % len(data) if len(data) >= 16 else str(values)
- )
+ msg = f"save: {tagname} ({tag}) - type: {typname} ({typ}) - value: "
+ msg += f"" if len(data) >= 16 else str(values)
logger.debug(msg)
# count is sum of lengths for string and arbitrary data
@@ -951,9 +1003,11 @@ class ImageFileDirectory_v2(_IFDv2Base):
if stripoffsets is not None:
tag, typ, count, value, data = entries[stripoffsets]
if data:
- msg = "multistrip support not yet implemented"
- raise NotImplementedError(msg)
- value = self._pack("L", self._unpack("L", value)[0] + offset)
+ size, handler = self._load_dispatch[typ]
+ values = [val + offset for val in handler(self, data, self.legacy_api)]
+ data = self._write_dispatch[typ](self, *values)
+ else:
+ value = self._pack("L", self._unpack("L", value)[0] + offset)
entries[stripoffsets] = tag, typ, count, value, data
# pass 2: write entries to file
@@ -972,7 +1026,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
return result
- def save(self, fp):
+ def save(self, fp: IO[bytes]) -> int:
if fp.tell() == 0: # skip TIFF header on subsequent pages
# tiff header -- PIL always starts the first IFD at offset 8
fp.write(self._prefix + self._pack("HL", 42, 8))
@@ -1012,7 +1066,7 @@ class ImageFileDirectory_v1(ImageFileDirectory_v2):
.. deprecated:: 3.0.0
"""
- def __init__(self, *args, **kwargs):
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
super().__init__(*args, **kwargs)
self._legacy_api = True
@@ -1024,7 +1078,7 @@ class ImageFileDirectory_v1(ImageFileDirectory_v2):
"""Dictionary of tag types"""
@classmethod
- def from_v2(cls, original):
+ def from_v2(cls, original: ImageFileDirectory_v2) -> ImageFileDirectory_v1:
"""Returns an
:py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v1`
instance with the same data as is contained in the original
@@ -1058,20 +1112,20 @@ class ImageFileDirectory_v1(ImageFileDirectory_v2):
ifd._tags_v2 = dict(self._tags_v2)
return ifd
- def __contains__(self, tag):
+ def __contains__(self, tag: object) -> bool:
return tag in self._tags_v1 or tag in self._tagdata
def __len__(self) -> int:
return len(set(self._tagdata) | set(self._tags_v1))
- def __iter__(self):
+ def __iter__(self) -> Iterator[int]:
return iter(set(self._tagdata) | set(self._tags_v1))
- def __setitem__(self, tag, value):
+ def __setitem__(self, tag: int, value: Any) -> None:
for legacy_api in (False, True):
self._setitem(tag, value, legacy_api)
- def __getitem__(self, tag):
+ def __getitem__(self, tag: int) -> Any:
if tag not in self._tags_v1: # unpack on the fly
data = self._tagdata[tag]
typ = self.tagtype[tag]
@@ -1084,7 +1138,7 @@ class ImageFileDirectory_v1(ImageFileDirectory_v2):
return val
-# undone -- switch this pointer when IFD_LEGACY_API == False
+# undone -- switch this pointer
ImageFileDirectory = ImageFileDirectory_v1
@@ -1097,16 +1151,20 @@ class TiffImageFile(ImageFile.ImageFile):
format_description = "Adobe TIFF"
_close_exclusive_fp_after_loading = False
- def __init__(self, fp=None, filename=None):
- self.tag_v2 = None
+ def __init__(
+ self,
+ fp: StrOrBytesPath | IO[bytes],
+ filename: str | bytes | None = None,
+ ) -> None:
+ self.tag_v2: ImageFileDirectory_v2
""" Image file directory (tag dictionary) """
- self.tag = None
+ self.tag: ImageFileDirectory_v1
""" Legacy tag entries """
super().__init__(fp, filename)
- def _open(self):
+ def _open(self) -> None:
"""Open the first image in a TIFF file"""
# Header
@@ -1116,15 +1174,12 @@ class TiffImageFile(ImageFile.ImageFile):
self.tag_v2 = ImageFileDirectory_v2(ifh)
- # legacy IFD entries will be filled in later
- self.ifd = None
-
# setup frame pointers
self.__first = self.__next = self.tag_v2.next
self.__frame = -1
self._fp = self.fp
- self._frame_pos = []
- self._n_frames = None
+ self._frame_pos: list[int] = []
+ self._n_frames: int | None = None
logger.debug("*** TiffImageFile._open ***")
logger.debug("- __first: %s", self.__first)
@@ -1134,13 +1189,15 @@ class TiffImageFile(ImageFile.ImageFile):
self._seek(0)
@property
- def n_frames(self):
- if self._n_frames is None:
+ def n_frames(self) -> int:
+ current_n_frames = self._n_frames
+ if current_n_frames is None:
current = self.tell()
self._seek(len(self._frame_pos))
while self._n_frames is None:
self._seek(self.tell() + 1)
self.seek(current)
+ assert self._n_frames is not None
return self._n_frames
def seek(self, frame: int) -> None:
@@ -1148,19 +1205,15 @@ class TiffImageFile(ImageFile.ImageFile):
if not self._seek_check(frame):
return
self._seek(frame)
- # Create a new core image object on second and
- # subsequent frames in the image. Image may be
- # different size/mode.
- Image._decompression_bomb_check(self.size)
- self.im = Image.core.new(self.mode, self.size)
+ if self._im is not None and (
+ self.im.size != self._tile_size or self.im.mode != self.mode
+ ):
+ # The core image will no longer be used
+ self._im = None
def _seek(self, frame: int) -> None:
self.fp = self._fp
- # reset buffered io handle in case fp
- # was passed to libtiff, invalidating the buffer
- self.fp.tell()
-
while len(self._frame_pos) <= frame:
if not self.__next:
msg = "no more images in TIFF file"
@@ -1192,6 +1245,10 @@ class TiffImageFile(ImageFile.ImageFile):
self.__frame += 1
self.fp.seek(self._frame_pos[frame])
self.tag_v2.load(self.fp)
+ if XMP in self.tag_v2:
+ self.info["xmp"] = self.tag_v2[XMP]
+ elif "xmp" in self.info:
+ del self.info["xmp"]
self._reload_exif()
# fill the legacy tag/ifd entries
self.tag = self.ifd = ImageFileDirectory_v1.from_v2(self.tag_v2)
@@ -1202,16 +1259,7 @@ class TiffImageFile(ImageFile.ImageFile):
"""Return the current frame number"""
return self.__frame
- def getxmp(self) -> dict[str, Any]:
- """
- Returns a dictionary containing the XMP tags.
- Requires defusedxml to be installed.
-
- :returns: XMP tags in a dictionary.
- """
- return self._getxmp(self.tag_v2[XMP]) if XMP in self.tag_v2 else {}
-
- def get_photoshop_blocks(self):
+ def get_photoshop_blocks(self) -> dict[int, dict[str, bytes]]:
"""
Returns a dictionary of Photoshop "Image Resource Blocks".
The keys are the image resource ID. For more information, see
@@ -1232,21 +1280,23 @@ class TiffImageFile(ImageFile.ImageFile):
val = val[math.ceil((10 + n + size) / 2) * 2 :]
return blocks
- def load(self):
+ def load(self) -> Image.core.PixelAccess | None:
if self.tile and self.use_load_libtiff:
return self._load_libtiff()
return super().load()
+ def load_prepare(self) -> None:
+ if self._im is None:
+ Image._decompression_bomb_check(self._tile_size)
+ self.im = Image.core.new(self.mode, self._tile_size)
+ ImageFile.ImageFile.load_prepare(self)
+
def load_end(self) -> None:
# allow closing if we're on the first frame, there's no next
# This is the ImageFile.load path only, libtiff specific below.
if not self.is_animated:
self._close_exclusive_fp_after_loading = True
- # reset buffered io handle in case fp
- # was passed to libtiff, invalidating the buffer
- self.fp.tell()
-
# load IFD data from fp before it is closed
exif = self.getexif()
for key in TiffTags.TAGS_V2_GROUPS:
@@ -1258,7 +1308,7 @@ class TiffImageFile(ImageFile.ImageFile):
if ExifTags.Base.Orientation in self.tag_v2:
del self.tag_v2[ExifTags.Base.Orientation]
- def _load_libtiff(self):
+ def _load_libtiff(self) -> Image.core.PixelAccess | None:
"""Overload method triggered when we detect a compressed tiff
Calls out to libtiff"""
@@ -1273,7 +1323,7 @@ class TiffImageFile(ImageFile.ImageFile):
# (self._compression, (extents tuple),
# 0, (rawmode, self._compression, fp))
extents = self.tile[0][1]
- args = list(self.tile[0][3])
+ args = self.tile[0][3]
# To be nice on memory footprint, if there's a
# file descriptor, use that instead of reading
@@ -1291,11 +1341,12 @@ class TiffImageFile(ImageFile.ImageFile):
fp = False
if fp:
- args[2] = fp
+ assert isinstance(args, tuple)
+ args_list = list(args)
+ args_list[2] = fp
+ args = tuple(args_list)
- decoder = Image._getdecoder(
- self.mode, "libtiff", tuple(args), self.decoderconfig
- )
+ decoder = Image._getdecoder(self.mode, "libtiff", args, self.decoderconfig)
try:
decoder.setimage(self.im, extents)
except ValueError as e:
@@ -1320,8 +1371,17 @@ class TiffImageFile(ImageFile.ImageFile):
logger.debug("have fileno, calling fileno version of the decoder.")
if not close_self_fp:
self.fp.seek(0)
+ # Save and restore the file position, because libtiff will move it
+ # outside of the Python runtime, and that will confuse
+ # io.BufferedReader and possible others.
+ # NOTE: This must use os.lseek(), and not fp.tell()/fp.seek(),
+ # because the buffer read head already may not equal the actual
+ # file position, and fp.seek() may just adjust it's internal
+ # pointer and not actually seek the OS file handle.
+ pos = os.lseek(fp, 0, os.SEEK_CUR)
# 4 bytes, otherwise the trace might error out
n, err = decoder.decode(b"fpfp")
+ os.lseek(fp, pos, os.SEEK_SET)
else:
# we have something else.
logger.debug("don't have fileno or getvalue. just reading")
@@ -1343,7 +1403,7 @@ class TiffImageFile(ImageFile.ImageFile):
return Image.Image.load(self)
- def _setup(self):
+ def _setup(self) -> None:
"""Setup this image object based on current tags"""
if 0xBC01 in self.tag_v2:
@@ -1369,12 +1429,24 @@ class TiffImageFile(ImageFile.ImageFile):
logger.debug("- photometric_interpretation: %s", photo)
logger.debug("- planar_configuration: %s", self._planar_configuration)
logger.debug("- fill_order: %s", fillorder)
- logger.debug("- YCbCr subsampling: %s", self.tag.get(YCBCRSUBSAMPLING))
+ logger.debug("- YCbCr subsampling: %s", self.tag_v2.get(YCBCRSUBSAMPLING))
# size
- xsize = int(self.tag_v2.get(IMAGEWIDTH))
- ysize = int(self.tag_v2.get(IMAGELENGTH))
- self._size = xsize, ysize
+ try:
+ xsize = self.tag_v2[IMAGEWIDTH]
+ ysize = self.tag_v2[IMAGELENGTH]
+ except KeyError as e:
+ msg = "Missing dimensions"
+ raise TypeError(msg) from e
+ if not isinstance(xsize, int) or not isinstance(ysize, int):
+ msg = "Invalid dimensions"
+ raise ValueError(msg)
+ self._tile_size = xsize, ysize
+ orientation = self.tag_v2.get(ExifTags.Base.Orientation)
+ if orientation in (5, 6, 7, 8):
+ self._size = ysize, xsize
+ else:
+ self._size = xsize, ysize
logger.debug("- size: %s", self.size)
@@ -1510,19 +1582,23 @@ class TiffImageFile(ImageFile.ImageFile):
# Offset in the tile tuple is 0, we go from 0,0 to
# w,h, and we only do this once -- eds
a = (rawmode, self._compression, False, self.tag_v2.offset)
- self.tile.append(("libtiff", (0, 0, xsize, ysize), 0, a))
+ self.tile.append(ImageFile._Tile("libtiff", (0, 0, xsize, ysize), 0, a))
elif STRIPOFFSETS in self.tag_v2 or TILEOFFSETS in self.tag_v2:
# striped image
if STRIPOFFSETS in self.tag_v2:
offsets = self.tag_v2[STRIPOFFSETS]
h = self.tag_v2.get(ROWSPERSTRIP, ysize)
- w = self.size[0]
+ w = xsize
else:
# tiled image
offsets = self.tag_v2[TILEOFFSETS]
- w = self.tag_v2.get(TILEWIDTH)
+ tilewidth = self.tag_v2.get(TILEWIDTH)
h = self.tag_v2.get(TILELENGTH)
+ if not isinstance(tilewidth, int) or not isinstance(h, int):
+ msg = "Invalid tile dimensions"
+ raise ValueError(msg)
+ w = tilewidth
for offset in offsets:
if x + w > xsize:
@@ -1537,19 +1613,19 @@ class TiffImageFile(ImageFile.ImageFile):
# adjust stride width accordingly
stride /= bps_count
- a = (tile_rawmode, int(stride), 1)
+ args = (tile_rawmode, int(stride), 1)
self.tile.append(
- (
+ ImageFile._Tile(
self._compression,
(x, y, min(x + w, xsize), min(y + h, ysize)),
offset,
- a,
+ args,
)
)
x = x + w
- if x >= self.size[0]:
+ if x >= xsize:
x, y = 0, y + h
- if y >= self.size[1]:
+ if y >= ysize:
x = y = 0
layer += 1
else:
@@ -1600,7 +1676,7 @@ SAVE_INFO = {
}
-def _save(im, fp, filename):
+def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
try:
rawmode, prefix, photo, format, bits, extra = SAVE_INFO[im.mode]
except KeyError as e:
@@ -1658,6 +1734,20 @@ def _save(im, fp, filename):
except Exception:
pass # might not be an IFD. Might not have populated type
+ legacy_ifd = {}
+ if hasattr(im, "tag"):
+ legacy_ifd = im.tag.to_v2()
+
+ supplied_tags = {**legacy_ifd, **getattr(im, "tag_v2", {})}
+ for tag in (
+ # IFD offset that may not be correct in the saved image
+ EXIFIFD,
+ # Determined by the image format and should not be copied from legacy_ifd.
+ SAMPLEFORMAT,
+ ):
+ if tag in supplied_tags:
+ del supplied_tags[tag]
+
# additions written by Greg Couch, gregc@cgl.ucsf.edu
# inspired by image-sig posting from Kevin Cazabon, kcazabon@home.com
if hasattr(im, "tag_v2"):
@@ -1671,8 +1761,14 @@ def _save(im, fp, filename):
XMP,
):
if key in im.tag_v2:
- ifd[key] = im.tag_v2[key]
- ifd.tagtype[key] = im.tag_v2.tagtype[key]
+ if key == IPTC_NAA_CHUNK and im.tag_v2.tagtype[key] not in (
+ TiffTags.BYTE,
+ TiffTags.UNDEFINED,
+ ):
+ del supplied_tags[key]
+ else:
+ ifd[key] = im.tag_v2[key]
+ ifd.tagtype[key] = im.tag_v2.tagtype[key]
# preserve ICC profile (should also work when saving other formats
# which support profiles as TIFF) -- 2008-06-06 Florian Hoech
@@ -1716,10 +1812,11 @@ def _save(im, fp, filename):
if im.mode == "1":
inverted_im = im.copy()
px = inverted_im.load()
- for y in range(inverted_im.height):
- for x in range(inverted_im.width):
- px[x, y] = 0 if px[x, y] == 255 else 255
- im = inverted_im
+ if px is not None:
+ for y in range(inverted_im.height):
+ for x in range(inverted_im.width):
+ px[x, y] = 0 if px[x, y] == 255 else 255
+ im = inverted_im
else:
im = ImageOps.invert(im)
@@ -1761,11 +1858,11 @@ def _save(im, fp, filename):
ifd[COMPRESSION] = COMPRESSION_INFO_REV.get(compression, 1)
if im.mode == "YCbCr":
- for tag, value in {
+ for tag, default_value in {
YCBCRSUBSAMPLING: (1, 1),
REFERENCEBLACKWHITE: (0, 255, 128, 255, 128, 255),
}.items():
- ifd.setdefault(tag, value)
+ ifd.setdefault(tag, default_value)
blocklist = [TILEWIDTH, TILELENGTH, TILEOFFSETS, TILEBYTECOUNTS]
if libtiff:
@@ -1785,7 +1882,7 @@ def _save(im, fp, filename):
if hasattr(fp, "fileno"):
try:
fp.seek(0)
- _fp = os.dup(fp.fileno())
+ _fp = fp.fileno()
except io.UnsupportedOperation:
pass
@@ -1808,20 +1905,10 @@ def _save(im, fp, filename):
]
# bits per sample is a single short in the tiff directory, not a list.
- atts = {BITSPERSAMPLE: bits[0]}
+ atts: dict[int, Any] = {BITSPERSAMPLE: bits[0]}
# Merge the ones that we have with (optional) more bits from
# the original file, e.g x,y resolution so that we can
# save(load('')) == original file.
- legacy_ifd = {}
- if hasattr(im, "tag"):
- legacy_ifd = im.tag.to_v2()
-
- # SAMPLEFORMAT is determined by the image format and should not be copied
- # from legacy_ifd.
- supplied_tags = {**getattr(im, "tag_v2", {}), **legacy_ifd}
- if SAMPLEFORMAT in supplied_tags:
- del supplied_tags[SAMPLEFORMAT]
-
for tag, value in itertools.chain(ifd.items(), supplied_tags.items()):
# Libtiff can only process certain core items without adding
# them to the custom dictionary.
@@ -1831,7 +1918,9 @@ def _save(im, fp, filename):
if not getattr(Image.core, "libtiff_support_custom_tags", False):
continue
- if tag in ifd.tagtype:
+ if tag in TiffTags.TAGS_V2_GROUPS:
+ types[tag] = TiffTags.LONG8
+ elif tag in ifd.tagtype:
types[tag] = ifd.tagtype[tag]
elif not (isinstance(value, (int, float, str, bytes))):
continue
@@ -1868,17 +1957,11 @@ def _save(im, fp, filename):
encoder = Image._getencoder(im.mode, "libtiff", a, encoderconfig)
encoder.setimage(im.im, (0, 0) + im.size)
while True:
- # undone, change to self.decodermaxblock:
- errcode, data = encoder.encode(16 * 1024)[1:]
+ errcode, data = encoder.encode(ImageFile.MAXBLOCK)[1:]
if not _fp:
fp.write(data)
if errcode:
break
- if _fp:
- try:
- os.close(_fp)
- except OSError:
- pass
if errcode < 0:
msg = f"encoder error {errcode} when writing image file"
raise OSError(msg)
@@ -1889,16 +1972,18 @@ def _save(im, fp, filename):
offset = ifd.save(fp)
ImageFile._save(
- im, fp, [("raw", (0, 0) + im.size, offset, (rawmode, stride, 1))]
+ im,
+ fp,
+ [ImageFile._Tile("raw", (0, 0) + im.size, offset, (rawmode, stride, 1))],
)
# -- helper for multi-page save --
if "_debug_multipage" in encoderinfo:
# just to access o32 and o16 (using correct byte order)
- im._debug_multipage = ifd
+ setattr(im, "_debug_multipage", ifd)
-class AppendingTiffWriter:
+class AppendingTiffWriter(io.BytesIO):
fieldSizes = [
0, # None
1, # byte
@@ -1928,17 +2013,18 @@ class AppendingTiffWriter:
521, # JPEGACTables
}
- def __init__(self, fn, new=False):
- if hasattr(fn, "read"):
- self.f = fn
- self.close_fp = False
- else:
+ def __init__(self, fn: StrOrBytesPath | IO[bytes], new: bool = False) -> None:
+ self.f: IO[bytes]
+ if is_path(fn):
self.name = fn
self.close_fp = True
try:
self.f = open(fn, "w+b" if new else "r+b")
except OSError:
self.f = open(fn, "w+b")
+ else:
+ self.f = cast(IO[bytes], fn)
+ self.close_fp = False
self.beginning = self.f.tell()
self.setup()
@@ -1946,7 +2032,7 @@ class AppendingTiffWriter:
# Reset everything.
self.f.seek(self.beginning, os.SEEK_SET)
- self.whereToWriteNewIFDOffset = None
+ self.whereToWriteNewIFDOffset: int | None = None
self.offsetOfNewPage = 0
self.IIMM = iimm = self.f.read(4)
@@ -1985,6 +2071,7 @@ class AppendingTiffWriter:
ifd_offset = self.readLong()
ifd_offset += self.offsetOfNewPage
+ assert self.whereToWriteNewIFDOffset is not None
self.f.seek(self.whereToWriteNewIFDOffset)
self.writeLong(ifd_offset)
self.f.seek(ifd_offset)
@@ -1998,15 +2085,20 @@ class AppendingTiffWriter:
def __enter__(self) -> AppendingTiffWriter:
return self
- def __exit__(self, exc_type, exc_value, traceback):
+ def __exit__(self, *args: object) -> None:
if self.close_fp:
self.close()
- return False
def tell(self) -> int:
return self.f.tell() - self.offsetOfNewPage
- def seek(self, offset, whence=io.SEEK_SET):
+ def seek(self, offset: int, whence: int = io.SEEK_SET) -> int:
+ """
+ :param offset: Distance to seek.
+ :param whence: Whether the distance is relative to the start,
+ end or current position.
+ :returns: The resulting position, relative to the start.
+ """
if whence == os.SEEK_SET:
offset += self.offsetOfNewPage
@@ -2040,53 +2132,64 @@ class AppendingTiffWriter:
num_tags = self.readShort()
self.f.seek(num_tags * 12, os.SEEK_CUR)
- def write(self, data):
+ def write(self, data: Buffer, /) -> int:
return self.f.write(data)
- def readShort(self):
- (value,) = struct.unpack(self.shortFmt, self.f.read(2))
+ def _fmt(self, field_size: int) -> str:
+ try:
+ return {2: "H", 4: "L", 8: "Q"}[field_size]
+ except KeyError:
+ msg = "offset is not supported"
+ raise RuntimeError(msg)
+
+ def _read(self, field_size: int) -> int:
+ (value,) = struct.unpack(
+ self.endian + self._fmt(field_size), self.f.read(field_size)
+ )
return value
- def readLong(self):
- (value,) = struct.unpack(self.longFmt, self.f.read(4))
- return value
+ def readShort(self) -> int:
+ return self._read(2)
- def rewriteLastShortToLong(self, value):
+ def readLong(self) -> int:
+ return self._read(4)
+
+ @staticmethod
+ def _verify_bytes_written(bytes_written: int | None, expected: int) -> None:
+ if bytes_written is not None and bytes_written != expected:
+ msg = f"wrote only {bytes_written} bytes but wanted {expected}"
+ raise RuntimeError(msg)
+
+ def rewriteLastShortToLong(self, value: int) -> None:
self.f.seek(-2, os.SEEK_CUR)
bytes_written = self.f.write(struct.pack(self.longFmt, value))
- if bytes_written is not None and bytes_written != 4:
- msg = f"wrote only {bytes_written} bytes but wanted 4"
- raise RuntimeError(msg)
+ self._verify_bytes_written(bytes_written, 4)
- def rewriteLastShort(self, value):
- self.f.seek(-2, os.SEEK_CUR)
+ def _rewriteLast(self, value: int, field_size: int) -> None:
+ self.f.seek(-field_size, os.SEEK_CUR)
+ bytes_written = self.f.write(
+ struct.pack(self.endian + self._fmt(field_size), value)
+ )
+ self._verify_bytes_written(bytes_written, field_size)
+
+ def rewriteLastShort(self, value: int) -> None:
+ return self._rewriteLast(value, 2)
+
+ def rewriteLastLong(self, value: int) -> None:
+ return self._rewriteLast(value, 4)
+
+ def writeShort(self, value: int) -> None:
bytes_written = self.f.write(struct.pack(self.shortFmt, value))
- if bytes_written is not None and bytes_written != 2:
- msg = f"wrote only {bytes_written} bytes but wanted 2"
- raise RuntimeError(msg)
+ self._verify_bytes_written(bytes_written, 2)
- def rewriteLastLong(self, value):
- self.f.seek(-4, os.SEEK_CUR)
+ def writeLong(self, value: int) -> None:
bytes_written = self.f.write(struct.pack(self.longFmt, value))
- if bytes_written is not None and bytes_written != 4:
- msg = f"wrote only {bytes_written} bytes but wanted 4"
- raise RuntimeError(msg)
-
- def writeShort(self, value):
- bytes_written = self.f.write(struct.pack(self.shortFmt, value))
- if bytes_written is not None and bytes_written != 2:
- msg = f"wrote only {bytes_written} bytes but wanted 2"
- raise RuntimeError(msg)
-
- def writeLong(self, value):
- bytes_written = self.f.write(struct.pack(self.longFmt, value))
- if bytes_written is not None and bytes_written != 4:
- msg = f"wrote only {bytes_written} bytes but wanted 4"
- raise RuntimeError(msg)
+ self._verify_bytes_written(bytes_written, 4)
def close(self) -> None:
self.finalize()
- self.f.close()
+ if self.close_fp:
+ self.f.close()
def fixIFD(self) -> None:
num_tags = self.readShort()
@@ -2098,40 +2201,29 @@ class AppendingTiffWriter:
total_size = field_size * count
is_local = total_size <= 4
if not is_local:
- offset = self.readLong()
- offset += self.offsetOfNewPage
+ offset = self.readLong() + self.offsetOfNewPage
self.rewriteLastLong(offset)
if tag in self.Tags:
cur_pos = self.f.tell()
if is_local:
- self.fixOffsets(
- count, isShort=(field_size == 2), isLong=(field_size == 4)
- )
+ self._fixOffsets(count, field_size)
self.f.seek(cur_pos + 4)
else:
self.f.seek(offset)
- self.fixOffsets(
- count, isShort=(field_size == 2), isLong=(field_size == 4)
- )
+ self._fixOffsets(count, field_size)
self.f.seek(cur_pos)
- offset = cur_pos = None
-
elif is_local:
# skip the locally stored value that is not an offset
self.f.seek(4, os.SEEK_CUR)
- def fixOffsets(self, count, isShort=False, isLong=False):
- if not isShort and not isLong:
- msg = "offset is neither short nor long"
- raise RuntimeError(msg)
-
+ def _fixOffsets(self, count: int, field_size: int) -> None:
for i in range(count):
- offset = self.readShort() if isShort else self.readLong()
+ offset = self._read(field_size)
offset += self.offsetOfNewPage
- if isShort and offset >= 65536:
+ if field_size == 2 and offset >= 65536:
# offset is now too large - we must convert shorts to longs
if count != 1:
msg = "not implemented"
@@ -2143,13 +2235,22 @@ class AppendingTiffWriter:
self.f.seek(-10, os.SEEK_CUR)
self.writeShort(TiffTags.LONG) # rewrite the type to LONG
self.f.seek(8, os.SEEK_CUR)
- elif isShort:
- self.rewriteLastShort(offset)
else:
- self.rewriteLastLong(offset)
+ self._rewriteLast(offset, field_size)
+
+ def fixOffsets(
+ self, count: int, isShort: bool = False, isLong: bool = False
+ ) -> None:
+ if isShort:
+ field_size = 2
+ elif isLong:
+ field_size = 4
+ else:
+ field_size = 0
+ return self._fixOffsets(count, field_size)
-def _save_all(im: Image.Image, fp: IO[bytes], filename: str) -> None:
+def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
encoderinfo = im.encoderinfo.copy()
encoderconfig = im.encoderconfig
append_images = list(encoderinfo.get("append_images", []))
diff --git a/src/PIL/TiffTags.py b/src/PIL/TiffTags.py
index 89fad7033..86adaa458 100644
--- a/src/PIL/TiffTags.py
+++ b/src/PIL/TiffTags.py
@@ -32,17 +32,24 @@ class _TagInfo(NamedTuple):
class TagInfo(_TagInfo):
__slots__: list[str] = []
- def __new__(cls, value=None, name="unknown", type=None, length=None, enum=None):
+ def __new__(
+ cls,
+ value: int | None = None,
+ name: str = "unknown",
+ type: int | None = None,
+ length: int | None = None,
+ enum: dict[str, int] | None = None,
+ ) -> TagInfo:
return super().__new__(cls, value, name, type, length, enum or {})
- def cvt_enum(self, value):
+ def cvt_enum(self, value: str) -> int | str:
# Using get will call hash(value), which can be expensive
# for some types (e.g. Fraction). Since self.enum is rarely
# used, it's usually better to test it first.
return self.enum.get(value, value) if self.enum else value
-def lookup(tag, group=None):
+def lookup(tag: int, group: int | None = None) -> TagInfo:
"""
:param tag: Integer tag number
:param group: Which :py:data:`~PIL.TiffTags.TAGS_V2_GROUPS` to look in
@@ -89,7 +96,7 @@ DOUBLE = 12
IFD = 13
LONG8 = 16
-TAGS_V2 = {
+_tags_v2: dict[int, tuple[str, int, int] | tuple[str, int, int, dict[str, int]]] = {
254: ("NewSubfileType", LONG, 1),
255: ("SubfileType", SHORT, 1),
256: ("ImageWidth", LONG, 1),
@@ -233,7 +240,7 @@ TAGS_V2 = {
50838: ("ImageJMetaDataByteCounts", LONG, 0), # Can be more than one
50839: ("ImageJMetaData", UNDEFINED, 1), # see Issue #2006
}
-TAGS_V2_GROUPS = {
+_tags_v2_groups = {
# ExifIFD
34665: {
36864: ("ExifVersion", UNDEFINED, 1),
@@ -281,7 +288,7 @@ TAGS_V2_GROUPS = {
# Legacy Tags structure
# these tags aren't included above, but were in the previous versions
-TAGS = {
+TAGS: dict[int | tuple[int, int], str] = {
347: "JPEGTables",
700: "XMP",
# Additional Exif Info
@@ -425,9 +432,12 @@ TAGS = {
50784: "Alias Layer Metadata",
}
+TAGS_V2: dict[int, TagInfo] = {}
+TAGS_V2_GROUPS: dict[int, dict[int, TagInfo]] = {}
-def _populate():
- for k, v in TAGS_V2.items():
+
+def _populate() -> None:
+ for k, v in _tags_v2.items():
# Populate legacy structure.
TAGS[k] = v[0]
if len(v) == 4:
@@ -436,9 +446,8 @@ def _populate():
TAGS_V2[k] = TagInfo(k, *v)
- for tags in TAGS_V2_GROUPS.values():
- for k, v in tags.items():
- tags[k] = TagInfo(k, *v)
+ for group, tags in _tags_v2_groups.items():
+ TAGS_V2_GROUPS[group] = {k: TagInfo(k, *v) for k, v in tags.items()}
_populate()
diff --git a/src/PIL/WalImageFile.py b/src/PIL/WalImageFile.py
index fbd7be6ed..87e32878b 100644
--- a/src/PIL/WalImageFile.py
+++ b/src/PIL/WalImageFile.py
@@ -24,8 +24,11 @@ and has been tested with a few sample files found using google.
"""
from __future__ import annotations
+from typing import IO
+
from . import Image, ImageFile
from ._binary import i32le as i32
+from ._typing import StrOrBytesPath
class WalImageFile(ImageFile.ImageFile):
@@ -50,15 +53,15 @@ class WalImageFile(ImageFile.ImageFile):
if next_name:
self.info["next_name"] = next_name
- def load(self):
- if not self.im:
+ def load(self) -> Image.core.PixelAccess | None:
+ if self._im is None:
self.im = Image.core.new(self.mode, self.size)
self.frombytes(self.fp.read(self.size[0] * self.size[1]))
self.putpalette(quake2palette)
return Image.Image.load(self)
-def open(filename):
+def open(filename: StrOrBytesPath | IO[bytes]) -> WalImageFile:
"""
Load texture from a Quake2 WAL texture file.
diff --git a/src/PIL/WebPImagePlugin.py b/src/PIL/WebPImagePlugin.py
index d2b14d2be..2d951d23e 100644
--- a/src/PIL/WebPImagePlugin.py
+++ b/src/PIL/WebPImagePlugin.py
@@ -1,7 +1,7 @@
from __future__ import annotations
from io import BytesIO
-from typing import Any
+from typing import IO, Any
from . import Image, ImageFile
@@ -13,10 +13,6 @@ except ImportError:
SUPPORTED = False
-_VALID_WEBP_MODES = {"RGBX": True, "RGBA": True, "RGB": True}
-
-_VALID_WEBP_LEGACY_MODES = {"RGB": True, "RGBA": True}
-
_VP8_MODES_BY_IDENTIFIER = {
b"VP8 ": "RGB",
b"VP8X": "RGBA",
@@ -45,22 +41,6 @@ class WebPImageFile(ImageFile.ImageFile):
__logical_frame = 0
def _open(self) -> None:
- if not _webp.HAVE_WEBPANIM:
- # Legacy mode
- data, width, height, self._mode, icc_profile, exif = _webp.WebPDecode(
- self.fp.read()
- )
- if icc_profile:
- self.info["icc_profile"] = icc_profile
- if exif:
- self.info["exif"] = exif
- self._size = width, height
- self.fp = BytesIO(data)
- self.tile = [("raw", (0, 0) + self.size, 0, self.mode)]
- self.n_frames = 1
- self.is_animated = False
- return
-
# Use the newer AnimDecoder API to parse the (possibly) animated file,
# and access muxed chunks like ICC/EXIF/XMP.
self._decoder = _webp.WebPAnimDecoder(self.fp.read())
@@ -80,7 +60,6 @@ class WebPImageFile(ImageFile.ImageFile):
self.is_animated = self.n_frames > 1
self._mode = "RGB" if mode == "RGBX" else mode
self.rawmode = mode
- self.tile = []
# Attempt to read ICC / EXIF / XMP chunks from file
icc_profile = self._decoder.get_chunk("ICCP")
@@ -96,20 +75,11 @@ class WebPImageFile(ImageFile.ImageFile):
# Initialize seek state
self._reset(reset=False)
- def _getexif(self) -> dict[str, Any] | None:
+ def _getexif(self) -> dict[int, Any] | None:
if "exif" not in self.info:
return None
return self.getexif()._get_merged_dict()
- def getxmp(self) -> dict[str, Any]:
- """
- Returns a dictionary containing the XMP tags.
- Requires defusedxml to be installed.
-
- :returns: XMP tags in a dictionary.
- """
- return self._getxmp(self.info["xmp"]) if "xmp" in self.info else {}
-
def seek(self, frame: int) -> None:
if not self._seek_check(frame):
return
@@ -124,7 +94,7 @@ class WebPImageFile(ImageFile.ImageFile):
self.__loaded = -1
self.__timestamp = 0
- def _get_next(self):
+ def _get_next(self) -> tuple[bytes, int, int]:
# Get next frame
ret = self._decoder.get_next()
self.__physical_frame += 1
@@ -153,22 +123,21 @@ class WebPImageFile(ImageFile.ImageFile):
while self.__physical_frame < frame:
self._get_next() # Advance to the requested frame
- def load(self):
- if _webp.HAVE_WEBPANIM:
- if self.__loaded != self.__logical_frame:
- self._seek(self.__logical_frame)
+ def load(self) -> Image.core.PixelAccess | None:
+ if self.__loaded != self.__logical_frame:
+ self._seek(self.__logical_frame)
- # We need to load the image data for this frame
- data, timestamp, duration = self._get_next()
- self.info["timestamp"] = timestamp
- self.info["duration"] = duration
- self.__loaded = self.__logical_frame
+ # We need to load the image data for this frame
+ data, timestamp, duration = self._get_next()
+ self.info["timestamp"] = timestamp
+ self.info["duration"] = duration
+ self.__loaded = self.__logical_frame
- # Set tile
- if self.fp and self._exclusive_fp:
- self.fp.close()
- self.fp = BytesIO(data)
- self.tile = [("raw", (0, 0) + self.size, 0, self.rawmode)]
+ # Set tile
+ if self.fp and self._exclusive_fp:
+ self.fp.close()
+ self.fp = BytesIO(data)
+ self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, 0, self.rawmode)]
return super().load()
@@ -176,13 +145,17 @@ class WebPImageFile(ImageFile.ImageFile):
pass
def tell(self) -> int:
- if not _webp.HAVE_WEBPANIM:
- return super().tell()
-
return self.__logical_frame
-def _save_all(im, fp, filename):
+def _convert_frame(im: Image.Image) -> Image.Image:
+ # Make sure image mode is supported
+ if im.mode not in ("RGBX", "RGBA", "RGB"):
+ im = im.convert("RGBA" if im.has_transparency_data else "RGB")
+ return im
+
+
+def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
encoderinfo = im.encoderinfo.copy()
append_images = list(encoderinfo.get("append_images", []))
@@ -195,7 +168,7 @@ def _save_all(im, fp, filename):
_save(im, fp, filename)
return
- background = (0, 0, 0, 0)
+ background: int | tuple[int, ...] = (0, 0, 0, 0)
if "background" in encoderinfo:
background = encoderinfo["background"]
elif "background" in im.info:
@@ -272,31 +245,13 @@ def _save_all(im, fp, filename):
for idx in range(nfr):
ims.seek(idx)
- ims.load()
- # Make sure image mode is supported
- frame = ims
- rawmode = ims.mode
- if ims.mode not in _VALID_WEBP_MODES:
- alpha = (
- "A" in ims.mode
- or "a" in ims.mode
- or (ims.mode == "P" and "A" in ims.im.getpalettemode())
- )
- rawmode = "RGBA" if alpha else "RGB"
- frame = ims.convert(rawmode)
-
- if rawmode == "RGB":
- # For faster conversion, use RGBX
- rawmode = "RGBX"
+ frame = _convert_frame(ims)
# Append the frame to the animation encoder
enc.add(
- frame.tobytes("raw", rawmode),
+ frame.getim(),
round(timestamp),
- frame.size[0],
- frame.size[1],
- rawmode,
lossless,
quality,
alpha_quality,
@@ -314,7 +269,7 @@ def _save_all(im, fp, filename):
im.seek(cur_idx)
# Force encoder to flush frames
- enc.add(None, round(timestamp), 0, 0, "", lossless, quality, alpha_quality, 0)
+ enc.add(None, round(timestamp), lossless, quality, alpha_quality, 0)
# Get the final output from the encoder
data = enc.assemble(icc_profile, exif, xmp)
@@ -325,7 +280,7 @@ def _save_all(im, fp, filename):
fp.write(data)
-def _save(im, fp, filename):
+def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
lossless = im.encoderinfo.get("lossless", False)
quality = im.encoderinfo.get("quality", 80)
alpha_quality = im.encoderinfo.get("alpha_quality", 100)
@@ -339,17 +294,13 @@ def _save(im, fp, filename):
method = im.encoderinfo.get("method", 4)
exact = 1 if im.encoderinfo.get("exact") else 0
- if im.mode not in _VALID_WEBP_LEGACY_MODES:
- im = im.convert("RGBA" if im.has_transparency_data else "RGB")
+ im = _convert_frame(im)
data = _webp.WebPEncode(
- im.tobytes(),
- im.size[0],
- im.size[1],
+ im.getim(),
lossless,
float(quality),
float(alpha_quality),
- im.mode,
icc_profile,
method,
exact,
@@ -385,7 +336,6 @@ def _supported_modes():
Image.register_open(WebPImageFile.format, WebPImageFile, _accept)
if SUPPORTED:
Image.register_save(WebPImageFile.format, _save)
- if _webp.HAVE_WEBPANIM:
- Image.register_save_all(WebPImageFile.format, _save_all)
+ Image.register_save_all(WebPImageFile.format, _save_all)
Image.register_extension(WebPImageFile.format, ".webp")
Image.register_mime(WebPImageFile.format, "image/webp")
diff --git a/src/PIL/WmfImagePlugin.py b/src/PIL/WmfImagePlugin.py
index a68f705a0..48e9823e8 100644
--- a/src/PIL/WmfImagePlugin.py
+++ b/src/PIL/WmfImagePlugin.py
@@ -92,6 +92,9 @@ class WmfStubImageFile(ImageFile.StubImageFile):
# get units per inch
self._inch = word(s, 14)
+ if self._inch == 0:
+ msg = "Invalid inch"
+ raise ValueError(msg)
# get bounding box
x0 = short(s, 6)
@@ -128,7 +131,7 @@ class WmfStubImageFile(ImageFile.StubImageFile):
size = x1 - x0, y1 - y0
# calculate dots per inch from bbox and frame
- xdpi = 2540.0 * (x1 - y0) / (frame[2] - frame[0])
+ xdpi = 2540.0 * (x1 - x0) / (frame[2] - frame[0])
ydpi = 2540.0 * (y1 - y0) / (frame[3] - frame[1])
self.info["wmf_bbox"] = x0, y0, x1, y1
@@ -152,7 +155,7 @@ class WmfStubImageFile(ImageFile.StubImageFile):
def _load(self) -> ImageFile.StubHandler | None:
return _handler
- def load(self, dpi=None):
+ def load(self, dpi: int | None = None) -> Image.core.PixelAccess | None:
if dpi is not None and self._inch is not None:
self.info["dpi"] = dpi
x0, y0, x1, y1 = self.info["wmf_bbox"]
@@ -163,7 +166,7 @@ class WmfStubImageFile(ImageFile.StubImageFile):
return super().load()
-def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None:
+def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
if _handler is None or not hasattr(_handler, "save"):
msg = "WMF save handler not installed"
raise OSError(msg)
diff --git a/src/PIL/XVThumbImagePlugin.py b/src/PIL/XVThumbImagePlugin.py
index c84adaca2..75333354d 100644
--- a/src/PIL/XVThumbImagePlugin.py
+++ b/src/PIL/XVThumbImagePlugin.py
@@ -73,7 +73,9 @@ class XVThumbImageFile(ImageFile.ImageFile):
self.palette = ImagePalette.raw("RGB", PALETTE)
- self.tile = [("raw", (0, 0) + self.size, self.fp.tell(), (self.mode, 0, 1))]
+ self.tile = [
+ ImageFile._Tile("raw", (0, 0) + self.size, self.fp.tell(), self.mode)
+ ]
# --------------------------------------------------------------------
diff --git a/src/PIL/XbmImagePlugin.py b/src/PIL/XbmImagePlugin.py
index eee727436..943a04470 100644
--- a/src/PIL/XbmImagePlugin.py
+++ b/src/PIL/XbmImagePlugin.py
@@ -67,10 +67,10 @@ class XbmImageFile(ImageFile.ImageFile):
self._mode = "1"
self._size = xsize, ysize
- self.tile = [("xbm", (0, 0) + self.size, m.end(), None)]
+ self.tile = [ImageFile._Tile("xbm", (0, 0) + self.size, m.end())]
-def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None:
+def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
if im.mode != "1":
msg = f"cannot write mode {im.mode} as XBM"
raise OSError(msg)
@@ -85,7 +85,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None:
fp.write(b"static char im_bits[] = {\n")
- ImageFile._save(im, fp, [("xbm", (0, 0) + im.size, 0, None)])
+ ImageFile._save(im, fp, [ImageFile._Tile("xbm", (0, 0) + im.size)])
fp.write(b"};\n")
diff --git a/src/PIL/XpmImagePlugin.py b/src/PIL/XpmImagePlugin.py
index 8d56331e6..b985aa5dc 100644
--- a/src/PIL/XpmImagePlugin.py
+++ b/src/PIL/XpmImagePlugin.py
@@ -101,7 +101,7 @@ class XpmImageFile(ImageFile.ImageFile):
self._mode = "P"
self.palette = ImagePalette.raw("RGB", b"".join(palette))
- self.tile = [("raw", (0, 0) + self.size, self.fp.tell(), ("P", 0, 1))]
+ self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, self.fp.tell(), "P")]
def load_read(self, read_bytes: int) -> bytes:
#
diff --git a/src/PIL/_deprecate.py b/src/PIL/_deprecate.py
index 33a0e07b3..83952b397 100644
--- a/src/PIL/_deprecate.py
+++ b/src/PIL/_deprecate.py
@@ -45,8 +45,6 @@ def deprecate(
elif when <= int(__version__.split(".")[0]):
msg = f"{deprecated} {is_} deprecated and should be removed."
raise RuntimeError(msg)
- elif when == 11:
- removed = "Pillow 11 (2024-10-15)"
elif when == 12:
removed = "Pillow 12 (2025-10-15)"
else:
diff --git a/src/PIL/_imaging.pyi b/src/PIL/_imaging.pyi
index 1fe954417..998bc52eb 100644
--- a/src/PIL/_imaging.pyi
+++ b/src/PIL/_imaging.pyi
@@ -1,6 +1,7 @@
from typing import Any
class ImagingCore:
+ def __getitem__(self, index: int) -> float: ...
def __getattr__(self, name: str) -> Any: ...
class ImagingFont:
@@ -10,7 +11,21 @@ class ImagingDraw:
def __getattr__(self, name: str) -> Any: ...
class PixelAccess:
+ def __getitem__(self, xy: tuple[int, int]) -> float | tuple[int, ...]: ...
+ def __setitem__(
+ self, xy: tuple[int, int], color: float | tuple[int, ...]
+ ) -> None: ...
+
+class ImagingDecoder:
def __getattr__(self, name: str) -> Any: ...
-def font(image, glyphdata: bytes) -> ImagingFont: ...
+class ImagingEncoder:
+ def __getattr__(self, name: str) -> Any: ...
+
+class _Outline:
+ def close(self) -> None: ...
+ def __getattr__(self, name: str) -> Any: ...
+
+def font(image: ImagingCore, glyphdata: bytes) -> ImagingFont: ...
+def outline() -> _Outline: ...
def __getattr__(name: str) -> Any: ...
diff --git a/src/PIL/_imagingcms.pyi b/src/PIL/_imagingcms.pyi
index 2abd6d0f7..ddcf93ab1 100644
--- a/src/PIL/_imagingcms.pyi
+++ b/src/PIL/_imagingcms.pyi
@@ -2,6 +2,8 @@ import datetime
import sys
from typing import Literal, SupportsFloat, TypedDict
+from ._typing import CapsuleType
+
littlecms_version: str | None
_Tuple3f = tuple[float, float, float]
@@ -108,7 +110,7 @@ class CmsProfile:
def is_intent_supported(self, intent: int, direction: int, /) -> int: ...
class CmsTransform:
- def apply(self, id_in: int, id_out: int) -> int: ...
+ def apply(self, id_in: CapsuleType, id_out: CapsuleType) -> int: ...
def profile_open(profile: str, /) -> CmsProfile: ...
def profile_frombytes(profile: bytes, /) -> CmsProfile: ...
diff --git a/src/PIL/_imagingft.pyi b/src/PIL/_imagingft.pyi
index 6e0ddd2f1..9cc9822f5 100644
--- a/src/PIL/_imagingft.pyi
+++ b/src/PIL/_imagingft.pyi
@@ -1,12 +1,6 @@
-from typing import Any, TypedDict
+from typing import Any, Callable
-from . import _imaging
-
-class _Axis(TypedDict):
- minimum: int | None
- default: int | None
- maximum: int | None
- name: bytes | None
+from . import ImageFont, _imaging
class Font:
@property
@@ -27,43 +21,49 @@ class Font:
def glyphs(self) -> int: ...
def render(
self,
- string: str,
- fill,
- mode=...,
- dir=...,
- features=...,
- lang=...,
- stroke_width=...,
- anchor=...,
- foreground_ink_long=...,
- x_start=...,
- y_start=...,
+ string: str | bytes,
+ fill: Callable[[int, int], _imaging.ImagingCore],
+ mode: str,
+ dir: str | None,
+ features: list[str] | None,
+ lang: str | None,
+ stroke_width: float,
+ anchor: str | None,
+ foreground_ink_long: int,
+ x_start: float,
+ y_start: float,
/,
) -> tuple[_imaging.ImagingCore, tuple[int, int]]: ...
def getsize(
self,
string: str | bytes | bytearray,
- mode=...,
- dir=...,
- features=...,
- lang=...,
- anchor=...,
+ mode: str,
+ dir: str | None,
+ features: list[str] | None,
+ lang: str | None,
+ anchor: str | None,
/,
) -> tuple[tuple[int, int], tuple[int, int]]: ...
def getlength(
- self, string: str, mode=..., dir=..., features=..., lang=..., /
+ self,
+ string: str | bytes,
+ mode: str,
+ dir: str | None,
+ features: list[str] | None,
+ lang: str | None,
+ /,
) -> float: ...
def getvarnames(self) -> list[bytes]: ...
- def getvaraxes(self) -> list[_Axis] | None: ...
+ def getvaraxes(self) -> list[ImageFont.Axis]: ...
def setvarname(self, instance_index: int, /) -> None: ...
def setvaraxes(self, axes: list[float], /) -> None: ...
def getfont(
filename: str | bytes,
size: float,
- index=...,
- encoding=...,
- font_bytes=...,
- layout_engine=...,
+ index: int,
+ encoding: str,
+ font_bytes: bytes,
+ layout_engine: int,
) -> Font: ...
def __getattr__(name: str) -> Any: ...
diff --git a/src/PIL/_imagingtk.pyi b/src/PIL/_imagingtk.pyi
new file mode 100644
index 000000000..e27843e53
--- /dev/null
+++ b/src/PIL/_imagingtk.pyi
@@ -0,0 +1,3 @@
+from typing import Any
+
+def __getattr__(name: str) -> Any: ...
diff --git a/src/PIL/_typing.py b/src/PIL/_typing.py
index 7075e8672..34a9a81e1 100644
--- a/src/PIL/_typing.py
+++ b/src/PIL/_typing.py
@@ -2,7 +2,28 @@ from __future__ import annotations
import os
import sys
-from typing import Protocol, Sequence, TypeVar, Union
+from collections.abc import Sequence
+from typing import TYPE_CHECKING, Any, Protocol, TypeVar, Union
+
+if TYPE_CHECKING:
+ from numbers import _IntegralLike as IntegralLike
+
+ try:
+ import numpy.typing as npt
+
+ NumpyArray = npt.NDArray[Any] # requires numpy>=1.21
+ except (ImportError, AttributeError):
+ pass
+
+if sys.version_info >= (3, 13):
+ from types import CapsuleType
+else:
+ CapsuleType = object
+
+if sys.version_info >= (3, 12):
+ from collections.abc import Buffer
+else:
+ Buffer = Any
if sys.version_info >= (3, 10):
from typing import TypeGuard
@@ -10,7 +31,6 @@ 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]:
@@ -24,10 +44,10 @@ _T_co = TypeVar("_T_co", covariant=True)
class SupportsRead(Protocol[_T_co]):
- def read(self, __length: int = ...) -> _T_co: ...
+ def read(self, length: int = ..., /) -> _T_co: ...
-StrOrBytesPath = Union[str, bytes, "os.PathLike[str]", "os.PathLike[bytes]"]
+StrOrBytesPath = Union[str, bytes, os.PathLike[str], os.PathLike[bytes]]
-__all__ = ["TypeGuard", "StrOrBytesPath", "SupportsRead"]
+__all__ = ["Buffer", "IntegralLike", "StrOrBytesPath", "SupportsRead", "TypeGuard"]
diff --git a/src/PIL/_util.py b/src/PIL/_util.py
index 6bc762816..8ef0d36f7 100644
--- a/src/PIL/_util.py
+++ b/src/PIL/_util.py
@@ -10,11 +10,6 @@ def is_path(f: Any) -> TypeGuard[StrOrBytesPath]:
return isinstance(f, (bytes, str, os.PathLike))
-def is_directory(f: Any) -> TypeGuard[StrOrBytesPath]:
- """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: BaseException):
self.ex = ex
diff --git a/src/PIL/_version.py b/src/PIL/_version.py
index 12d7412ea..0807f949c 100644
--- a/src/PIL/_version.py
+++ b/src/PIL/_version.py
@@ -1,4 +1,4 @@
# Master version for Pillow
from __future__ import annotations
-__version__ = "10.4.0.dev0"
+__version__ = "11.1.0.dev0"
diff --git a/src/PIL/features.py b/src/PIL/features.py
index 16c749f14..3645e3def 100644
--- a/src/PIL/features.py
+++ b/src/PIL/features.py
@@ -4,10 +4,12 @@ import collections
import os
import sys
import warnings
+from typing import IO
import PIL
from . import Image
+from ._deprecate import deprecate
modules = {
"pil": ("PIL._imaging", "PILLOW_VERSION"),
@@ -117,14 +119,15 @@ def get_supported_codecs() -> list[str]:
return [f for f in codecs if check_codec(f)]
-features = {
- "webp_anim": ("PIL._webp", "HAVE_WEBPANIM", None),
- "webp_mux": ("PIL._webp", "HAVE_WEBPMUX", None),
- "transp_webp": ("PIL._webp", "HAVE_TRANSPARENCY", None),
+features: dict[str, tuple[str, str | bool, str | None]] = {
+ "webp_anim": ("PIL._webp", True, None),
+ "webp_mux": ("PIL._webp", True, None),
+ "transp_webp": ("PIL._webp", True, None),
"raqm": ("PIL._imagingft", "HAVE_RAQM", "raqm_version"),
"fribidi": ("PIL._imagingft", "HAVE_FRIBIDI", "fribidi_version"),
"harfbuzz": ("PIL._imagingft", "HAVE_HARFBUZZ", "harfbuzz_version"),
"libjpeg_turbo": ("PIL._imaging", "HAVE_LIBJPEGTURBO", "libjpeg_turbo_version"),
+ "zlib_ng": ("PIL._imaging", "HAVE_ZLIBNG", "zlib_ng_version"),
"libimagequant": ("PIL._imaging", "HAVE_LIBIMAGEQUANT", "imagequant_version"),
"xcb": ("PIL._imaging", "HAVE_XCB", None),
}
@@ -144,8 +147,12 @@ def check_feature(feature: str) -> bool | None:
module, flag, ver = features[feature]
+ if isinstance(flag, bool):
+ deprecate(f'check_feature("{feature}")', 12)
try:
imported_module = __import__(module, fromlist=["PIL"])
+ if isinstance(flag, bool):
+ return flag
return getattr(imported_module, flag)
except ModuleNotFoundError:
return None
@@ -175,7 +182,17 @@ def get_supported_features() -> list[str]:
"""
:returns: A list of all supported features.
"""
- return [f for f in features if check_feature(f)]
+ supported_features = []
+ for f, (module, flag, _) in features.items():
+ if flag is True:
+ for feature, (feature_module, _) in modules.items():
+ if feature_module == module:
+ if check_module(feature):
+ supported_features.append(f)
+ break
+ elif check_feature(f):
+ supported_features.append(f)
+ return supported_features
def check(feature: str) -> bool | None:
@@ -223,7 +240,7 @@ def get_supported() -> list[str]:
return ret
-def pilinfo(out=None, supported_formats=True):
+def pilinfo(out: IO[str] | None = None, supported_formats: bool = True) -> None:
"""
Prints information about this installation of Pillow.
This function can be called with ``python3 -m PIL``.
@@ -244,9 +261,9 @@ def pilinfo(out=None, supported_formats=True):
print("-" * 68, file=out)
print(f"Pillow {PIL.__version__}", file=out)
- py_version = sys.version.splitlines()
- print(f"Python {py_version[0].strip()}", file=out)
- for py_version in py_version[1:]:
+ py_version_lines = sys.version.splitlines()
+ print(f"Python {py_version_lines[0].strip()}", file=out)
+ for py_version in py_version_lines[1:]:
print(f" {py_version.strip()}", file=out)
print("-" * 68, file=out)
print(f"Python executable is {sys.executable or 'unknown'}", file=out)
@@ -270,9 +287,6 @@ def pilinfo(out=None, supported_formats=True):
("freetype2", "FREETYPE2"),
("littlecms2", "LITTLECMS2"),
("webp", "WEBP"),
- ("transp_webp", "WEBP Transparency"),
- ("webp_mux", "WEBPMUX"),
- ("webp_anim", "WEBP Animation"),
("jpg", "JPEG"),
("jpg_2000", "OPENJPEG (JPEG2000)"),
("zlib", "ZLIB (PNG/ZIP)"),
@@ -282,9 +296,12 @@ def pilinfo(out=None, supported_formats=True):
("xcb", "XCB (X protocol)"),
]:
if check(name):
- if name == "jpg" and check_feature("libjpeg_turbo"):
- v = "libjpeg-turbo " + version_feature("libjpeg_turbo")
- else:
+ v: str | None = None
+ if name == "jpg":
+ libjpeg_turbo_version = version_feature("libjpeg_turbo")
+ if libjpeg_turbo_version is not None:
+ v = "libjpeg-turbo " + libjpeg_turbo_version
+ if v is None:
v = version(name)
if v is not None:
version_static = name in ("pil", "jpg")
@@ -292,7 +309,11 @@ def pilinfo(out=None, supported_formats=True):
# this check is also in src/_imagingcms.c:setup_module()
version_static = tuple(int(x) for x in v.split(".")) < (2, 7)
t = "compiled for" if version_static else "loaded"
- if name == "raqm":
+ if name == "zlib":
+ zlib_ng_version = version_feature("zlib_ng")
+ if zlib_ng_version is not None:
+ v += ", compiled for zlib-ng " + zlib_ng_version
+ elif name == "raqm":
for f in ("fribidi", "harfbuzz"):
v2 = version_feature(f)
if v2 is not None:
diff --git a/src/Tk/_tkmini.h b/src/Tk/_tkmini.h
index 68247bc47..a260fa0d1 100644
--- a/src/Tk/_tkmini.h
+++ b/src/Tk/_tkmini.h
@@ -80,7 +80,8 @@ typedef struct Tcl_Command_ *Tcl_Command;
typedef void *ClientData;
typedef int(Tcl_CmdProc)(
- ClientData clientData, Tcl_Interp *interp, int argc, const char *argv[]);
+ ClientData clientData, Tcl_Interp *interp, int argc, const char *argv[]
+);
typedef void(Tcl_CmdDeleteProc)(ClientData clientData);
/* Typedefs derived from function signatures in Tcl header */
@@ -90,7 +91,8 @@ typedef Tcl_Command (*Tcl_CreateCommand_t)(
const char *cmdName,
Tcl_CmdProc *proc,
ClientData clientData,
- Tcl_CmdDeleteProc *deleteProc);
+ Tcl_CmdDeleteProc *deleteProc
+);
/* Tcl_AppendResult */
typedef void (*Tcl_AppendResult_t)(Tcl_Interp *interp, ...);
@@ -127,7 +129,8 @@ typedef int (*Tk_PhotoPutBlock_t)(
int y,
int width,
int height,
- int compRule);
+ int compRule
+);
/* Tk_FindPhoto */
typedef Tk_PhotoHandle (*Tk_FindPhoto_t)(Tcl_Interp *interp, const char *imageName);
/* Tk_PhotoGetImage */
diff --git a/src/Tk/tkImaging.c b/src/Tk/tkImaging.c
index ef1c00a94..a36c3e0bd 100644
--- a/src/Tk/tkImaging.c
+++ b/src/Tk/tkImaging.c
@@ -56,31 +56,48 @@ static Tk_PhotoPutBlock_t TK_PHOTO_PUT_BLOCK;
static Imaging
ImagingFind(const char *name) {
- Py_ssize_t id;
+ PyObject *capsule;
+ int direct_pointer = 0;
+ const char *expected = "capsule object \"" IMAGING_MAGIC "\" at 0x";
- /* FIXME: use CObject instead? */
-#if defined(_WIN64)
- id = _atoi64(name);
-#else
- id = atol(name);
-#endif
- if (!id) {
+ if (name[0] == '<') {
+ name++;
+ } else {
+ // Special case for PyPy, where the string representation of a Capsule
+ // refers directly to the pointer itself, not to the PyCapsule object.
+ direct_pointer = 1;
+ }
+
+ if (strncmp(name, expected, strlen(expected))) {
return NULL;
}
- return (Imaging)id;
+ capsule = (PyObject *)strtoull(name + strlen(expected), NULL, 16);
+
+ if (direct_pointer) {
+ return (Imaging)capsule;
+ }
+
+ if (!PyCapsule_IsValid(capsule, IMAGING_MAGIC)) {
+ PyErr_Format(PyExc_TypeError, "Expected '%s' Capsule", IMAGING_MAGIC);
+ return NULL;
+ }
+
+ return (Imaging)PyCapsule_GetPointer(capsule, IMAGING_MAGIC);
}
static int
PyImagingPhotoPut(
- ClientData clientdata, Tcl_Interp *interp, int argc, const char **argv) {
+ ClientData clientdata, Tcl_Interp *interp, int argc, const char **argv
+) {
Imaging im;
Tk_PhotoHandle photo;
Tk_PhotoImageBlock block;
if (argc != 3) {
TCL_APPEND_RESULT(
- interp, "usage: ", argv[0], " destPhoto srcImage", (char *)NULL);
+ interp, "usage: ", argv[0], " destPhoto srcImage", (char *)NULL
+ );
return TCL_ERROR;
}
@@ -128,14 +145,16 @@ PyImagingPhotoPut(
block.pixelPtr = (unsigned char *)im->block;
TK_PHOTO_PUT_BLOCK(
- interp, photo, &block, 0, 0, block.width, block.height, TK_PHOTO_COMPOSITE_SET);
+ interp, photo, &block, 0, 0, block.width, block.height, TK_PHOTO_COMPOSITE_SET
+ );
return TCL_OK;
}
static int
PyImagingPhotoGet(
- ClientData clientdata, Tcl_Interp *interp, int argc, const char **argv) {
+ ClientData clientdata, Tcl_Interp *interp, int argc, const char **argv
+) {
Imaging im;
Tk_PhotoHandle photo;
Tk_PhotoImageBlock block;
@@ -143,7 +162,8 @@ PyImagingPhotoGet(
if (argc != 3) {
TCL_APPEND_RESULT(
- interp, "usage: ", argv[0], " srcPhoto destImage", (char *)NULL);
+ interp, "usage: ", argv[0], " srcPhoto destImage", (char *)NULL
+ );
return TCL_ERROR;
}
@@ -183,13 +203,15 @@ TkImaging_Init(Tcl_Interp *interp) {
"PyImagingPhoto",
PyImagingPhotoPut,
(ClientData)0,
- (Tcl_CmdDeleteProc *)NULL);
+ (Tcl_CmdDeleteProc *)NULL
+ );
TCL_CREATE_COMMAND(
interp,
"PyImagingPhotoGet",
PyImagingPhotoGet,
(ClientData)0,
- (Tcl_CmdDeleteProc *)NULL);
+ (Tcl_CmdDeleteProc *)NULL
+ );
}
/*
@@ -394,7 +416,8 @@ _func_loader(void *lib) {
}
return (
(TK_PHOTO_PUT_BLOCK = (Tk_PhotoPutBlock_t)_dfunc(lib, "Tk_PhotoPutBlock")) ==
- NULL);
+ NULL
+ );
}
int
diff --git a/src/_imaging.c b/src/_imaging.c
index f398c6c7c..5d6d97bed 100644
--- a/src/_imaging.c
+++ b/src/_imaging.c
@@ -92,19 +92,7 @@
#define _USE_MATH_DEFINES
#include
-
-/* Configuration stuff. Feel free to undef things you don't need. */
-#define WITH_IMAGECHOPS /* ImageChops support */
-#define WITH_IMAGEDRAW /* ImageDraw support */
-#define WITH_MAPPING /* use memory mapping to read some file formats */
-#define WITH_IMAGEPATH /* ImagePath stuff */
-#define WITH_ARROW /* arrow graphics stuff (experimental) */
-#define WITH_EFFECTS /* special effects */
-#define WITH_QUANTIZE /* quantization support */
-#define WITH_RANKFILTER /* rank filter */
-#define WITH_MODEFILTER /* mode filter */
-#define WITH_THREADING /* "friendly" threading support */
-#define WITH_UNSHARPMASK /* Kevin Cazabon's unsharpmask module */
+#include
#undef VERBOSE
@@ -123,8 +111,6 @@ typedef struct {
static PyTypeObject Imaging_Type;
-#ifdef WITH_IMAGEDRAW
-
typedef struct {
/* to write a character, cut out sxy from glyph data, place
at current position plus dxy, and advance by (dx, dy) */
@@ -151,8 +137,6 @@ typedef struct {
static PyTypeObject ImagingDraw_Type;
-#endif
-
typedef struct {
PyObject_HEAD ImagingObject *image;
int readonly;
@@ -215,16 +199,12 @@ PyImaging_AsImaging(PyObject *op) {
void
ImagingSectionEnter(ImagingSectionCookie *cookie) {
-#ifdef WITH_THREADING
*cookie = (PyThreadState *)PyEval_SaveThread();
-#endif
}
void
ImagingSectionLeave(ImagingSectionCookie *cookie) {
-#ifdef WITH_THREADING
PyEval_RestoreThread((PyThreadState *)*cookie);
-#endif
}
/* -------------------------------------------------------------------- */
@@ -290,7 +270,8 @@ ImagingError_ModeError(void) {
void *
ImagingError_ValueError(const char *message) {
PyErr_SetString(
- PyExc_ValueError, (message) ? (char *)message : "unrecognized argument value");
+ PyExc_ValueError, (message) ? (char *)message : "unrecognized argument value"
+ );
return NULL;
}
@@ -467,7 +448,8 @@ getpixel(Imaging im, ImagingAccess access, int x, int y) {
return Py_BuildValue("BBB", pixel.b[0], pixel.b[1], pixel.b[2]);
case 4:
return Py_BuildValue(
- "BBBB", pixel.b[0], pixel.b[1], pixel.b[2], pixel.b[3]);
+ "BBBB", pixel.b[0], pixel.b[1], pixel.b[2], pixel.b[3]
+ );
}
break;
case IMAGING_TYPE_INT32:
@@ -518,7 +500,8 @@ getink(PyObject *color, Imaging im, char *ink) {
rIsInt = 1;
} else if (im->bands == 1) {
PyErr_SetString(
- PyExc_TypeError, "color must be int or single-element tuple");
+ PyExc_TypeError, "color must be int or single-element tuple"
+ );
return NULL;
} else if (tupleSize == -1) {
PyErr_SetString(PyExc_TypeError, "color must be int or tuple");
@@ -534,8 +517,8 @@ getink(PyObject *color, Imaging im, char *ink) {
if (rIsInt != 1) {
if (tupleSize != 1) {
PyErr_SetString(
- PyExc_TypeError,
- "color must be int or single-element tuple");
+ PyExc_TypeError, "color must be int or single-element tuple"
+ );
return NULL;
} else if (!PyArg_ParseTuple(color, "L", &r)) {
return NULL;
@@ -556,7 +539,8 @@ getink(PyObject *color, Imaging im, char *ink) {
if (tupleSize != 1 && tupleSize != 2) {
PyErr_SetString(
PyExc_TypeError,
- "color must be int, or tuple of one or two elements");
+ "color must be int, or tuple of one or two elements"
+ );
return NULL;
} else if (!PyArg_ParseTuple(color, "L|i", &r, &a)) {
return NULL;
@@ -567,7 +551,8 @@ getink(PyObject *color, Imaging im, char *ink) {
PyErr_SetString(
PyExc_TypeError,
"color must be int, or tuple of one, three or four "
- "elements");
+ "elements"
+ );
return NULL;
} else if (!PyArg_ParseTuple(color, "Lii|i", &r, &g, &b, &a)) {
return NULL;
@@ -608,7 +593,8 @@ getink(PyObject *color, Imaging im, char *ink) {
} else if (tupleSize != 3) {
PyErr_SetString(
PyExc_TypeError,
- "color must be int, or tuple of one or three elements");
+ "color must be int, or tuple of one or three elements"
+ );
return NULL;
} else if (!PyArg_ParseTuple(color, "iiL", &b, &g, &r)) {
return NULL;
@@ -733,7 +719,8 @@ _alpha_composite(ImagingObject *self, PyObject *args) {
ImagingObject *imagep2;
if (!PyArg_ParseTuple(
- args, "O!O!", &Imaging_Type, &imagep1, &Imaging_Type, &imagep2)) {
+ args, "O!O!", &Imaging_Type, &imagep1, &Imaging_Type, &imagep2
+ )) {
return NULL;
}
@@ -748,7 +735,8 @@ _blend(ImagingObject *self, PyObject *args) {
alpha = 0.5;
if (!PyArg_ParseTuple(
- args, "O!O!|d", &Imaging_Type, &imagep1, &Imaging_Type, &imagep2, &alpha)) {
+ args, "O!O!|d", &Imaging_Type, &imagep1, &Imaging_Type, &imagep2, &alpha
+ )) {
return NULL;
}
@@ -827,7 +815,8 @@ _prepare_lut_table(PyObject *table, Py_ssize_t table_size) {
break;
case TYPE_FLOAT32:
memcpy(
- &item, ((char *)table_data) + i * sizeof(FLOAT32), sizeof(FLOAT32));
+ &item, ((char *)table_data) + i * sizeof(FLOAT32), sizeof(FLOAT32)
+ );
break;
case TYPE_DOUBLE:
memcpy(&dtmp, ((char *)table_data) + i * sizeof(dtmp), sizeof(dtmp));
@@ -878,7 +867,8 @@ _color_lut_3d(ImagingObject *self, PyObject *args) {
&size1D,
&size2D,
&size3D,
- &table)) {
+ &table
+ )) {
return NULL;
}
@@ -896,7 +886,8 @@ _color_lut_3d(ImagingObject *self, PyObject *args) {
if (2 > size1D || size1D > 65 || 2 > size2D || size2D > 65 || 2 > size3D ||
size3D > 65) {
PyErr_SetString(
- PyExc_ValueError, "Table size in any dimension should be from 2 to 65");
+ PyExc_ValueError, "Table size in any dimension should be from 2 to 65"
+ );
return NULL;
}
@@ -913,13 +904,8 @@ _color_lut_3d(ImagingObject *self, PyObject *args) {
}
if (!ImagingColorLUT3D_linear(
- imOut,
- self->image,
- table_channels,
- size1D,
- size2D,
- size3D,
- prepared_table)) {
+ imOut, self->image, table_channels, size1D, size2D, size3D, prepared_table
+ )) {
free(prepared_table);
ImagingDelete(imOut);
return NULL;
@@ -943,7 +929,8 @@ _convert(ImagingObject *self, PyObject *args) {
if (!PyImaging_Check(paletteimage)) {
PyObject_Print((PyObject *)paletteimage, stderr, 0);
PyErr_SetString(
- PyExc_ValueError, "palette argument must be image with mode 'P'");
+ PyExc_ValueError, "palette argument must be image with mode 'P'"
+ );
return NULL;
}
if (paletteimage->image->palette == NULL) {
@@ -953,7 +940,8 @@ _convert(ImagingObject *self, PyObject *args) {
}
return PyImagingNew(ImagingConvert(
- self->image, mode, paletteimage ? paletteimage->image->palette : NULL, dither));
+ self->image, mode, paletteimage ? paletteimage->image->palette : NULL, dither
+ ));
}
static PyObject *
@@ -961,7 +949,8 @@ _convert2(ImagingObject *self, PyObject *args) {
ImagingObject *imagep1;
ImagingObject *imagep2;
if (!PyArg_ParseTuple(
- args, "O!O!", &Imaging_Type, &imagep1, &Imaging_Type, &imagep2)) {
+ args, "O!O!", &Imaging_Type, &imagep1, &Imaging_Type, &imagep2
+ )) {
return NULL;
}
@@ -994,7 +983,8 @@ _convert_matrix(ImagingObject *self, PyObject *args) {
m + 8,
m + 9,
m + 10,
- m + 11)) {
+ m + 11
+ )) {
return NULL;
}
}
@@ -1055,7 +1045,8 @@ _filter(ImagingObject *self, PyObject *args) {
float divisor, offset;
PyObject *kernel = NULL;
if (!PyArg_ParseTuple(
- args, "(ii)ffO", &xsize, &ysize, &divisor, &offset, &kernel)) {
+ args, "(ii)ffO", &xsize, &ysize, &divisor, &offset, &kernel
+ )) {
return NULL;
}
@@ -1080,7 +1071,6 @@ _filter(ImagingObject *self, PyObject *args) {
return imOut;
}
-#ifdef WITH_UNSHARPMASK
static PyObject *
_gaussian_blur(ImagingObject *self, PyObject *args) {
Imaging imIn;
@@ -1105,7 +1095,6 @@ _gaussian_blur(ImagingObject *self, PyObject *args) {
return PyImagingNew(imOut);
}
-#endif
static PyObject *
_getpalette(ImagingObject *self, PyObject *args) {
@@ -1138,7 +1127,8 @@ _getpalette(ImagingObject *self, PyObject *args) {
}
pack(
- (UINT8 *)PyBytes_AsString(palette), self->image->palette->palette, palettesize);
+ (UINT8 *)PyBytes_AsString(palette), self->image->palette->palette, palettesize
+ );
return palette;
}
@@ -1232,7 +1222,8 @@ union hist_extrema {
static union hist_extrema *
parse_histogram_extremap(
- ImagingObject *self, PyObject *extremap, union hist_extrema *ep) {
+ ImagingObject *self, PyObject *extremap, union hist_extrema *ep
+) {
int i0, i1;
double f0, f1;
@@ -1361,7 +1352,6 @@ _entropy(ImagingObject *self, PyObject *args) {
return PyFloat_FromDouble(-entropy);
}
-#ifdef WITH_MODEFILTER
static PyObject *
_modefilter(ImagingObject *self, PyObject *args) {
int size;
@@ -1371,7 +1361,6 @@ _modefilter(ImagingObject *self, PyObject *args) {
return PyImagingNew(ImagingModeFilter(self->image, size));
}
-#endif
static PyObject *
_offset(ImagingObject *self, PyObject *args) {
@@ -1392,7 +1381,8 @@ _paste(ImagingObject *self, PyObject *args) {
int x0, y0, x1, y1;
ImagingObject *maskp = NULL;
if (!PyArg_ParseTuple(
- args, "O(iiii)|O!", &source, &x0, &y0, &x1, &y1, &Imaging_Type, &maskp)) {
+ args, "O(iiii)|O!", &source, &x0, &y0, &x1, &y1, &Imaging_Type, &maskp
+ )) {
return NULL;
}
@@ -1404,14 +1394,16 @@ _paste(ImagingObject *self, PyObject *args) {
x0,
y0,
x1,
- y1);
+ y1
+ );
} else {
if (!getink(source, self->image, ink)) {
return NULL;
}
status = ImagingFill2(
- self->image, ink, (maskp) ? maskp->image : NULL, x0, y0, x1, y1);
+ self->image, ink, (maskp) ? maskp->image : NULL, x0, y0, x1, y1
+ );
}
if (status < 0) {
@@ -1587,16 +1579,12 @@ _putdata(ImagingObject *self, PyObject *args) {
int bigendian = 0;
if (image->type == IMAGING_TYPE_SPECIAL) {
// I;16*
- if (strcmp(image->mode, "I;16N") == 0) {
+ if (strcmp(image->mode, "I;16B") == 0
#ifdef WORDS_BIGENDIAN
- bigendian = 1;
-#else
- bigendian = 0;
+ || strcmp(image->mode, "I;16N") == 0
#endif
- } else if (strcmp(image->mode, "I;16B") == 0) {
+ ) {
bigendian = 1;
- } else {
- bigendian = 0;
}
}
for (i = x = y = 0; i < n; i++) {
@@ -1700,8 +1688,6 @@ _putdata(ImagingObject *self, PyObject *args) {
return Py_None;
}
-#ifdef WITH_QUANTIZE
-
static PyObject *
_quantize(ImagingObject *self, PyObject *args) {
int colours = 256;
@@ -1718,17 +1704,18 @@ _quantize(ImagingObject *self, PyObject *args) {
return PyImagingNew(ImagingQuantize(self->image, colours, method, kmeans));
}
-#endif
static PyObject *
_putpalette(ImagingObject *self, PyObject *args) {
ImagingShuffler unpack;
int bits;
- char *rawmode, *palette_mode;
+ char *palette_mode, *rawmode;
UINT8 *palette;
Py_ssize_t palettesize;
- if (!PyArg_ParseTuple(args, "sy#", &rawmode, &palette, &palettesize)) {
+ if (!PyArg_ParseTuple(
+ args, "ssy#", &palette_mode, &rawmode, &palette, &palettesize
+ )) {
return NULL;
}
@@ -1738,7 +1725,6 @@ _putpalette(ImagingObject *self, PyObject *args) {
return NULL;
}
- palette_mode = strncmp("RGBA", rawmode, 4) == 0 ? "RGBA" : "RGB";
unpack = ImagingFindUnpacker(palette_mode, rawmode, &bits);
if (!unpack) {
PyErr_SetString(PyExc_ValueError, wrong_raw_mode);
@@ -1853,7 +1839,6 @@ _putpixel(ImagingObject *self, PyObject *args) {
return Py_None;
}
-#ifdef WITH_RANKFILTER
static PyObject *
_rankfilter(ImagingObject *self, PyObject *args) {
int size, rank;
@@ -1863,7 +1848,6 @@ _rankfilter(ImagingObject *self, PyObject *args) {
return PyImagingNew(ImagingRankFilter(self->image, size, rank));
}
-#endif
static PyObject *
_resize(ImagingObject *self, PyObject *args) {
@@ -1887,7 +1871,8 @@ _resize(ImagingObject *self, PyObject *args) {
&box[0],
&box[1],
&box[2],
- &box[3])) {
+ &box[3]
+ )) {
return NULL;
}
@@ -1923,7 +1908,8 @@ _resize(ImagingObject *self, PyObject *args) {
imOut = ImagingNewDirty(imIn->mode, xsize, ysize);
imOut = ImagingTransform(
- imOut, imIn, IMAGING_TRANSFORM_AFFINE, 0, 0, xsize, ysize, a, filter, 1);
+ imOut, imIn, IMAGING_TRANSFORM_AFFINE, 0, 0, xsize, ysize, a, filter, 1
+ );
} else {
imOut = ImagingResample(imIn, xsize, ysize, filter, box);
}
@@ -1944,14 +1930,8 @@ _reduce(ImagingObject *self, PyObject *args) {
box[3] = imIn->ysize;
if (!PyArg_ParseTuple(
- args,
- "(ii)|(iiii)",
- &xscale,
- &yscale,
- &box[0],
- &box[1],
- &box[2],
- &box[3])) {
+ args, "(ii)|(iiii)", &xscale, &yscale, &box[0], &box[1], &box[2], &box[3]
+ )) {
return NULL;
}
@@ -2053,7 +2033,8 @@ _transform(ImagingObject *self, PyObject *args) {
&method,
&data,
&filter,
- &fill)) {
+ &fill
+ )) {
return NULL;
}
@@ -2077,7 +2058,8 @@ _transform(ImagingObject *self, PyObject *args) {
}
imOut = ImagingTransform(
- self->image, imagep->image, method, x0, y0, x1, y1, a, filter, fill);
+ self->image, imagep->image, method, x0, y0, x1, y1, a, filter, fill
+ );
free(a);
@@ -2147,7 +2129,6 @@ _transpose(ImagingObject *self, PyObject *args) {
return PyImagingNew(imOut);
}
-#ifdef WITH_UNSHARPMASK
static PyObject *
_unsharp_mask(ImagingObject *self, PyObject *args) {
Imaging imIn;
@@ -2171,7 +2152,6 @@ _unsharp_mask(ImagingObject *self, PyObject *args) {
return PyImagingNew(imOut);
}
-#endif
static PyObject *
_box_blur(ImagingObject *self, PyObject *args) {
@@ -2250,7 +2230,13 @@ _getcolors(ImagingObject *self, PyObject *args) {
for (i = 0; i < colors; i++) {
ImagingColorItem *v = &items[i];
PyObject *item = Py_BuildValue(
- "iN", v->count, getpixel(self->image, self->access, v->x, v->y));
+ "iN", v->count, getpixel(self->image, self->access, v->x, v->y)
+ );
+ if (item == NULL) {
+ Py_DECREF(out);
+ free(items);
+ return NULL;
+ }
PyList_SetItem(out, i, item);
}
}
@@ -2311,14 +2297,16 @@ _getprojection(ImagingObject *self) {
}
ImagingGetProjection(
- self->image, (unsigned char *)xprofile, (unsigned char *)yprofile);
+ self->image, (unsigned char *)xprofile, (unsigned char *)yprofile
+ );
result = Py_BuildValue(
"y#y#",
xprofile,
(Py_ssize_t)self->image->xsize,
yprofile,
- (Py_ssize_t)self->image->ysize);
+ (Py_ssize_t)self->image->ysize
+ );
free(xprofile);
free(yprofile);
@@ -2392,7 +2380,8 @@ _merge(PyObject *self, PyObject *args) {
&Imaging_Type,
&band2,
&Imaging_Type,
- &band3)) {
+ &band3
+ )) {
return NULL;
}
@@ -2439,9 +2428,7 @@ _split(ImagingObject *self) {
return list;
}
-/* -------------------------------------------------------------------- */
-
-#ifdef WITH_IMAGECHOPS
+/* Channel operations (ImageChops) ------------------------------------ */
static PyObject *
_chop_invert(ImagingObject *self) {
@@ -2622,11 +2609,8 @@ _chop_overlay(ImagingObject *self, PyObject *args) {
return PyImagingNew(ImagingOverlay(self->image, imagep->image));
}
-#endif
-/* -------------------------------------------------------------------- */
-
-#ifdef WITH_IMAGEDRAW
+/* Fonts (ImageDraw and ImageFont) ------------------------------------ */
static PyObject *
_font_new(PyObject *self_, PyObject *args) {
@@ -2638,7 +2622,8 @@ _font_new(PyObject *self_, PyObject *args) {
unsigned char *glyphdata;
Py_ssize_t glyphdata_length;
if (!PyArg_ParseTuple(
- args, "O!y#", &Imaging_Type, &imagep, &glyphdata, &glyphdata_length)) {
+ args, "O!y#", &Imaging_Type, &imagep, &glyphdata, &glyphdata_length
+ )) {
return NULL;
}
@@ -2796,7 +2781,8 @@ _font_getmask(ImagingFontObject *self, PyObject *args) {
if (i == 0 || text[i] != text[i - 1]) {
ImagingDelete(bitmap);
bitmap = ImagingCrop(
- self->bitmap, glyph->sx0, glyph->sy0, glyph->sx1, glyph->sy1);
+ self->bitmap, glyph->sx0, glyph->sy0, glyph->sx1, glyph->sy1
+ );
if (!bitmap) {
goto failed;
}
@@ -2808,7 +2794,8 @@ _font_getmask(ImagingFontObject *self, PyObject *args) {
glyph->dx0 + x,
glyph->dy0 + b,
glyph->dx1 + x,
- glyph->dy1 + b);
+ glyph->dy1 + b
+ );
if (status < 0) {
goto failed;
}
@@ -2852,7 +2839,7 @@ static struct PyMethodDef _font_methods[] = {
{NULL, NULL} /* sentinel */
};
-/* -------------------------------------------------------------------- */
+/* Graphics (ImageDraw) ----------------------------------------------- */
static PyObject *
_draw_new(PyObject *self_, PyObject *args) {
@@ -2947,7 +2934,8 @@ _draw_arc(ImagingDrawObject *self, PyObject *args) {
end,
&ink,
width,
- self->blend);
+ self->blend
+ );
free(xy);
@@ -2977,13 +2965,15 @@ _draw_bitmap(ImagingDrawObject *self, PyObject *args) {
}
if (n != 1) {
PyErr_SetString(
- PyExc_TypeError, "coordinate list must contain exactly 1 coordinate");
+ PyExc_TypeError, "coordinate list must contain exactly 1 coordinate"
+ );
free(xy);
return NULL;
}
n = ImagingDrawBitmap(
- self->image->image, (int)xy[0], (int)xy[1], bitmap->image, &ink, self->blend);
+ self->image->image, (int)xy[0], (int)xy[1], bitmap->image, &ink, self->blend
+ );
free(xy);
@@ -3039,7 +3029,8 @@ _draw_chord(ImagingDrawObject *self, PyObject *args) {
&ink,
fill,
width,
- self->blend);
+ self->blend
+ );
free(xy);
@@ -3093,7 +3084,8 @@ _draw_ellipse(ImagingDrawObject *self, PyObject *args) {
&ink,
fill,
width,
- self->blend);
+ self->blend
+ );
free(xy);
@@ -3133,14 +3125,16 @@ _draw_lines(ImagingDrawObject *self, PyObject *args) {
(int)p[2],
(int)p[3],
&ink,
- self->blend) < 0) {
+ self->blend
+ ) < 0) {
free(xy);
return NULL;
}
}
if (p) { /* draw last point */
ImagingDrawPoint(
- self->image->image, (int)p[2], (int)p[3], &ink, self->blend);
+ self->image->image, (int)p[2], (int)p[3], &ink, self->blend
+ );
}
} else {
for (i = 0; i < n - 1; i++) {
@@ -3153,7 +3147,8 @@ _draw_lines(ImagingDrawObject *self, PyObject *args) {
(int)p[3],
&ink,
width,
- self->blend) < 0) {
+ self->blend
+ ) < 0) {
free(xy);
return NULL;
}
@@ -3185,7 +3180,8 @@ _draw_points(ImagingDrawObject *self, PyObject *args) {
for (i = 0; i < n; i++) {
double *p = &xy[i + i];
if (ImagingDrawPoint(
- self->image->image, (int)p[0], (int)p[1], &ink, self->blend) < 0) {
+ self->image->image, (int)p[0], (int)p[1], &ink, self->blend
+ ) < 0) {
free(xy);
return NULL;
}
@@ -3197,8 +3193,6 @@ _draw_points(ImagingDrawObject *self, PyObject *args) {
return Py_None;
}
-#ifdef WITH_ARROW
-
/* from outline.c */
extern ImagingOutline
PyOutline_AsOutline(PyObject *outline);
@@ -3228,8 +3222,6 @@ _draw_outline(ImagingDrawObject *self, PyObject *args) {
return Py_None;
}
-#endif
-
static PyObject *
_draw_pieslice(ImagingDrawObject *self, PyObject *args) {
double *xy;
@@ -3274,7 +3266,8 @@ _draw_pieslice(ImagingDrawObject *self, PyObject *args) {
&ink,
fill,
width,
- self->blend);
+ self->blend
+ );
free(xy);
@@ -3306,7 +3299,8 @@ _draw_polygon(ImagingDrawObject *self, PyObject *args) {
}
if (n < 2) {
PyErr_SetString(
- PyExc_TypeError, "coordinate list must contain at least 2 coordinates");
+ PyExc_TypeError, "coordinate list must contain at least 2 coordinates"
+ );
free(xy);
return NULL;
}
@@ -3379,7 +3373,8 @@ _draw_rectangle(ImagingDrawObject *self, PyObject *args) {
&ink,
fill,
width,
- self->blend);
+ self->blend
+ );
free(xy);
@@ -3392,12 +3387,9 @@ _draw_rectangle(ImagingDrawObject *self, PyObject *args) {
}
static struct PyMethodDef _draw_methods[] = {
-#ifdef WITH_IMAGEDRAW
/* Graphics (ImageDraw) */
{"draw_lines", (PyCFunction)_draw_lines, METH_VARARGS},
-#ifdef WITH_ARROW
{"draw_outline", (PyCFunction)_draw_outline, METH_VARARGS},
-#endif
{"draw_polygon", (PyCFunction)_draw_polygon, METH_VARARGS},
{"draw_rectangle", (PyCFunction)_draw_rectangle, METH_VARARGS},
{"draw_points", (PyCFunction)_draw_points, METH_VARARGS},
@@ -3407,12 +3399,9 @@ static struct PyMethodDef _draw_methods[] = {
{"draw_ellipse", (PyCFunction)_draw_ellipse, METH_VARARGS},
{"draw_pieslice", (PyCFunction)_draw_pieslice, METH_VARARGS},
{"draw_ink", (PyCFunction)_draw_ink, METH_VARARGS},
-#endif
{NULL, NULL} /* sentinel */
};
-#endif
-
static PyObject *
pixel_access_new(ImagingObject *imagep, PyObject *args) {
PixelAccessObject *self;
@@ -3493,11 +3482,9 @@ pixel_access_setitem(PixelAccessObject *self, PyObject *xy, PyObject *color) {
}
/* -------------------------------------------------------------------- */
-/* EFFECTS (experimental) */
+/* EFFECTS (experimental) */
/* -------------------------------------------------------------------- */
-#ifdef WITH_EFFECTS
-
static PyObject *
_effect_mandelbrot(ImagingObject *self, PyObject *args) {
int xsize = 512;
@@ -3519,7 +3506,8 @@ _effect_mandelbrot(ImagingObject *self, PyObject *args) {
&extent[1],
&extent[2],
&extent[3],
- &quality)) {
+ &quality
+ )) {
return NULL;
}
@@ -3548,8 +3536,6 @@ _effect_spread(ImagingObject *self, PyObject *args) {
return PyImagingNew(ImagingEffectSpread(self->image, dist));
}
-#endif
-
/* -------------------------------------------------------------------- */
/* UTILITIES */
/* -------------------------------------------------------------------- */
@@ -3630,20 +3616,14 @@ static struct PyMethodDef methods[] = {
{"filter", (PyCFunction)_filter, METH_VARARGS},
{"histogram", (PyCFunction)_histogram, METH_VARARGS},
{"entropy", (PyCFunction)_entropy, METH_VARARGS},
-#ifdef WITH_MODEFILTER
{"modefilter", (PyCFunction)_modefilter, METH_VARARGS},
-#endif
{"offset", (PyCFunction)_offset, METH_VARARGS},
{"paste", (PyCFunction)_paste, METH_VARARGS},
{"point", (PyCFunction)_point, METH_VARARGS},
{"point_transform", (PyCFunction)_point_transform, METH_VARARGS},
{"putdata", (PyCFunction)_putdata, METH_VARARGS},
-#ifdef WITH_QUANTIZE
{"quantize", (PyCFunction)_quantize, METH_VARARGS},
-#endif
-#ifdef WITH_RANKFILTER
{"rankfilter", (PyCFunction)_rankfilter, METH_VARARGS},
-#endif
{"resize", (PyCFunction)_resize, METH_VARARGS},
{"reduce", (PyCFunction)_reduce, METH_VARARGS},
{"transpose", (PyCFunction)_transpose, METH_VARARGS},
@@ -3669,7 +3649,6 @@ static struct PyMethodDef methods[] = {
{"putpalettealpha", (PyCFunction)_putpalettealpha, METH_VARARGS},
{"putpalettealphas", (PyCFunction)_putpalettealphas, METH_VARARGS},
-#ifdef WITH_IMAGECHOPS
/* Channel operations (ImageChops) */
{"chop_invert", (PyCFunction)_chop_invert, METH_NOARGS},
{"chop_lighter", (PyCFunction)_chop_lighter, METH_VARARGS},
@@ -3688,24 +3667,15 @@ static struct PyMethodDef methods[] = {
{"chop_hard_light", (PyCFunction)_chop_hard_light, METH_VARARGS},
{"chop_overlay", (PyCFunction)_chop_overlay, METH_VARARGS},
-#endif
-
-#ifdef WITH_UNSHARPMASK
- /* Kevin Cazabon's unsharpmask extension */
+ /* Unsharpmask extension */
{"gaussian_blur", (PyCFunction)_gaussian_blur, METH_VARARGS},
{"unsharp_mask", (PyCFunction)_unsharp_mask, METH_VARARGS},
-#endif
-
{"box_blur", (PyCFunction)_box_blur, METH_VARARGS},
-#ifdef WITH_EFFECTS
/* Special effects */
{"effect_spread", (PyCFunction)_effect_spread, METH_VARARGS},
-#endif
/* Misc. */
- {"new_block", (PyCFunction)_new_block, METH_VARARGS},
-
{"save_ppm", (PyCFunction)_save_ppm, METH_VARARGS},
{NULL, NULL} /* sentinel */
@@ -3730,16 +3700,40 @@ _getattr_bands(ImagingObject *self, void *closure) {
static PyObject *
_getattr_id(ImagingObject *self, void *closure) {
+ if (PyErr_WarnEx(
+ PyExc_DeprecationWarning,
+ "id property is deprecated and will be removed in Pillow 12 (2025-10-15)",
+ 1
+ ) < 0) {
+ return NULL;
+ }
return PyLong_FromSsize_t((Py_ssize_t)self->image);
}
+static void
+_ptr_destructor(PyObject *capsule) {
+ PyObject *self = (PyObject *)PyCapsule_GetContext(capsule);
+ Py_DECREF(self);
+}
+
static PyObject *
_getattr_ptr(ImagingObject *self, void *closure) {
- return PyCapsule_New(self->image, IMAGING_MAGIC, NULL);
+ PyObject *capsule = PyCapsule_New(self->image, IMAGING_MAGIC, _ptr_destructor);
+ Py_INCREF(self);
+ PyCapsule_SetContext(capsule, self);
+ return capsule;
}
static PyObject *
_getattr_unsafe_ptrs(ImagingObject *self, void *closure) {
+ if (PyErr_WarnEx(
+ PyExc_DeprecationWarning,
+ "unsafe_ptrs property is deprecated and will be removed in Pillow 12 "
+ "(2025-10-15)",
+ 1
+ ) < 0) {
+ return NULL;
+ }
return Py_BuildValue(
"(sn)(sn)(sn)",
"image8",
@@ -3747,7 +3741,8 @@ _getattr_unsafe_ptrs(ImagingObject *self, void *closure) {
"image32",
self->image->image32,
"image",
- self->image->image);
+ self->image->image
+ );
}
static struct PyGetSetDef getsetters[] = {
@@ -3757,7 +3752,8 @@ static struct PyGetSetDef getsetters[] = {
{"id", (getter)_getattr_id},
{"ptr", (getter)_getattr_ptr},
{"unsafe_ptrs", (getter)_getattr_unsafe_ptrs},
- {NULL}};
+ {NULL}
+};
/* basic sequence semantics */
@@ -3828,8 +3824,6 @@ static PyTypeObject Imaging_Type = {
getsetters, /*tp_getset*/
};
-#ifdef WITH_IMAGEDRAW
-
static PyTypeObject ImagingFont_Type = {
PyVarObject_HEAD_INIT(NULL, 0) "ImagingFont", /*tp_name*/
sizeof(ImagingFontObject), /*tp_basicsize*/
@@ -3896,8 +3890,6 @@ static PyTypeObject ImagingDraw_Type = {
0, /*tp_getset*/
};
-#endif
-
static PyMappingMethods pixel_access_as_mapping = {
(lenfunc)NULL, /*mp_length*/
(binaryfunc)pixel_access_getitem, /*mp_subscript*/
@@ -3929,7 +3921,6 @@ static PyObject *
_get_stats(PyObject *self, PyObject *args) {
PyObject *d;
PyObject *v;
- ImagingMemoryArena arena = &ImagingDefaultArena;
if (!PyArg_ParseTuple(args, ":get_stats")) {
return NULL;
@@ -3939,6 +3930,10 @@ _get_stats(PyObject *self, PyObject *args) {
if (!d) {
return NULL;
}
+
+ MUTEX_LOCK(&ImagingDefaultArena.mutex);
+ ImagingMemoryArena arena = &ImagingDefaultArena;
+
v = PyLong_FromLong(arena->stats_new_count);
PyDict_SetItemString(d, "new_count", v ? v : Py_None);
Py_XDECREF(v);
@@ -3962,22 +3957,25 @@ _get_stats(PyObject *self, PyObject *args) {
v = PyLong_FromLong(arena->blocks_cached);
PyDict_SetItemString(d, "blocks_cached", v ? v : Py_None);
Py_XDECREF(v);
+
+ MUTEX_UNLOCK(&ImagingDefaultArena.mutex);
return d;
}
static PyObject *
_reset_stats(PyObject *self, PyObject *args) {
- ImagingMemoryArena arena = &ImagingDefaultArena;
-
if (!PyArg_ParseTuple(args, ":reset_stats")) {
return NULL;
}
+ MUTEX_LOCK(&ImagingDefaultArena.mutex);
+ ImagingMemoryArena arena = &ImagingDefaultArena;
arena->stats_new_count = 0;
arena->stats_allocated_blocks = 0;
arena->stats_reused_blocks = 0;
arena->stats_reallocated_blocks = 0;
arena->stats_freed_blocks = 0;
+ MUTEX_UNLOCK(&ImagingDefaultArena.mutex);
Py_INCREF(Py_None);
return Py_None;
@@ -3989,7 +3987,10 @@ _get_alignment(PyObject *self, PyObject *args) {
return NULL;
}
- return PyLong_FromLong(ImagingDefaultArena.alignment);
+ MUTEX_LOCK(&ImagingDefaultArena.mutex);
+ int alignment = ImagingDefaultArena.alignment;
+ MUTEX_UNLOCK(&ImagingDefaultArena.mutex);
+ return PyLong_FromLong(alignment);
}
static PyObject *
@@ -3998,7 +3999,10 @@ _get_block_size(PyObject *self, PyObject *args) {
return NULL;
}
- return PyLong_FromLong(ImagingDefaultArena.block_size);
+ MUTEX_LOCK(&ImagingDefaultArena.mutex);
+ int block_size = ImagingDefaultArena.block_size;
+ MUTEX_UNLOCK(&ImagingDefaultArena.mutex);
+ return PyLong_FromLong(block_size);
}
static PyObject *
@@ -4007,7 +4011,10 @@ _get_blocks_max(PyObject *self, PyObject *args) {
return NULL;
}
- return PyLong_FromLong(ImagingDefaultArena.blocks_max);
+ MUTEX_LOCK(&ImagingDefaultArena.mutex);
+ int blocks_max = ImagingDefaultArena.blocks_max;
+ MUTEX_UNLOCK(&ImagingDefaultArena.mutex);
+ return PyLong_FromLong(blocks_max);
}
static PyObject *
@@ -4027,7 +4034,9 @@ _set_alignment(PyObject *self, PyObject *args) {
return NULL;
}
+ MUTEX_LOCK(&ImagingDefaultArena.mutex);
ImagingDefaultArena.alignment = alignment;
+ MUTEX_UNLOCK(&ImagingDefaultArena.mutex);
Py_INCREF(Py_None);
return Py_None;
@@ -4050,7 +4059,9 @@ _set_block_size(PyObject *self, PyObject *args) {
return NULL;
}
+ MUTEX_LOCK(&ImagingDefaultArena.mutex);
ImagingDefaultArena.block_size = block_size;
+ MUTEX_UNLOCK(&ImagingDefaultArena.mutex);
Py_INCREF(Py_None);
return Py_None;
@@ -4066,14 +4077,18 @@ _set_blocks_max(PyObject *self, PyObject *args) {
if (blocks_max < 0) {
PyErr_SetString(PyExc_ValueError, "blocks_max should be greater than 0");
return NULL;
- } else if (
- (unsigned long)blocks_max >
+ }
+
+ if ((unsigned long)blocks_max >
SIZE_MAX / sizeof(ImagingDefaultArena.blocks_pool[0])) {
PyErr_SetString(PyExc_ValueError, "blocks_max is too large");
return NULL;
}
- if (!ImagingMemorySetBlocksMax(&ImagingDefaultArena, blocks_max)) {
+ MUTEX_LOCK(&ImagingDefaultArena.mutex);
+ int status = ImagingMemorySetBlocksMax(&ImagingDefaultArena, blocks_max);
+ MUTEX_UNLOCK(&ImagingDefaultArena.mutex);
+ if (!status) {
return ImagingError_MemoryError();
}
@@ -4089,7 +4104,9 @@ _clear_cache(PyObject *self, PyObject *args) {
return NULL;
}
+ MUTEX_LOCK(&ImagingDefaultArena.mutex);
ImagingMemoryClearCache(&ImagingDefaultArena, i);
+ MUTEX_UNLOCK(&ImagingDefaultArena.mutex);
Py_INCREF(Py_None);
return Py_None;
@@ -4198,6 +4215,7 @@ static PyMethodDef functions[] = {
{"blend", (PyCFunction)_blend, METH_VARARGS},
{"fill", (PyCFunction)_fill, METH_VARARGS},
{"new", (PyCFunction)_new, METH_VARARGS},
+ {"new_block", (PyCFunction)_new_block, METH_VARARGS},
{"merge", (PyCFunction)_merge, METH_VARARGS},
/* Functions */
@@ -4241,13 +4259,11 @@ static PyMethodDef functions[] = {
{"zip_encoder", (PyCFunction)PyImaging_ZipEncoderNew, METH_VARARGS},
#endif
-/* Memory mapping */
-#ifdef WITH_MAPPING
+ /* Memory mapping */
{"map_buffer", (PyCFunction)PyImaging_MapBuffer, METH_VARARGS},
-#endif
-/* Display support */
#ifdef _WIN32
+ /* Display support */
{"display", (PyCFunction)PyImaging_DisplayWin32, METH_VARARGS},
{"display_mode", (PyCFunction)PyImaging_DisplayModeWin32, METH_VARARGS},
{"grabscreen_win32", (PyCFunction)PyImaging_GrabScreenWin32, METH_VARARGS},
@@ -4263,30 +4279,22 @@ static PyMethodDef functions[] = {
/* Utilities */
{"getcodecstatus", (PyCFunction)_getcodecstatus, METH_VARARGS},
-/* Special effects (experimental) */
-#ifdef WITH_EFFECTS
+ /* Special effects (experimental) */
{"effect_mandelbrot", (PyCFunction)_effect_mandelbrot, METH_VARARGS},
{"effect_noise", (PyCFunction)_effect_noise, METH_VARARGS},
{"linear_gradient", (PyCFunction)_linear_gradient, METH_VARARGS},
{"radial_gradient", (PyCFunction)_radial_gradient, METH_VARARGS},
{"wedge", (PyCFunction)_linear_gradient, METH_VARARGS}, /* Compatibility */
-#endif
-/* Drawing support stuff */
-#ifdef WITH_IMAGEDRAW
+ /* Drawing support stuff */
{"font", (PyCFunction)_font_new, METH_VARARGS},
{"draw", (PyCFunction)_draw_new, METH_VARARGS},
-#endif
-/* Experimental path stuff */
-#ifdef WITH_IMAGEPATH
+ /* Experimental path stuff */
{"path", (PyCFunction)PyPath_Create, METH_VARARGS},
-#endif
-/* Experimental arrow graphics stuff */
-#ifdef WITH_ARROW
+ /* Experimental arrow graphics stuff */
{"outline", (PyCFunction)PyOutline_Create, METH_VARARGS},
-#endif
/* Resource management */
{"get_stats", (PyCFunction)_get_stats, METH_VARARGS},
@@ -4311,16 +4319,12 @@ setup_module(PyObject *m) {
if (PyType_Ready(&Imaging_Type) < 0) {
return -1;
}
-
-#ifdef WITH_IMAGEDRAW
if (PyType_Ready(&ImagingFont_Type) < 0) {
return -1;
}
-
if (PyType_Ready(&ImagingDraw_Type) < 0) {
return -1;
}
-#endif
if (PyType_Ready(&PixelAccess_Type) < 0) {
return -1;
}
@@ -4393,6 +4397,20 @@ setup_module(PyObject *m) {
}
#endif
+ PyObject *have_zlibng;
+#ifdef ZLIBNG_VERSION
+ have_zlibng = Py_True;
+ {
+ PyObject *v = PyUnicode_FromString(ZLIBNG_VERSION);
+ PyDict_SetItemString(d, "zlib_ng_version", v ? v : Py_None);
+ Py_XDECREF(v);
+ }
+#else
+ have_zlibng = Py_False;
+#endif
+ Py_INCREF(have_zlibng);
+ PyModule_AddObject(m, "HAVE_ZLIBNG", have_zlibng);
+
#ifdef HAVE_LIBTIFF
{
extern const char *ImagingTiffVersion(void);
@@ -4423,7 +4441,8 @@ setup_module(PyObject *m) {
PyObject *pillow_version = PyUnicode_FromString(version);
PyDict_SetItemString(
- d, "PILLOW_VERSION", pillow_version ? pillow_version : Py_None);
+ d, "PILLOW_VERSION", pillow_version ? pillow_version : Py_None
+ );
Py_XDECREF(pillow_version);
return 0;
@@ -4448,5 +4467,9 @@ PyInit__imaging(void) {
return NULL;
}
+#ifdef Py_GIL_DISABLED
+ PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
+#endif
+
return m;
}
diff --git a/src/_imagingcms.c b/src/_imagingcms.c
index 2b9612db7..1805ebde1 100644
--- a/src/_imagingcms.c
+++ b/src/_imagingcms.c
@@ -223,20 +223,22 @@ findLCMStype(char *PILmode) {
if (strcmp(PILmode, "CMYK") == 0) {
return TYPE_CMYK_8;
}
- if (strcmp(PILmode, "L;16") == 0) {
+ if (strcmp(PILmode, "I;16") == 0 || strcmp(PILmode, "I;16L") == 0 ||
+ strcmp(PILmode, "L;16") == 0) {
return TYPE_GRAY_16;
}
- if (strcmp(PILmode, "L;16B") == 0) {
+ if (strcmp(PILmode, "I;16B") == 0 || strcmp(PILmode, "L;16B") == 0) {
return TYPE_GRAY_16_SE;
}
- if (strcmp(PILmode, "YCCA") == 0 || strcmp(PILmode, "YCC") == 0) {
+ if (strcmp(PILmode, "YCbCr") == 0 || strcmp(PILmode, "YCCA") == 0 ||
+ strcmp(PILmode, "YCC") == 0) {
return TYPE_YCbCr_8;
}
if (strcmp(PILmode, "LAB") == 0) {
// LabX equivalent like ALab, but not reversed -- no #define in lcms2
return (COLORSPACE_SH(PT_LabV2) | CHANNELS_SH(3) | BYTES_SH(1) | EXTRA_SH(1));
}
- /* presume "L" by default */
+ /* presume "1" or "L" by default */
return TYPE_GRAY_8;
}
@@ -329,7 +331,8 @@ pyCMScopyAux(cmsHTRANSFORM hTransform, Imaging imDst, const Imaging imSrc) {
memcpy(
pDstExtras + x * dstChunkSize,
pSrcExtras + x * srcChunkSize,
- channelSize);
+ channelSize
+ );
}
}
}
@@ -343,10 +346,10 @@ pyCMSdoTransform(Imaging im, Imaging imOut, cmsHTRANSFORM hTransform) {
return -1;
}
- Py_BEGIN_ALLOW_THREADS
+ Py_BEGIN_ALLOW_THREADS;
- // transform color channels only
- for (i = 0; i < im->ysize; i++) {
+ // transform color channels only
+ for (i = 0; i < im->ysize; i++) {
cmsDoTransform(hTransform, im->image[i], imOut->image[i], im->xsize);
}
@@ -359,9 +362,9 @@ pyCMSdoTransform(Imaging im, Imaging imOut, cmsHTRANSFORM hTransform) {
// enough available on all platforms, so we polyfill it here for now.
pyCMScopyAux(hTransform, imOut, im);
- Py_END_ALLOW_THREADS
+ Py_END_ALLOW_THREADS;
- return 0;
+ return 0;
}
static cmsHTRANSFORM
@@ -371,19 +374,21 @@ _buildTransform(
char *sInMode,
char *sOutMode,
int iRenderingIntent,
- cmsUInt32Number cmsFLAGS) {
+ cmsUInt32Number cmsFLAGS
+) {
cmsHTRANSFORM hTransform;
- Py_BEGIN_ALLOW_THREADS
+ Py_BEGIN_ALLOW_THREADS;
- /* create the transform */
- hTransform = cmsCreateTransform(
- hInputProfile,
- findLCMStype(sInMode),
- hOutputProfile,
- findLCMStype(sOutMode),
- iRenderingIntent,
- cmsFLAGS);
+ /* create the transform */
+ hTransform = cmsCreateTransform(
+ hInputProfile,
+ findLCMStype(sInMode),
+ hOutputProfile,
+ findLCMStype(sOutMode),
+ iRenderingIntent,
+ cmsFLAGS
+ );
Py_END_ALLOW_THREADS;
@@ -403,21 +408,23 @@ _buildProofTransform(
char *sOutMode,
int iRenderingIntent,
int iProofIntent,
- cmsUInt32Number cmsFLAGS) {
+ cmsUInt32Number cmsFLAGS
+) {
cmsHTRANSFORM hTransform;
- Py_BEGIN_ALLOW_THREADS
+ Py_BEGIN_ALLOW_THREADS;
- /* create the transform */
- hTransform = cmsCreateProofingTransform(
- hInputProfile,
- findLCMStype(sInMode),
- hOutputProfile,
- findLCMStype(sOutMode),
- hProofProfile,
- iRenderingIntent,
- iProofIntent,
- cmsFLAGS);
+ /* create the transform */
+ hTransform = cmsCreateProofingTransform(
+ hInputProfile,
+ findLCMStype(sInMode),
+ hOutputProfile,
+ findLCMStype(sOutMode),
+ hProofProfile,
+ iRenderingIntent,
+ iProofIntent,
+ cmsFLAGS
+ );
Py_END_ALLOW_THREADS;
@@ -452,7 +459,8 @@ buildTransform(PyObject *self, PyObject *args) {
&sInMode,
&sOutMode,
&iRenderingIntent,
- &cmsFLAGS)) {
+ &cmsFLAGS
+ )) {
return NULL;
}
@@ -462,7 +470,8 @@ buildTransform(PyObject *self, PyObject *args) {
sInMode,
sOutMode,
iRenderingIntent,
- cmsFLAGS);
+ cmsFLAGS
+ );
if (!transform) {
return NULL;
@@ -497,7 +506,8 @@ buildProofTransform(PyObject *self, PyObject *args) {
&sOutMode,
&iRenderingIntent,
&iProofIntent,
- &cmsFLAGS)) {
+ &cmsFLAGS
+ )) {
return NULL;
}
@@ -509,7 +519,8 @@ buildProofTransform(PyObject *self, PyObject *args) {
sOutMode,
iRenderingIntent,
iProofIntent,
- cmsFLAGS);
+ cmsFLAGS
+ );
if (!transform) {
return NULL;
@@ -520,23 +531,24 @@ buildProofTransform(PyObject *self, PyObject *args) {
static PyObject *
cms_transform_apply(CmsTransformObject *self, PyObject *args) {
- Py_ssize_t idIn;
- Py_ssize_t idOut;
+ PyObject *i0, *i1;
Imaging im;
Imaging imOut;
- int result;
-
- if (!PyArg_ParseTuple(args, "nn:apply", &idIn, &idOut)) {
+ if (!PyArg_ParseTuple(args, "OO:apply", &i0, &i1)) {
return NULL;
}
- im = (Imaging)idIn;
- imOut = (Imaging)idOut;
+ if (!PyCapsule_IsValid(i0, IMAGING_MAGIC) ||
+ !PyCapsule_IsValid(i1, IMAGING_MAGIC)) {
+ PyErr_Format(PyExc_TypeError, "Expected '%s' Capsule", IMAGING_MAGIC);
+ return NULL;
+ }
- result = pyCMSdoTransform(im, imOut, self->transform);
+ im = (Imaging)PyCapsule_GetPointer(i0, IMAGING_MAGIC);
+ imOut = (Imaging)PyCapsule_GetPointer(i1, IMAGING_MAGIC);
- return Py_BuildValue("i", result);
+ return Py_BuildValue("i", pyCMSdoTransform(im, imOut, self->transform));
}
/* -------------------------------------------------------------------- */
@@ -561,7 +573,8 @@ createProfile(PyObject *self, PyObject *args) {
PyErr_SetString(
PyExc_ValueError,
"ERROR: Could not calculate white point from color temperature "
- "provided, must be float in degrees Kelvin");
+ "provided, must be float in degrees Kelvin"
+ );
return NULL;
}
hProfile = cmsCreateLab2Profile(&whitePoint);
@@ -622,7 +635,8 @@ cms_get_display_profile_win32(PyObject *self, PyObject *args) {
HANDLE handle = 0;
int is_dc = 0;
if (!PyArg_ParseTuple(
- args, "|" F_HANDLE "i:get_display_profile", &handle, &is_dc)) {
+ args, "|" F_HANDLE "i:get_display_profile", &handle, &is_dc
+ )) {
return NULL;
}
@@ -727,7 +741,8 @@ _xyz_py(cmsCIEXYZ *XYZ) {
cmsCIExyY xyY;
cmsXYZ2xyY(&xyY, XYZ);
return Py_BuildValue(
- "((d,d,d),(d,d,d))", XYZ->X, XYZ->Y, XYZ->Z, xyY.x, xyY.y, xyY.Y);
+ "((d,d,d),(d,d,d))", XYZ->X, XYZ->Y, XYZ->Z, xyY.x, xyY.y, xyY.Y
+ );
}
static PyObject *
@@ -756,7 +771,8 @@ _xyz3_py(cmsCIEXYZ *XYZ) {
xyY[1].Y,
xyY[2].x,
xyY[2].y,
- xyY[2].Y);
+ xyY[2].Y
+ );
}
static PyObject *
@@ -807,7 +823,8 @@ _profile_read_ciexyy_triple(CmsProfileObject *self, cmsTagSignature info) {
triple->Green.Y,
triple->Blue.x,
triple->Blue.y,
- triple->Blue.Y);
+ triple->Blue.Y
+ );
}
static PyObject *
@@ -871,7 +888,8 @@ _calculate_rgb_primaries(CmsProfileObject *self, cmsCIEXYZTRIPLE *result) {
hXYZ,
TYPE_XYZ_DBL,
INTENT_RELATIVE_COLORIMETRIC,
- cmsFLAGS_NOCACHE | cmsFLAGS_NOOPTIMIZE);
+ cmsFLAGS_NOCACHE | cmsFLAGS_NOOPTIMIZE
+ );
cmsCloseProfile(hXYZ);
if (hTransform == NULL) {
return 0;
@@ -887,7 +905,8 @@ _check_intent(
int clut,
cmsHPROFILE hProfile,
cmsUInt32Number Intent,
- cmsUInt32Number UsedDirection) {
+ cmsUInt32Number UsedDirection
+) {
if (clut) {
return cmsIsCLUT(hProfile, Intent, UsedDirection);
} else {
@@ -932,7 +951,8 @@ _is_intent_supported(CmsProfileObject *self, int clut) {
_check_intent(clut, self->profile, intent, LCMS_USED_AS_OUTPUT) ? Py_True
: Py_False,
_check_intent(clut, self->profile, intent, LCMS_USED_AS_PROOF) ? Py_True
- : Py_False);
+ : Py_False
+ );
if (id == NULL || entry == NULL) {
Py_XDECREF(id);
Py_XDECREF(entry);
@@ -966,7 +986,8 @@ static PyMethodDef pyCMSdll_methods[] = {
{"get_display_profile_win32", cms_get_display_profile_win32, METH_VARARGS},
#endif
- {NULL, NULL}};
+ {NULL, NULL}
+};
static struct PyMethodDef cms_profile_methods[] = {
{"is_intent_supported", (PyCFunction)cms_profile_is_intent_supported, METH_VARARGS},
@@ -1026,7 +1047,8 @@ cms_profile_getattr_creation_date(CmsProfileObject *self, void *closure) {
}
return PyDateTime_FromDateAndTime(
- 1900 + ct.tm_year, ct.tm_mon, ct.tm_mday, ct.tm_hour, ct.tm_min, ct.tm_sec, 0);
+ 1900 + ct.tm_year, ct.tm_mon, ct.tm_mday, ct.tm_hour, ct.tm_min, ct.tm_sec, 0
+ );
}
static PyObject *
@@ -1104,13 +1126,15 @@ cms_profile_getattr_colorimetric_intent(CmsProfileObject *self, void *closure) {
static PyObject *
cms_profile_getattr_perceptual_rendering_intent_gamut(
- CmsProfileObject *self, void *closure) {
+ CmsProfileObject *self, void *closure
+) {
return _profile_read_signature(self, cmsSigPerceptualRenderingIntentGamutTag);
}
static PyObject *
cms_profile_getattr_saturation_rendering_intent_gamut(
- CmsProfileObject *self, void *closure) {
+ CmsProfileObject *self, void *closure
+) {
return _profile_read_signature(self, cmsSigSaturationRenderingIntentGamutTag);
}
@@ -1143,7 +1167,8 @@ cms_profile_getattr_blue_colorant(CmsProfileObject *self, void *closure) {
static PyObject *
cms_profile_getattr_media_white_point_temperature(
- CmsProfileObject *self, void *closure) {
+ CmsProfileObject *self, void *closure
+) {
cmsCIEXYZ *XYZ;
cmsCIExyY xyY;
cmsFloat64Number tempK;
@@ -1327,7 +1352,8 @@ cms_profile_getattr_icc_measurement_condition(CmsProfileObject *self, void *clos
"flare",
mc->Flare,
"illuminant_type",
- _illu_map(mc->IlluminantType));
+ _illu_map(mc->IlluminantType)
+ );
}
static PyObject *
@@ -1357,7 +1383,8 @@ cms_profile_getattr_icc_viewing_condition(CmsProfileObject *self, void *closure)
vc->SurroundXYZ.Y,
vc->SurroundXYZ.Z,
"illuminant_type",
- _illu_map(vc->IlluminantType));
+ _illu_map(vc->IlluminantType)
+ );
}
static struct PyGetSetDef cms_profile_getsetters[] = {
@@ -1405,11 +1432,12 @@ static struct PyGetSetDef cms_profile_getsetters[] = {
{"colorant_table_out", (getter)cms_profile_getattr_colorant_table_out},
{"intent_supported", (getter)cms_profile_getattr_is_intent_supported},
{"clut", (getter)cms_profile_getattr_is_clut},
- {"icc_measurement_condition",
- (getter)cms_profile_getattr_icc_measurement_condition},
+ {"icc_measurement_condition", (getter)cms_profile_getattr_icc_measurement_condition
+ },
{"icc_viewing_condition", (getter)cms_profile_getattr_icc_viewing_condition},
- {NULL}};
+ {NULL}
+};
static PyTypeObject CmsProfile_Type = {
PyVarObject_HEAD_INIT(NULL, 0) "PIL.ImageCms.core.CmsProfile", /*tp_name*/
@@ -1536,5 +1564,9 @@ PyInit__imagingcms(void) {
PyDateTime_IMPORT;
+#ifdef Py_GIL_DISABLED
+ PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
+#endif
+
return m;
}
diff --git a/src/_imagingft.c b/src/_imagingft.c
index e83ddfec1..d38279f3e 100644
--- a/src/_imagingft.c
+++ b/src/_imagingft.c
@@ -20,6 +20,7 @@
#define PY_SSIZE_T_CLEAN
#include "Python.h"
+#include "thirdparty/pythoncapi_compat.h"
#include "libImaging/Imaging.h"
#include
@@ -81,6 +82,9 @@ struct {
/* font objects */
static FT_Library library;
+#ifdef Py_GIL_DISABLED
+static PyMutex ft_library_mutex;
+#endif
typedef struct {
PyObject_HEAD FT_Face face;
@@ -125,7 +129,8 @@ getfont(PyObject *self_, PyObject *args, PyObject *kw) {
unsigned char *font_bytes;
Py_ssize_t font_bytes_size = 0;
static char *kwlist[] = {
- "filename", "size", "index", "encoding", "font_bytes", "layout_engine", NULL};
+ "filename", "size", "index", "encoding", "font_bytes", "layout_engine", NULL
+ };
if (!library) {
PyErr_SetString(PyExc_OSError, "failed to initialize FreeType library");
@@ -147,7 +152,8 @@ getfont(PyObject *self_, PyObject *args, PyObject *kw) {
&encoding,
&font_bytes,
&font_bytes_size,
- &layout_engine)) {
+ &layout_engine
+ )) {
PyConfig_Clear(&config);
return NULL;
}
@@ -165,7 +171,8 @@ getfont(PyObject *self_, PyObject *args, PyObject *kw) {
&encoding,
&font_bytes,
&font_bytes_size,
- &layout_engine)) {
+ &layout_engine
+ )) {
return NULL;
}
#endif
@@ -183,7 +190,9 @@ getfont(PyObject *self_, PyObject *args, PyObject *kw) {
if (filename && font_bytes_size <= 0) {
self->font_bytes = NULL;
+ MUTEX_LOCK(&ft_library_mutex);
error = FT_New_Face(library, filename, index, &self->face);
+ MUTEX_UNLOCK(&ft_library_mutex);
} else {
/* need to have allocated storage for font_bytes for the life of the object.*/
/* Don't free this before FT_Done_Face */
@@ -193,12 +202,15 @@ getfont(PyObject *self_, PyObject *args, PyObject *kw) {
}
if (!error) {
memcpy(self->font_bytes, font_bytes, (size_t)font_bytes_size);
+ MUTEX_LOCK(&ft_library_mutex);
error = FT_New_Memory_Face(
library,
(FT_Byte *)self->font_bytes,
font_bytes_size,
index,
- &self->face);
+ &self->face
+ );
+ MUTEX_UNLOCK(&ft_library_mutex);
}
}
@@ -233,18 +245,6 @@ getfont(PyObject *self_, PyObject *args, PyObject *kw) {
return (PyObject *)self;
}
-static int
-font_getchar(PyObject *string, int index, FT_ULong *char_out) {
- if (PyUnicode_Check(string)) {
- if (index >= PyUnicode_GET_LENGTH(string)) {
- return 0;
- }
- *char_out = PyUnicode_READ_CHAR(string, index);
- return 1;
- }
- return 0;
-}
-
#ifdef HAVE_RAQM
static size_t
@@ -254,7 +254,8 @@ text_layout_raqm(
const char *dir,
PyObject *features,
const char *lang,
- GlyphInfo **glyph_info) {
+ GlyphInfo **glyph_info
+) {
size_t i = 0, count = 0, start = 0;
raqm_t *rq;
raqm_glyph_t *glyphs = NULL;
@@ -266,28 +267,37 @@ text_layout_raqm(
goto failed;
}
+ Py_ssize_t size;
+ int set_text;
if (PyUnicode_Check(string)) {
Py_UCS4 *text = PyUnicode_AsUCS4Copy(string);
- Py_ssize_t size = PyUnicode_GET_LENGTH(string);
+ size = PyUnicode_GET_LENGTH(string);
if (!text || !size) {
/* return 0 and clean up, no glyphs==no size,
and raqm fails with empty strings */
goto failed;
}
- int set_text = raqm_set_text(rq, text, size);
+ set_text = raqm_set_text(rq, text, size);
PyMem_Free(text);
- if (!set_text) {
- PyErr_SetString(PyExc_ValueError, "raqm_set_text() failed");
+ } else if (PyBytes_Check(string)) {
+ char *buffer;
+ PyBytes_AsStringAndSize(string, &buffer, &size);
+ if (!buffer || !size) {
+ /* return 0 and clean up, no glyphs==no size,
+ and raqm fails with empty strings */
goto failed;
}
- if (lang) {
- if (!raqm_set_language(rq, lang, start, size)) {
- PyErr_SetString(PyExc_ValueError, "raqm_set_language() failed");
- goto failed;
- }
- }
+ set_text = raqm_set_text_utf8(rq, buffer, size);
} else {
- PyErr_SetString(PyExc_TypeError, "expected string");
+ PyErr_SetString(PyExc_TypeError, "expected string or bytes");
+ goto failed;
+ }
+ if (!set_text) {
+ PyErr_SetString(PyExc_ValueError, "raqm_set_text() failed");
+ goto failed;
+ }
+ if (lang && !raqm_set_language(rq, lang, start, size)) {
+ PyErr_SetString(PyExc_ValueError, "raqm_set_language() failed");
goto failed;
}
@@ -302,13 +312,14 @@ text_layout_raqm(
#if !defined(RAQM_VERSION_ATLEAST)
/* RAQM_VERSION_ATLEAST was added in Raqm 0.7.0 */
PyErr_SetString(
- PyExc_ValueError,
- "libraqm 0.7 or greater required for 'ttb' direction");
+ PyExc_ValueError, "libraqm 0.7 or greater required for 'ttb' direction"
+ );
goto failed;
#endif
} else {
PyErr_SetString(
- PyExc_ValueError, "direction must be either 'rtl', 'ltr' or 'ttb'");
+ PyExc_ValueError, "direction must be either 'rtl', 'ltr' or 'ttb'"
+ );
goto failed;
}
}
@@ -404,29 +415,31 @@ text_layout_fallback(
const char *lang,
GlyphInfo **glyph_info,
int mask,
- int color) {
- int error, load_flags;
+ int color
+) {
+ int error, load_flags, i;
+ char *buffer = NULL;
FT_ULong ch;
Py_ssize_t count;
FT_GlyphSlot glyph;
FT_Bool kerning = FT_HAS_KERNING(self->face);
FT_UInt last_index = 0;
- int i;
if (features != Py_None || dir != NULL || lang != NULL) {
PyErr_SetString(
PyExc_KeyError,
"setting text direction, language or font features is not supported "
- "without libraqm");
- }
- if (!PyUnicode_Check(string)) {
- PyErr_SetString(PyExc_TypeError, "expected string");
- return 0;
+ "without libraqm"
+ );
}
- count = 0;
- while (font_getchar(string, count, &ch)) {
- count++;
+ if (PyUnicode_Check(string)) {
+ count = PyUnicode_GET_LENGTH(string);
+ } else if (PyBytes_Check(string)) {
+ PyBytes_AsStringAndSize(string, &buffer, &count);
+ } else {
+ PyErr_SetString(PyExc_TypeError, "expected string or bytes");
+ return 0;
}
if (count == 0) {
return 0;
@@ -445,7 +458,12 @@ text_layout_fallback(
if (color) {
load_flags |= FT_LOAD_COLOR;
}
- for (i = 0; font_getchar(string, i, &ch); i++) {
+ for (i = 0; i < count; i++) {
+ if (buffer) {
+ ch = buffer[i];
+ } else {
+ ch = PyUnicode_READ_CHAR(string, i);
+ }
(*glyph_info)[i].index = FT_Get_Char_Index(self->face, ch);
error = FT_Load_Glyph(self->face, (*glyph_info)[i].index, load_flags);
if (error) {
@@ -462,7 +480,8 @@ text_layout_fallback(
last_index,
(*glyph_info)[i].index,
ft_kerning_default,
- &delta) == 0) {
+ &delta
+ ) == 0) {
(*glyph_info)[i - 1].x_advance += PIXEL(delta.x);
(*glyph_info)[i - 1].y_advance += PIXEL(delta.y);
}
@@ -486,7 +505,8 @@ text_layout(
const char *lang,
GlyphInfo **glyph_info,
int mask,
- int color) {
+ int color
+) {
size_t count;
#ifdef HAVE_RAQM
if (have_raqm && self->layout_engine == LAYOUT_RAQM) {
@@ -495,7 +515,8 @@ text_layout(
#endif
{
count = text_layout_fallback(
- string, self, dir, features, lang, glyph_info, mask, color);
+ string, self, dir, features, lang, glyph_info, mask, color
+ );
}
return count;
}
@@ -517,7 +538,8 @@ font_getlength(FontObject *self, PyObject *args) {
/* calculate size and bearing for a given string */
if (!PyArg_ParseTuple(
- args, "O|zzOz:getlength", &string, &mode, &dir, &features, &lang)) {
+ args, "O|zzOz:getlength", &string, &mode, &dir, &features, &lang
+ )) {
return NULL;
}
@@ -559,7 +581,8 @@ bounding_box_and_anchors(
int *width,
int *height,
int *x_offset,
- int *y_offset) {
+ int *y_offset
+) {
int position; /* pen position along primary axis, in 26.6 precision */
int advanced; /* pen position along primary axis, in pixels */
int px, py; /* position of current glyph, in pixels */
@@ -664,7 +687,8 @@ bounding_box_and_anchors(
case 'm': // middle (ascender + descender) / 2
y_anchor = PIXEL(
(face->size->metrics.ascender + face->size->metrics.descender) /
- 2);
+ 2
+ );
break;
case 's': // horizontal baseline
y_anchor = 0;
@@ -744,7 +768,8 @@ font_getsize(FontObject *self, PyObject *args) {
/* calculate size and bearing for a given string */
if (!PyArg_ParseTuple(
- args, "O|zzOzz:getsize", &string, &mode, &dir, &features, &lang, &anchor)) {
+ args, "O|zzOzz:getsize", &string, &mode, &dir, &features, &lang, &anchor
+ )) {
return NULL;
}
@@ -776,7 +801,8 @@ font_getsize(FontObject *self, PyObject *args) {
&width,
&height,
&x_offset,
- &y_offset);
+ &y_offset
+ );
if (glyph_info) {
PyMem_Free(glyph_info);
glyph_info = NULL;
@@ -811,10 +837,9 @@ font_render(FontObject *self, PyObject *args) {
unsigned char convert_scale; /* scale factor for non-8bpp bitmaps */
PyObject *image;
Imaging im;
- Py_ssize_t id;
int mask = 0; /* is FT_LOAD_TARGET_MONO enabled? */
int color = 0; /* is FT_LOAD_COLOR enabled? */
- int stroke_width = 0;
+ float stroke_width = 0;
PY_LONG_LONG foreground_ink_long = 0;
unsigned int foreground_ink;
const char *mode = NULL;
@@ -834,7 +859,7 @@ font_render(FontObject *self, PyObject *args) {
if (!PyArg_ParseTuple(
args,
- "OO|zzOzizLffO:render",
+ "OO|zzOzfzLffO:render",
&string,
&fill,
&mode,
@@ -845,7 +870,8 @@ font_render(FontObject *self, PyObject *args) {
&anchor,
&foreground_ink_long,
&x_start,
- &y_start)) {
+ &y_start
+ )) {
return NULL;
}
@@ -892,29 +918,26 @@ font_render(FontObject *self, PyObject *args) {
&width,
&height,
&x_offset,
- &y_offset);
+ &y_offset
+ );
if (error) {
PyMem_Del(glyph_info);
return NULL;
}
- width += stroke_width * 2 + ceil(x_start);
- height += stroke_width * 2 + ceil(y_start);
+ width += ceil(stroke_width * 2 + x_start);
+ height += ceil(stroke_width * 2 + y_start);
image = PyObject_CallFunction(fill, "ii", width, height);
- if (image == Py_None) {
- PyMem_Del(glyph_info);
- return Py_BuildValue("N(ii)", image, 0, 0);
- } else if (image == NULL) {
+ if (image == NULL) {
PyMem_Del(glyph_info);
return NULL;
}
- PyObject *imageId = PyObject_GetAttrString(image, "id");
- id = PyLong_AsSsize_t(imageId);
- Py_XDECREF(imageId);
- im = (Imaging)id;
+ PyObject *imagePtr = PyObject_GetAttrString(image, "ptr");
+ im = (Imaging)PyCapsule_GetPointer(imagePtr, IMAGING_MAGIC);
+ Py_XDECREF(imagePtr);
- x_offset -= stroke_width;
- y_offset -= stroke_width;
+ x_offset = round(x_offset - stroke_width);
+ y_offset = round(y_offset - stroke_width);
if (count == 0 || width == 0 || height == 0) {
PyMem_Del(glyph_info);
return Py_BuildValue("N(ii)", image, x_offset, y_offset);
@@ -929,10 +952,11 @@ font_render(FontObject *self, PyObject *args) {
FT_Stroker_Set(
stroker,
- (FT_Fixed)stroke_width * 64,
+ (FT_Fixed)round(stroke_width * 64),
FT_STROKER_LINECAP_ROUND,
FT_STROKER_LINEJOIN_ROUND,
- 0);
+ 0
+ );
}
/*
@@ -966,8 +990,8 @@ font_render(FontObject *self, PyObject *args) {
}
/* set pen position to text origin */
- x = (-x_min + stroke_width + x_start) * 64;
- y = (-y_max + (-stroke_width) - y_start) * 64;
+ x = round((-x_min + stroke_width + x_start) * 64);
+ y = round((-y_max + (-stroke_width) - y_start) * 64);
if (stroker == NULL) {
load_flags |= FT_LOAD_RENDER;
@@ -1107,8 +1131,8 @@ font_render(FontObject *self, PyObject *args) {
BLEND(src_alpha, target[k * 4 + 2], src_blue, tmp);
target[k * 4 + 3] = CLIP8(
src_alpha +
- MULDIV255(
- target[k * 4 + 3], (255 - src_alpha), tmp));
+ MULDIV255(target[k * 4 + 3], (255 - src_alpha), tmp)
+ );
} else {
/* paste unpremultiplied RGBA values */
target[k * 4 + 0] = src_red;
@@ -1126,15 +1150,20 @@ font_render(FontObject *self, PyObject *args) {
if (src_alpha > 0) {
if (target[k * 4 + 3] > 0) {
target[k * 4 + 0] = BLEND(
- src_alpha, target[k * 4 + 0], ink[0], tmp);
+ src_alpha, target[k * 4 + 0], ink[0], tmp
+ );
target[k * 4 + 1] = BLEND(
- src_alpha, target[k * 4 + 1], ink[1], tmp);
+ src_alpha, target[k * 4 + 1], ink[1], tmp
+ );
target[k * 4 + 2] = BLEND(
- src_alpha, target[k * 4 + 2], ink[2], tmp);
+ src_alpha, target[k * 4 + 2], ink[2], tmp
+ );
target[k * 4 + 3] = CLIP8(
src_alpha +
MULDIV255(
- target[k * 4 + 3], (255 - src_alpha), tmp));
+ target[k * 4 + 3], (255 - src_alpha), tmp
+ )
+ );
} else {
target[k * 4 + 0] = ink[0];
target[k * 4 + 1] = ink[1];
@@ -1152,7 +1181,9 @@ font_render(FontObject *self, PyObject *args) {
? CLIP8(
src_alpha +
MULDIV255(
- target[k], (255 - src_alpha), tmp))
+ target[k], (255 - src_alpha), tmp
+ )
+ )
: src_alpha;
}
}
@@ -1213,30 +1244,49 @@ font_getvarnames(FontObject *self) {
return NULL;
}
+ int *list_names_filled = PyMem_Malloc(num_namedstyles * sizeof(int));
+ if (list_names_filled == NULL) {
+ Py_DECREF(list_names);
+ FT_Done_MM_Var(library, master);
+ return PyErr_NoMemory();
+ }
+
+ for (unsigned int i = 0; i < num_namedstyles; i++) {
+ list_names_filled[i] = 0;
+ }
+
name_count = FT_Get_Sfnt_Name_Count(self->face);
for (i = 0; i < name_count; i++) {
error = FT_Get_Sfnt_Name(self->face, i, &name);
if (error) {
+ PyMem_Free(list_names_filled);
Py_DECREF(list_names);
FT_Done_MM_Var(library, master);
return geterror(error);
}
for (j = 0; j < num_namedstyles; j++) {
- if (PyList_GetItem(list_names, j) != NULL) {
+ if (list_names_filled[j]) {
continue;
}
if (master->namedstyle[j].strid == name.name_id) {
list_name = Py_BuildValue("y#", name.string, name.string_len);
+ if (list_name == NULL) {
+ PyMem_Free(list_names_filled);
+ Py_DECREF(list_names);
+ FT_Done_MM_Var(library, master);
+ return NULL;
+ }
PyList_SetItem(list_names, j, list_name);
+ list_names_filled[j] = 1;
break;
}
}
}
+ PyMem_Free(list_names_filled);
FT_Done_MM_Var(library, master);
-
return list_names;
}
@@ -1293,9 +1343,14 @@ font_getvaraxes(FontObject *self) {
if (name.name_id == axis.strid) {
axis_name = Py_BuildValue("y#", name.string, name.string_len);
- PyDict_SetItemString(
- list_axis, "name", axis_name ? axis_name : Py_None);
- Py_XDECREF(axis_name);
+ if (axis_name == NULL) {
+ Py_DECREF(list_axis);
+ Py_DECREF(list_axes);
+ FT_Done_MM_Var(library, master);
+ return NULL;
+ }
+ PyDict_SetItemString(list_axis, "name", axis_name);
+ Py_DECREF(axis_name);
break;
}
}
@@ -1349,7 +1404,12 @@ font_setvaraxes(FontObject *self, PyObject *args) {
return PyErr_NoMemory();
}
for (i = 0; i < num_coords; i++) {
- item = PyList_GET_ITEM(axes, i);
+ item = PyList_GetItemRef(axes, i);
+ if (item == NULL) {
+ free(coords);
+ return NULL;
+ }
+
if (PyFloat_Check(item)) {
coord = PyFloat_AS_DOUBLE(item);
} else if (PyLong_Check(item)) {
@@ -1357,10 +1417,12 @@ font_setvaraxes(FontObject *self, PyObject *args) {
} else if (PyNumber_Check(item)) {
coord = PyFloat_AsDouble(item);
} else {
+ Py_DECREF(item);
free(coords);
PyErr_SetString(PyExc_TypeError, "list must contain numbers");
return NULL;
}
+ Py_DECREF(item);
coords[i] = coord * 65536;
}
@@ -1378,7 +1440,9 @@ font_setvaraxes(FontObject *self, PyObject *args) {
static void
font_dealloc(FontObject *self) {
if (self->face) {
+ MUTEX_LOCK(&ft_library_mutex);
FT_Done_Face(self->face);
+ MUTEX_UNLOCK(&ft_library_mutex);
}
if (self->font_bytes) {
PyMem_Free(self->font_bytes);
@@ -1397,7 +1461,8 @@ static PyMethodDef font_methods[] = {
{"setvarname", (PyCFunction)font_setvarname, METH_VARARGS},
{"setvaraxes", (PyCFunction)font_setvaraxes, METH_VARARGS},
#endif
- {NULL, NULL}};
+ {NULL, NULL}
+};
static PyObject *
font_getattr_family(FontObject *self, void *closure) {
@@ -1454,7 +1519,8 @@ static struct PyGetSetDef font_getsetters[] = {
{"x_ppem", (getter)font_getattr_x_ppem},
{"y_ppem", (getter)font_getattr_y_ppem},
{"glyphs", (getter)font_getattr_glyphs},
- {NULL}};
+ {NULL}
+};
static PyTypeObject Font_Type = {
PyVarObject_HEAD_INIT(NULL, 0) "Font", /*tp_name*/
@@ -1490,7 +1556,8 @@ static PyTypeObject Font_Type = {
};
static PyMethodDef _functions[] = {
- {"getfont", (PyCFunction)getfont, METH_VARARGS | METH_KEYWORDS}, {NULL, NULL}};
+ {"getfont", (PyCFunction)getfont, METH_VARARGS | METH_KEYWORDS}, {NULL, NULL}
+};
static int
setup_module(PyObject *m) {
@@ -1580,5 +1647,9 @@ PyInit__imagingft(void) {
return NULL;
}
+#ifdef Py_GIL_DISABLED
+ PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
+#endif
+
return m;
}
diff --git a/src/_imagingmath.c b/src/_imagingmath.c
index 067c165b2..dbe636707 100644
--- a/src/_imagingmath.c
+++ b/src/_imagingmath.c
@@ -23,6 +23,9 @@
#define MAX_INT32 2147483647.0
#define MIN_INT32 -2147483648.0
+#define MATH_FUNC_UNOP_MAGIC "Pillow Math unary func"
+#define MATH_FUNC_BINOP_MAGIC "Pillow Math binary func"
+
#define UNOP(name, op, type) \
void name(Imaging out, Imaging im1) { \
int x, y; \
@@ -168,15 +171,24 @@ _unop(PyObject *self, PyObject *args) {
Imaging im1;
void (*unop)(Imaging, Imaging);
- Py_ssize_t op, i0, i1;
- if (!PyArg_ParseTuple(args, "nnn", &op, &i0, &i1)) {
+ PyObject *op, *i0, *i1;
+ if (!PyArg_ParseTuple(args, "OOO", &op, &i0, &i1)) {
return NULL;
}
- out = (Imaging)i0;
- im1 = (Imaging)i1;
+ if (!PyCapsule_IsValid(op, MATH_FUNC_UNOP_MAGIC)) {
+ PyErr_Format(PyExc_TypeError, "Expected '%s' Capsule", MATH_FUNC_UNOP_MAGIC);
+ return NULL;
+ }
+ if (!PyCapsule_IsValid(i0, IMAGING_MAGIC) ||
+ !PyCapsule_IsValid(i1, IMAGING_MAGIC)) {
+ PyErr_Format(PyExc_TypeError, "Expected '%s' Capsule", IMAGING_MAGIC);
+ return NULL;
+ }
- unop = (void *)op;
+ unop = (void *)PyCapsule_GetPointer(op, MATH_FUNC_UNOP_MAGIC);
+ out = (Imaging)PyCapsule_GetPointer(i0, IMAGING_MAGIC);
+ im1 = (Imaging)PyCapsule_GetPointer(i1, IMAGING_MAGIC);
unop(out, im1);
@@ -191,16 +203,26 @@ _binop(PyObject *self, PyObject *args) {
Imaging im2;
void (*binop)(Imaging, Imaging, Imaging);
- Py_ssize_t op, i0, i1, i2;
- if (!PyArg_ParseTuple(args, "nnnn", &op, &i0, &i1, &i2)) {
+ PyObject *op, *i0, *i1, *i2;
+ if (!PyArg_ParseTuple(args, "OOOO", &op, &i0, &i1, &i2)) {
return NULL;
}
- out = (Imaging)i0;
- im1 = (Imaging)i1;
- im2 = (Imaging)i2;
+ if (!PyCapsule_IsValid(op, MATH_FUNC_BINOP_MAGIC)) {
+ PyErr_Format(PyExc_TypeError, "Expected '%s' Capsule", MATH_FUNC_BINOP_MAGIC);
+ return NULL;
+ }
+ if (!PyCapsule_IsValid(i0, IMAGING_MAGIC) ||
+ !PyCapsule_IsValid(i1, IMAGING_MAGIC) ||
+ !PyCapsule_IsValid(i2, IMAGING_MAGIC)) {
+ PyErr_Format(PyExc_TypeError, "Expected '%s' Capsule", IMAGING_MAGIC);
+ return NULL;
+ }
- binop = (void *)op;
+ binop = (void *)PyCapsule_GetPointer(op, MATH_FUNC_BINOP_MAGIC);
+ out = (Imaging)PyCapsule_GetPointer(i0, IMAGING_MAGIC);
+ im1 = (Imaging)PyCapsule_GetPointer(i1, IMAGING_MAGIC);
+ im2 = (Imaging)PyCapsule_GetPointer(i2, IMAGING_MAGIC);
binop(out, im1, im2);
@@ -209,11 +231,21 @@ _binop(PyObject *self, PyObject *args) {
}
static PyMethodDef _functions[] = {
- {"unop", _unop, 1}, {"binop", _binop, 1}, {NULL, NULL}};
+ {"unop", _unop, 1}, {"binop", _binop, 1}, {NULL, NULL}
+};
static void
-install(PyObject *d, char *name, void *value) {
- PyObject *v = PyLong_FromSsize_t((Py_ssize_t)value);
+install_unary(PyObject *d, char *name, void *func) {
+ PyObject *v = PyCapsule_New(func, MATH_FUNC_UNOP_MAGIC, NULL);
+ if (!v || PyDict_SetItemString(d, name, v)) {
+ PyErr_Clear();
+ }
+ Py_XDECREF(v);
+}
+
+static void
+install_binary(PyObject *d, char *name, void *func) {
+ PyObject *v = PyCapsule_New(func, MATH_FUNC_BINOP_MAGIC, NULL);
if (!v || PyDict_SetItemString(d, name, v)) {
PyErr_Clear();
}
@@ -224,50 +256,50 @@ static int
setup_module(PyObject *m) {
PyObject *d = PyModule_GetDict(m);
- install(d, "abs_I", abs_I);
- install(d, "neg_I", neg_I);
- install(d, "add_I", add_I);
- install(d, "sub_I", sub_I);
- install(d, "diff_I", diff_I);
- install(d, "mul_I", mul_I);
- install(d, "div_I", div_I);
- install(d, "mod_I", mod_I);
- install(d, "min_I", min_I);
- install(d, "max_I", max_I);
- install(d, "pow_I", pow_I);
+ install_unary(d, "abs_I", abs_I);
+ install_unary(d, "neg_I", neg_I);
+ install_binary(d, "add_I", add_I);
+ install_binary(d, "sub_I", sub_I);
+ install_binary(d, "diff_I", diff_I);
+ install_binary(d, "mul_I", mul_I);
+ install_binary(d, "div_I", div_I);
+ install_binary(d, "mod_I", mod_I);
+ install_binary(d, "min_I", min_I);
+ install_binary(d, "max_I", max_I);
+ install_binary(d, "pow_I", pow_I);
- install(d, "invert_I", invert_I);
- install(d, "and_I", and_I);
- install(d, "or_I", or_I);
- install(d, "xor_I", xor_I);
- install(d, "lshift_I", lshift_I);
- install(d, "rshift_I", rshift_I);
+ install_unary(d, "invert_I", invert_I);
+ install_binary(d, "and_I", and_I);
+ install_binary(d, "or_I", or_I);
+ install_binary(d, "xor_I", xor_I);
+ install_binary(d, "lshift_I", lshift_I);
+ install_binary(d, "rshift_I", rshift_I);
- install(d, "eq_I", eq_I);
- install(d, "ne_I", ne_I);
- install(d, "lt_I", lt_I);
- install(d, "le_I", le_I);
- install(d, "gt_I", gt_I);
- install(d, "ge_I", ge_I);
+ install_binary(d, "eq_I", eq_I);
+ install_binary(d, "ne_I", ne_I);
+ install_binary(d, "lt_I", lt_I);
+ install_binary(d, "le_I", le_I);
+ install_binary(d, "gt_I", gt_I);
+ install_binary(d, "ge_I", ge_I);
- install(d, "abs_F", abs_F);
- install(d, "neg_F", neg_F);
- install(d, "add_F", add_F);
- install(d, "sub_F", sub_F);
- install(d, "diff_F", diff_F);
- install(d, "mul_F", mul_F);
- install(d, "div_F", div_F);
- install(d, "mod_F", mod_F);
- install(d, "min_F", min_F);
- install(d, "max_F", max_F);
- install(d, "pow_F", pow_F);
+ install_unary(d, "abs_F", abs_F);
+ install_unary(d, "neg_F", neg_F);
+ install_binary(d, "add_F", add_F);
+ install_binary(d, "sub_F", sub_F);
+ install_binary(d, "diff_F", diff_F);
+ install_binary(d, "mul_F", mul_F);
+ install_binary(d, "div_F", div_F);
+ install_binary(d, "mod_F", mod_F);
+ install_binary(d, "min_F", min_F);
+ install_binary(d, "max_F", max_F);
+ install_binary(d, "pow_F", pow_F);
- install(d, "eq_F", eq_F);
- install(d, "ne_F", ne_F);
- install(d, "lt_F", lt_F);
- install(d, "le_F", le_F);
- install(d, "gt_F", gt_F);
- install(d, "ge_F", ge_F);
+ install_binary(d, "eq_F", eq_F);
+ install_binary(d, "ne_F", ne_F);
+ install_binary(d, "lt_F", lt_F);
+ install_binary(d, "le_F", le_F);
+ install_binary(d, "gt_F", gt_F);
+ install_binary(d, "ge_F", ge_F);
return 0;
}
@@ -290,5 +322,9 @@ PyInit__imagingmath(void) {
return NULL;
}
+#ifdef Py_GIL_DISABLED
+ PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
+#endif
+
return m;
}
diff --git a/src/_imagingmorph.c b/src/_imagingmorph.c
index 8815c2b7e..b763e3a6f 100644
--- a/src/_imagingmorph.c
+++ b/src/_imagingmorph.c
@@ -11,7 +11,6 @@
* See the README file for information on usage and redistribution.
*/
-#include "Python.h"
#include "libImaging/Imaging.h"
#define LUT_SIZE (1 << 9)
@@ -30,43 +29,36 @@
static PyObject *
apply(PyObject *self, PyObject *args) {
const char *lut;
- PyObject *py_lut;
- Py_ssize_t lut_len, i0, i1;
+ Py_ssize_t lut_len;
+ PyObject *i0, *i1;
Imaging imgin, imgout;
int width, height;
int row_idx, col_idx;
UINT8 **inrows, **outrows;
int num_changed_pixels = 0;
- if (!PyArg_ParseTuple(args, "Onn", &py_lut, &i0, &i1)) {
- PyErr_SetString(PyExc_RuntimeError, "Argument parsing problem");
+ if (!PyArg_ParseTuple(args, "s#OO", &lut, &lut_len, &i0, &i1)) {
return NULL;
}
- if (!PyBytes_Check(py_lut)) {
- PyErr_SetString(PyExc_RuntimeError, "The morphology LUT is not a bytes object");
- return NULL;
- }
-
- lut_len = PyBytes_Size(py_lut);
-
if (lut_len < LUT_SIZE) {
PyErr_SetString(PyExc_RuntimeError, "The morphology LUT has the wrong size");
return NULL;
}
- lut = PyBytes_AsString(py_lut);
+ if (!PyCapsule_IsValid(i0, IMAGING_MAGIC) ||
+ !PyCapsule_IsValid(i1, IMAGING_MAGIC)) {
+ PyErr_Format(PyExc_TypeError, "Expected '%s' Capsule", IMAGING_MAGIC);
+ return NULL;
+ }
- imgin = (Imaging)i0;
- imgout = (Imaging)i1;
+ imgin = (Imaging)PyCapsule_GetPointer(i0, IMAGING_MAGIC);
+ imgout = (Imaging)PyCapsule_GetPointer(i1, IMAGING_MAGIC);
width = imgin->xsize;
height = imgin->ysize;
- if (imgin->type != IMAGING_TYPE_UINT8 || imgin->bands != 1) {
- PyErr_SetString(PyExc_RuntimeError, "Unsupported image type");
- return NULL;
- }
- if (imgout->type != IMAGING_TYPE_UINT8 || imgout->bands != 1) {
+ if (imgin->type != IMAGING_TYPE_UINT8 || imgin->bands != 1 ||
+ imgout->type != IMAGING_TYPE_UINT8 || imgout->bands != 1) {
PyErr_SetString(PyExc_RuntimeError, "Unsupported image type");
return NULL;
}
@@ -129,46 +121,39 @@ apply(PyObject *self, PyObject *args) {
static PyObject *
match(PyObject *self, PyObject *args) {
const char *lut;
- PyObject *py_lut;
- Py_ssize_t lut_len, i0;
+ Py_ssize_t lut_len;
+ PyObject *i0;
Imaging imgin;
int width, height;
int row_idx, col_idx;
UINT8 **inrows;
- PyObject *ret = PyList_New(0);
- if (ret == NULL) {
+
+ if (!PyArg_ParseTuple(args, "s#O", &lut, &lut_len, &i0)) {
return NULL;
}
- if (!PyArg_ParseTuple(args, "On", &py_lut, &i0)) {
- Py_DECREF(ret);
- PyErr_SetString(PyExc_RuntimeError, "Argument parsing problem");
- return NULL;
- }
-
- if (!PyBytes_Check(py_lut)) {
- Py_DECREF(ret);
- PyErr_SetString(PyExc_RuntimeError, "The morphology LUT is not a bytes object");
- return NULL;
- }
-
- lut_len = PyBytes_Size(py_lut);
-
if (lut_len < LUT_SIZE) {
- Py_DECREF(ret);
PyErr_SetString(PyExc_RuntimeError, "The morphology LUT has the wrong size");
return NULL;
}
- lut = PyBytes_AsString(py_lut);
- imgin = (Imaging)i0;
+ if (!PyCapsule_IsValid(i0, IMAGING_MAGIC)) {
+ PyErr_Format(PyExc_TypeError, "Expected '%s' Capsule", IMAGING_MAGIC);
+ return NULL;
+ }
+
+ imgin = (Imaging)PyCapsule_GetPointer(i0, IMAGING_MAGIC);
if (imgin->type != IMAGING_TYPE_UINT8 || imgin->bands != 1) {
- Py_DECREF(ret);
PyErr_SetString(PyExc_RuntimeError, "Unsupported image type");
return NULL;
}
+ PyObject *ret = PyList_New(0);
+ if (ret == NULL) {
+ return NULL;
+ }
+
inrows = imgin->image8;
width = imgin->xsize;
height = imgin->ysize;
@@ -215,26 +200,31 @@ match(PyObject *self, PyObject *args) {
*/
static PyObject *
get_on_pixels(PyObject *self, PyObject *args) {
- Py_ssize_t i0;
+ PyObject *i0;
Imaging img;
UINT8 **rows;
int row_idx, col_idx;
int width, height;
+
+ if (!PyArg_ParseTuple(args, "O", &i0)) {
+ return NULL;
+ }
+
+ if (!PyCapsule_IsValid(i0, IMAGING_MAGIC)) {
+ PyErr_Format(PyExc_TypeError, "Expected '%s' Capsule", IMAGING_MAGIC);
+ return NULL;
+ }
+
+ img = (Imaging)PyCapsule_GetPointer(i0, IMAGING_MAGIC);
+ rows = img->image8;
+ width = img->xsize;
+ height = img->ysize;
+
PyObject *ret = PyList_New(0);
if (ret == NULL) {
return NULL;
}
- if (!PyArg_ParseTuple(args, "n", &i0)) {
- Py_DECREF(ret);
- PyErr_SetString(PyExc_RuntimeError, "Argument parsing problem");
- return NULL;
- }
- img = (Imaging)i0;
- rows = img->image8;
- width = img->xsize;
- height = img->ysize;
-
for (row_idx = 0; row_idx < height; row_idx++) {
UINT8 *row = rows[row_idx];
for (col_idx = 0; col_idx < width; col_idx++) {
@@ -253,7 +243,8 @@ static PyMethodDef functions[] = {
{"apply", (PyCFunction)apply, METH_VARARGS, NULL},
{"get_on_pixels", (PyCFunction)get_on_pixels, METH_VARARGS, NULL},
{"match", (PyCFunction)match, METH_VARARGS, NULL},
- {NULL, NULL, 0, NULL}};
+ {NULL, NULL, 0, NULL}
+};
PyMODINIT_FUNC
PyInit__imagingmorph(void) {
@@ -269,5 +260,9 @@ PyInit__imagingmorph(void) {
m = PyModule_Create(&module_def);
+#ifdef Py_GIL_DISABLED
+ PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
+#endif
+
return m;
}
diff --git a/src/_imagingtk.c b/src/_imagingtk.c
index efa7fc1b6..c70d044bb 100644
--- a/src/_imagingtk.c
+++ b/src/_imagingtk.c
@@ -62,5 +62,10 @@ PyInit__imagingtk(void) {
Py_DECREF(m);
return NULL;
}
+
+#ifdef Py_GIL_DISABLED
+ PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
+#endif
+
return m;
}
diff --git a/src/_webp.c b/src/_webp.c
index 0a70e3357..dfda7048d 100644
--- a/src/_webp.c
+++ b/src/_webp.c
@@ -4,8 +4,6 @@
#include
#include
#include
-
-#ifdef HAVE_WEBPMUX
#include
#include
@@ -13,12 +11,10 @@
* Check the versions from mux.h and demux.h, to ensure the WebPAnimEncoder and
* WebPAnimDecoder APIs are present (initial support was added in 0.5.0). The
* very early versions had some significant differences, so we require later
- * versions, before enabling animation support.
+ * versions.
*/
-#if WEBP_MUX_ABI_VERSION >= 0x0104 && WEBP_DEMUX_ABI_VERSION >= 0x0105
-#define HAVE_WEBPANIM
-#endif
-
+#if WEBP_MUX_ABI_VERSION < 0x0106 || WEBP_DEMUX_ABI_VERSION < 0x0107
+#error libwebp 0.5.0 and above is required. Upgrade libwebp or build Pillow with --disable-webp flag
#endif
void
@@ -35,14 +31,13 @@ ImagingSectionLeave(ImagingSectionCookie *cookie) {
/* WebP Muxer Error Handling */
/* -------------------------------------------------------------------- */
-#ifdef HAVE_WEBPMUX
-
static const char *const kErrorMessages[-WEBP_MUX_NOT_ENOUGH_DATA + 1] = {
"WEBP_MUX_NOT_FOUND",
"WEBP_MUX_INVALID_ARGUMENT",
"WEBP_MUX_BAD_DATA",
"WEBP_MUX_MEMORY_ERROR",
- "WEBP_MUX_NOT_ENOUGH_DATA"};
+ "WEBP_MUX_NOT_ENOUGH_DATA"
+};
PyObject *
HandleMuxError(WebPMuxError err, char *chunk) {
@@ -61,7 +56,8 @@ HandleMuxError(WebPMuxError err, char *chunk) {
sprintf(message, "could not assemble chunks: %s", kErrorMessages[-err]);
} else {
message_len = sprintf(
- message, "could not set %.4s chunk: %s", chunk, kErrorMessages[-err]);
+ message, "could not set %.4s chunk: %s", chunk, kErrorMessages[-err]
+ );
}
if (message_len < 0) {
PyErr_SetString(PyExc_RuntimeError, "failed to construct error message");
@@ -87,14 +83,53 @@ HandleMuxError(WebPMuxError err, char *chunk) {
return NULL;
}
-#endif
+/* -------------------------------------------------------------------- */
+/* Frame import */
+/* -------------------------------------------------------------------- */
+
+static int
+import_frame_libwebp(WebPPicture *frame, Imaging im) {
+ if (strcmp(im->mode, "RGBA") && strcmp(im->mode, "RGB") &&
+ strcmp(im->mode, "RGBX")) {
+ PyErr_SetString(PyExc_ValueError, "unsupported image mode");
+ return -1;
+ }
+
+ frame->width = im->xsize;
+ frame->height = im->ysize;
+ frame->use_argb = 1; // Don't convert RGB pixels to YUV
+
+ if (!WebPPictureAlloc(frame)) {
+ PyErr_SetString(PyExc_MemoryError, "can't allocate picture frame");
+ return -2;
+ }
+
+ int ignore_fourth_channel = strcmp(im->mode, "RGBA");
+ for (int y = 0; y < im->ysize; ++y) {
+ UINT8 *src = (UINT8 *)im->image32[y];
+ UINT32 *dst = frame->argb + frame->argb_stride * y;
+ if (ignore_fourth_channel) {
+ for (int x = 0; x < im->xsize; ++x) {
+ dst[x] =
+ ((UINT32)(src[x * 4 + 2]) | ((UINT32)(src[x * 4 + 1]) << 8) |
+ ((UINT32)(src[x * 4]) << 16) | (0xff << 24));
+ }
+ } else {
+ for (int x = 0; x < im->xsize; ++x) {
+ dst[x] =
+ ((UINT32)(src[x * 4 + 2]) | ((UINT32)(src[x * 4 + 1]) << 8) |
+ ((UINT32)(src[x * 4]) << 16) | ((UINT32)(src[x * 4 + 3]) << 24));
+ }
+ }
+ }
+
+ return 0;
+}
/* -------------------------------------------------------------------- */
/* WebP Animation Support */
/* -------------------------------------------------------------------- */
-#ifdef HAVE_WEBPANIM
-
// Encoder type
typedef struct {
PyObject_HEAD WebPAnimEncoder *enc;
@@ -138,7 +173,8 @@ _anim_encoder_new(PyObject *self, PyObject *args) {
&kmin,
&kmax,
&allow_mixed,
- &verbose)) {
+ &verbose
+ )) {
return NULL;
}
@@ -187,16 +223,14 @@ _anim_encoder_dealloc(PyObject *self) {
PyObject *
_anim_encoder_add(PyObject *self, PyObject *args) {
- uint8_t *rgb;
- Py_ssize_t size;
+ PyObject *i0;
+ Imaging im;
int timestamp;
- int width;
- int height;
- char *mode;
int lossless;
float quality_factor;
float alpha_quality_factor;
int method;
+ ImagingSectionCookie cookie;
WebPConfig config;
WebPAnimEncoderObject *encp = (WebPAnimEncoderObject *)self;
WebPAnimEncoder *enc = encp->enc;
@@ -204,26 +238,30 @@ _anim_encoder_add(PyObject *self, PyObject *args) {
if (!PyArg_ParseTuple(
args,
- "z#iiisiffi",
- (char **)&rgb,
- &size,
+ "Oiiffi",
+ &i0,
×tamp,
- &width,
- &height,
- &mode,
&lossless,
&quality_factor,
&alpha_quality_factor,
- &method)) {
+ &method
+ )) {
return NULL;
}
// Check for NULL frame, which sets duration of final frame
- if (!rgb) {
+ if (i0 == Py_None) {
WebPAnimEncoderAdd(enc, NULL, timestamp, NULL);
Py_RETURN_NONE;
}
+ if (!PyCapsule_IsValid(i0, IMAGING_MAGIC)) {
+ PyErr_Format(PyExc_TypeError, "Expected '%s' Capsule", IMAGING_MAGIC);
+ return NULL;
+ }
+
+ im = (Imaging)PyCapsule_GetPointer(i0, IMAGING_MAGIC);
+
// Setup config for this frame
if (!WebPConfigInit(&config)) {
PyErr_SetString(PyExc_RuntimeError, "failed to initialize config!");
@@ -240,20 +278,15 @@ _anim_encoder_add(PyObject *self, PyObject *args) {
return NULL;
}
- // Populate the frame with raw bytes passed to us
- frame->width = width;
- frame->height = height;
- frame->use_argb = 1; // Don't convert RGB pixels to YUV
- if (strcmp(mode, "RGBA") == 0) {
- WebPPictureImportRGBA(frame, rgb, 4 * width);
- } else if (strcmp(mode, "RGBX") == 0) {
- WebPPictureImportRGBX(frame, rgb, 4 * width);
- } else {
- WebPPictureImportRGB(frame, rgb, 3 * width);
+ if (import_frame_libwebp(frame, im)) {
+ return NULL;
}
- // Add the frame to the encoder
- if (!WebPAnimEncoderAdd(enc, frame, timestamp, &config)) {
+ ImagingSectionEnter(&cookie);
+ int ok = WebPAnimEncoderAdd(enc, frame, timestamp, &config);
+ ImagingSectionLeave(&cookie);
+
+ if (!ok) {
PyErr_SetString(PyExc_RuntimeError, WebPAnimEncoderGetError(enc));
return NULL;
}
@@ -283,7 +316,8 @@ _anim_encoder_assemble(PyObject *self, PyObject *args) {
&exif_bytes,
&exif_size,
&xmp_bytes,
- &xmp_size)) {
+ &xmp_size
+ )) {
return NULL;
}
@@ -421,7 +455,8 @@ _anim_decoder_get_info(PyObject *self) {
info->loop_count,
info->bgcolor,
info->frame_count,
- decp->mode);
+ decp->mode
+ );
}
PyObject *
@@ -466,7 +501,8 @@ _anim_decoder_get_next(PyObject *self) {
}
bytes = PyBytes_FromStringAndSize(
- (char *)buf, decp->info.canvas_width * 4 * decp->info.canvas_height);
+ (char *)buf, decp->info.canvas_width * 4 * decp->info.canvas_height
+ );
ret = Py_BuildValue("Si", bytes, timestamp);
@@ -569,34 +605,27 @@ static PyTypeObject WebPAnimDecoder_Type = {
0, /*tp_getset*/
};
-#endif
-
/* -------------------------------------------------------------------- */
/* Legacy WebP Support */
/* -------------------------------------------------------------------- */
PyObject *
WebPEncode_wrapper(PyObject *self, PyObject *args) {
- int width;
- int height;
int lossless;
float quality_factor;
float alpha_quality_factor;
int method;
int exact;
- uint8_t *rgb;
+ Imaging im;
+ PyObject *i0;
uint8_t *icc_bytes;
uint8_t *exif_bytes;
uint8_t *xmp_bytes;
uint8_t *output;
- char *mode;
- Py_ssize_t size;
Py_ssize_t icc_size;
Py_ssize_t exif_size;
Py_ssize_t xmp_size;
size_t ret_size;
- int rgba_mode;
- int channels;
int ok;
ImagingSectionCookie cookie;
WebPConfig config;
@@ -605,15 +634,11 @@ WebPEncode_wrapper(PyObject *self, PyObject *args) {
if (!PyArg_ParseTuple(
args,
- "y#iiiffss#iis#s#",
- (char **)&rgb,
- &size,
- &width,
- &height,
+ "Oiffs#iis#s#",
+ &i0,
&lossless,
&quality_factor,
&alpha_quality_factor,
- &mode,
&icc_bytes,
&icc_size,
&method,
@@ -621,19 +646,17 @@ WebPEncode_wrapper(PyObject *self, PyObject *args) {
&exif_bytes,
&exif_size,
&xmp_bytes,
- &xmp_size)) {
+ &xmp_size
+ )) {
return NULL;
}
- rgba_mode = strcmp(mode, "RGBA") == 0;
- if (!rgba_mode && strcmp(mode, "RGB") != 0) {
- Py_RETURN_NONE;
+ if (!PyCapsule_IsValid(i0, IMAGING_MAGIC)) {
+ PyErr_Format(PyExc_TypeError, "Expected '%s' Capsule", IMAGING_MAGIC);
+ return NULL;
}
- channels = rgba_mode ? 4 : 3;
- if (size < width * height * channels) {
- Py_RETURN_NONE;
- }
+ im = (Imaging)PyCapsule_GetPointer(i0, IMAGING_MAGIC);
// Setup config for this frame
if (!WebPConfigInit(&config)) {
@@ -644,10 +667,7 @@ WebPEncode_wrapper(PyObject *self, PyObject *args) {
config.quality = quality_factor;
config.alpha_quality = alpha_quality_factor;
config.method = method;
-#if WEBP_ENCODER_ABI_VERSION >= 0x0209
- // the "exact" flag is only available in libwebp 0.5.0 and later
config.exact = exact;
-#endif
// Validate the config
if (!WebPValidateConfig(&config)) {
@@ -659,14 +679,9 @@ WebPEncode_wrapper(PyObject *self, PyObject *args) {
PyErr_SetString(PyExc_ValueError, "could not initialise picture");
return NULL;
}
- pic.width = width;
- pic.height = height;
- pic.use_argb = 1; // Don't convert RGB pixels to YUV
- if (rgba_mode) {
- WebPPictureImportRGBA(&pic, rgb, channels * width);
- } else {
- WebPPictureImportRGB(&pic, rgb, channels * width);
+ if (import_frame_libwebp(&pic, im)) {
+ return NULL;
}
WebPMemoryWriterInit(&writer);
@@ -679,19 +694,21 @@ WebPEncode_wrapper(PyObject *self, PyObject *args) {
WebPPictureFree(&pic);
if (!ok) {
- PyErr_Format(PyExc_ValueError, "encoding error %d", (&pic)->error_code);
+ int error_code = (&pic)->error_code;
+ char message[50] = "";
+ if (error_code == VP8_ENC_ERROR_BAD_DIMENSION) {
+ sprintf(
+ message,
+ ": Image size exceeds WebP limit of %d pixels",
+ WEBP_MAX_DIMENSION
+ );
+ }
+ PyErr_Format(PyExc_ValueError, "encoding error %d%s", error_code, message);
return NULL;
}
output = writer.mem;
ret_size = writer.size;
-#ifndef HAVE_WEBPMUX
- if (ret_size > 0) {
- PyObject *ret = PyBytes_FromStringAndSize((char *)output, ret_size);
- free(output);
- return ret;
- }
-#else
{
/* I want to truncate the *_size items that get passed into WebP
data. Pypy2.1.0 had some issues where the Py_ssize_t items had
@@ -767,127 +784,9 @@ WebPEncode_wrapper(PyObject *self, PyObject *args) {
return ret;
}
}
-#endif
Py_RETURN_NONE;
}
-PyObject *
-WebPDecode_wrapper(PyObject *self, PyObject *args) {
- PyBytesObject *webp_string;
- const uint8_t *webp;
- Py_ssize_t size;
- PyObject *ret = Py_None, *bytes = NULL, *pymode = NULL, *icc_profile = NULL,
- *exif = NULL;
- WebPDecoderConfig config;
- VP8StatusCode vp8_status_code = VP8_STATUS_OK;
- char *mode = "RGB";
-
- if (!PyArg_ParseTuple(args, "S", &webp_string)) {
- return NULL;
- }
-
- if (!WebPInitDecoderConfig(&config)) {
- Py_RETURN_NONE;
- }
-
- PyBytes_AsStringAndSize((PyObject *)webp_string, (char **)&webp, &size);
-
- vp8_status_code = WebPGetFeatures(webp, size, &config.input);
- if (vp8_status_code == VP8_STATUS_OK) {
- // If we don't set it, we don't get alpha.
- // Initialized to MODE_RGB
- if (config.input.has_alpha) {
- config.output.colorspace = MODE_RGBA;
- mode = "RGBA";
- }
-
-#ifndef HAVE_WEBPMUX
- vp8_status_code = WebPDecode(webp, size, &config);
-#else
- {
- int copy_data = 0;
- WebPData data = {webp, size};
- WebPMuxFrameInfo image;
- WebPData icc_profile_data = {0};
- WebPData exif_data = {0};
-
- WebPMux *mux = WebPMuxCreate(&data, copy_data);
- if (NULL == mux) {
- goto end;
- }
-
- if (WEBP_MUX_OK != WebPMuxGetFrame(mux, 1, &image)) {
- WebPMuxDelete(mux);
- goto end;
- }
-
- webp = image.bitstream.bytes;
- size = image.bitstream.size;
-
- vp8_status_code = WebPDecode(webp, size, &config);
-
- if (WEBP_MUX_OK == WebPMuxGetChunk(mux, "ICCP", &icc_profile_data)) {
- icc_profile = PyBytes_FromStringAndSize(
- (const char *)icc_profile_data.bytes, icc_profile_data.size);
- }
-
- if (WEBP_MUX_OK == WebPMuxGetChunk(mux, "EXIF", &exif_data)) {
- exif = PyBytes_FromStringAndSize(
- (const char *)exif_data.bytes, exif_data.size);
- }
-
- WebPDataClear(&image.bitstream);
- WebPMuxDelete(mux);
- }
-#endif
- }
-
- if (vp8_status_code != VP8_STATUS_OK) {
- goto end;
- }
-
- if (config.output.colorspace < MODE_YUV) {
- bytes = PyBytes_FromStringAndSize(
- (char *)config.output.u.RGBA.rgba, config.output.u.RGBA.size);
- } else {
- // Skipping YUV for now. Need Test Images.
- // UNDONE -- unclear if we'll ever get here if we set mode_rgb*
- bytes = PyBytes_FromStringAndSize(
- (char *)config.output.u.YUVA.y, config.output.u.YUVA.y_size);
- }
-
- pymode = PyUnicode_FromString(mode);
- ret = Py_BuildValue(
- "SiiSSS",
- bytes,
- config.output.width,
- config.output.height,
- pymode,
- NULL == icc_profile ? Py_None : icc_profile,
- NULL == exif ? Py_None : exif);
-
-end:
- WebPFreeDecBuffer(&config.output);
-
- Py_XDECREF(bytes);
- Py_XDECREF(pymode);
- Py_XDECREF(icc_profile);
- Py_XDECREF(exif);
-
- if (Py_None == ret) {
- Py_RETURN_NONE;
- }
-
- return ret;
-}
-
-// Return the decoder's version number, packed in hexadecimal using 8bits for
-// each of major/minor/revision. E.g: v2.5.7 is 0x020507.
-PyObject *
-WebPDecoderVersion_wrapper() {
- return Py_BuildValue("i", WebPGetDecoderVersion());
-}
-
// Version as string
const char *
WebPDecoderVersion_str(void) {
@@ -898,88 +797,31 @@ WebPDecoderVersion_str(void) {
"%d.%d.%d",
version_number >> 16,
(version_number >> 8) % 0x100,
- version_number % 0x100);
+ version_number % 0x100
+ );
return version;
}
-/*
- * The version of webp that ships with (0.1.3) Ubuntu 12.04 doesn't handle alpha well.
- * Files that are valid with 0.3 are reported as being invalid.
- */
-int
-WebPDecoderBuggyAlpha(void) {
- return WebPGetDecoderVersion() == 0x0103;
-}
-
-PyObject *
-WebPDecoderBuggyAlpha_wrapper() {
- return Py_BuildValue("i", WebPDecoderBuggyAlpha());
-}
-
/* -------------------------------------------------------------------- */
/* Module Setup */
/* -------------------------------------------------------------------- */
static PyMethodDef webpMethods[] = {
-#ifdef HAVE_WEBPANIM
{"WebPAnimDecoder", _anim_decoder_new, METH_VARARGS, "WebPAnimDecoder"},
{"WebPAnimEncoder", _anim_encoder_new, METH_VARARGS, "WebPAnimEncoder"},
-#endif
{"WebPEncode", WebPEncode_wrapper, METH_VARARGS, "WebPEncode"},
- {"WebPDecode", WebPDecode_wrapper, METH_VARARGS, "WebPDecode"},
- {"WebPDecoderVersion", WebPDecoderVersion_wrapper, METH_NOARGS, "WebPVersion"},
- {"WebPDecoderBuggyAlpha",
- WebPDecoderBuggyAlpha_wrapper,
- METH_NOARGS,
- "WebPDecoderBuggyAlpha"},
- {NULL, NULL}};
-
-void
-addMuxFlagToModule(PyObject *m) {
- PyObject *have_webpmux;
-#ifdef HAVE_WEBPMUX
- have_webpmux = Py_True;
-#else
- have_webpmux = Py_False;
-#endif
- Py_INCREF(have_webpmux);
- PyModule_AddObject(m, "HAVE_WEBPMUX", have_webpmux);
-}
-
-void
-addAnimFlagToModule(PyObject *m) {
- PyObject *have_webpanim;
-#ifdef HAVE_WEBPANIM
- have_webpanim = Py_True;
-#else
- have_webpanim = Py_False;
-#endif
- Py_INCREF(have_webpanim);
- PyModule_AddObject(m, "HAVE_WEBPANIM", have_webpanim);
-}
-
-void
-addTransparencyFlagToModule(PyObject *m) {
- PyObject *have_transparency = PyBool_FromLong(!WebPDecoderBuggyAlpha());
- if (PyModule_AddObject(m, "HAVE_TRANSPARENCY", have_transparency)) {
- Py_DECREF(have_transparency);
- }
-}
+ {NULL, NULL}
+};
static int
setup_module(PyObject *m) {
-#ifdef HAVE_WEBPANIM
/* Ready object types */
if (PyType_Ready(&WebPAnimDecoder_Type) < 0 ||
PyType_Ready(&WebPAnimEncoder_Type) < 0) {
return -1;
}
-#endif
- PyObject *d = PyModule_GetDict(m);
- addMuxFlagToModule(m);
- addAnimFlagToModule(m);
- addTransparencyFlagToModule(m);
+ PyObject *d = PyModule_GetDict(m);
PyObject *v = PyUnicode_FromString(WebPDecoderVersion_str());
PyDict_SetItemString(d, "webpdecoder_version", v ? v : Py_None);
Py_XDECREF(v);
@@ -1005,5 +847,9 @@ PyInit__webp(void) {
return NULL;
}
+#ifdef Py_GIL_DISABLED
+ PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
+#endif
+
return m;
}
diff --git a/src/decode.c b/src/decode.c
index ea2f3af80..51d0aced2 100644
--- a/src/decode.c
+++ b/src/decode.c
@@ -46,7 +46,8 @@
typedef struct {
PyObject_HEAD int (*decode)(
- Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes);
+ Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes
+ );
int (*cleanup)(ImagingCodecState state);
struct ImagingCodecStateInstance state;
Imaging im;
@@ -889,7 +890,8 @@ PyImaging_Jpeg2KDecoderNew(PyObject *self, PyObject *args) {
PY_LONG_LONG length = -1;
if (!PyArg_ParseTuple(
- args, "ss|iiiL", &mode, &format, &reduce, &layers, &fd, &length)) {
+ args, "ss|iiiL", &mode, &format, &reduce, &layers, &fd, &length
+ )) {
return NULL;
}
diff --git a/src/display.c b/src/display.c
index abf94f1e1..eed75975d 100644
--- a/src/display.c
+++ b/src/display.c
@@ -105,7 +105,8 @@ _draw(ImagingDisplayObject *display, PyObject *args) {
src + 0,
src + 1,
src + 2,
- src + 3)) {
+ src + 3
+ )) {
return NULL;
}
@@ -221,7 +222,8 @@ _tobytes(ImagingDisplayObject *display, PyObject *args) {
}
return PyBytes_FromStringAndSize(
- display->dib->bits, display->dib->ysize * display->dib->linesize);
+ display->dib->bits, display->dib->ysize * display->dib->linesize
+ );
}
static struct PyMethodDef methods[] = {
@@ -247,7 +249,8 @@ _getattr_size(ImagingDisplayObject *self, void *closure) {
}
static struct PyGetSetDef getsetters[] = {
- {"mode", (getter)_getattr_mode}, {"size", (getter)_getattr_size}, {NULL}};
+ {"mode", (getter)_getattr_mode}, {"size", (getter)_getattr_size}, {NULL}
+};
static PyTypeObject ImagingDisplayType = {
PyVarObject_HEAD_INIT(NULL, 0) "ImagingDisplay", /*tp_name*/
@@ -341,9 +344,8 @@ PyImaging_GrabScreenWin32(PyObject *self, PyObject *args) {
// added in Windows 10 (1607)
// loaded dynamically to avoid link errors
user32 = LoadLibraryA("User32.dll");
- SetThreadDpiAwarenessContext_function =
- (Func_SetThreadDpiAwarenessContext)GetProcAddress(
- user32, "SetThreadDpiAwarenessContext");
+ SetThreadDpiAwarenessContext_function = (Func_SetThreadDpiAwarenessContext
+ )GetProcAddress(user32, "SetThreadDpiAwarenessContext");
if (SetThreadDpiAwarenessContext_function != NULL) {
// DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE = ((DPI_CONTEXT_HANDLE)-3)
dpiAwareness = SetThreadDpiAwarenessContext_function((HANDLE)-3);
@@ -403,7 +405,8 @@ PyImaging_GrabScreenWin32(PyObject *self, PyObject *args) {
height,
PyBytes_AS_STRING(buffer),
(BITMAPINFO *)&core,
- DIB_RGB_COLORS)) {
+ DIB_RGB_COLORS
+ )) {
goto error;
}
@@ -547,7 +550,8 @@ windowCallback(HWND wnd, UINT message, WPARAM wParam, LPARAM lParam) {
ps.rcPaint.left,
ps.rcPaint.top,
ps.rcPaint.right,
- ps.rcPaint.bottom);
+ ps.rcPaint.bottom
+ );
if (result) {
Py_DECREF(result);
} else {
@@ -562,7 +566,8 @@ windowCallback(HWND wnd, UINT message, WPARAM wParam, LPARAM lParam) {
0,
0,
rect.right - rect.left,
- rect.bottom - rect.top);
+ rect.bottom - rect.top
+ );
if (result) {
Py_DECREF(result);
} else {
@@ -577,7 +582,8 @@ windowCallback(HWND wnd, UINT message, WPARAM wParam, LPARAM lParam) {
0,
0,
rect.right - rect.left,
- rect.bottom - rect.top);
+ rect.bottom - rect.top
+ );
if (result) {
Py_DECREF(result);
} else {
@@ -591,7 +597,8 @@ windowCallback(HWND wnd, UINT message, WPARAM wParam, LPARAM lParam) {
case WM_SIZE:
/* resize window */
result = PyObject_CallFunction(
- callback, "sii", "resize", LOWORD(lParam), HIWORD(lParam));
+ callback, "sii", "resize", LOWORD(lParam), HIWORD(lParam)
+ );
if (result) {
InvalidateRect(wnd, NULL, 1);
Py_DECREF(result);
@@ -618,7 +625,7 @@ windowCallback(HWND wnd, UINT message, WPARAM wParam, LPARAM lParam) {
if (callback) {
/* restore thread state */
PyEval_SaveThread();
- PyThreadState_Swap(threadstate);
+ PyThreadState_Swap(current_threadstate);
}
return status;
@@ -670,7 +677,8 @@ PyImaging_CreateWindowWin32(PyObject *self, PyObject *args) {
HWND_DESKTOP,
NULL,
NULL,
- NULL);
+ NULL
+ );
if (!wnd) {
PyErr_SetString(PyExc_OSError, "failed to create window");
@@ -682,24 +690,26 @@ PyImaging_CreateWindowWin32(PyObject *self, PyObject *args) {
SetWindowLongPtr(wnd, 0, (LONG_PTR)callback);
SetWindowLongPtr(wnd, sizeof(callback), (LONG_PTR)PyThreadState_Get());
- Py_BEGIN_ALLOW_THREADS ShowWindow(wnd, SW_SHOWNORMAL);
+ Py_BEGIN_ALLOW_THREADS;
+ ShowWindow(wnd, SW_SHOWNORMAL);
SetForegroundWindow(wnd); /* to make sure it's visible */
- Py_END_ALLOW_THREADS
+ Py_END_ALLOW_THREADS;
- return Py_BuildValue(F_HANDLE, wnd);
+ return Py_BuildValue(F_HANDLE, wnd);
}
PyObject *
PyImaging_EventLoopWin32(PyObject *self, PyObject *args) {
MSG msg;
- Py_BEGIN_ALLOW_THREADS while (mainloop && GetMessage(&msg, NULL, 0, 0)) {
+ Py_BEGIN_ALLOW_THREADS;
+ while (mainloop && GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
- Py_END_ALLOW_THREADS
+ Py_END_ALLOW_THREADS;
- Py_INCREF(Py_None);
+ Py_INCREF(Py_None);
return Py_None;
}
@@ -732,7 +742,8 @@ PyImaging_DrawWmf(PyObject *self, PyObject *args) {
&x0,
&x1,
&y0,
- &y1)) {
+ &y1
+ )) {
return NULL;
}
@@ -844,7 +855,8 @@ PyImaging_GrabScreenX11(PyObject *self, PyObject *args) {
PyErr_Format(
PyExc_OSError,
"X connection failed: error %i",
- xcb_connection_has_error(connection));
+ xcb_connection_has_error(connection)
+ );
xcb_disconnect(connection);
return NULL;
}
@@ -878,8 +890,10 @@ PyImaging_GrabScreenX11(PyObject *self, PyObject *args) {
0,
width,
height,
- 0x00ffffff),
- &error);
+ 0x00ffffff
+ ),
+ &error
+ );
if (reply == NULL) {
PyErr_Format(
PyExc_OSError,
@@ -887,7 +901,8 @@ PyImaging_GrabScreenX11(PyObject *self, PyObject *args) {
error->error_code,
error->major_code,
error->minor_code,
- error->resource_id);
+ error->resource_id
+ );
free(error);
xcb_disconnect(connection);
return NULL;
@@ -897,7 +912,8 @@ PyImaging_GrabScreenX11(PyObject *self, PyObject *args) {
if (reply->depth == 24) {
buffer = PyBytes_FromStringAndSize(
- (char *)xcb_get_image_data(reply), xcb_get_image_data_length(reply));
+ (char *)xcb_get_image_data(reply), xcb_get_image_data_length(reply)
+ );
} else {
PyErr_Format(PyExc_OSError, "unsupported bit depth: %i", reply->depth);
}
diff --git a/src/encode.c b/src/encode.c
index 442b5d04f..d369a1b45 100644
--- a/src/encode.c
+++ b/src/encode.c
@@ -25,6 +25,7 @@
#define PY_SSIZE_T_CLEAN
#include "Python.h"
+#include "thirdparty/pythoncapi_compat.h"
#include "libImaging/Imaging.h"
#include "libImaging/Gif.h"
@@ -38,7 +39,8 @@
typedef struct {
PyObject_HEAD int (*encode)(
- Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes);
+ Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes
+ );
int (*cleanup)(ImagingCodecState state);
struct ImagingCodecStateInstance state;
Imaging im;
@@ -134,7 +136,8 @@ _encode(ImagingEncoderObject *encoder, PyObject *args) {
}
status = encoder->encode(
- encoder->im, &encoder->state, (UINT8 *)PyBytes_AsString(buf), bufsize);
+ encoder->im, &encoder->state, (UINT8 *)PyBytes_AsString(buf), bufsize
+ );
/* adjust string length to avoid slicing in encoder */
if (_PyBytes_Resize(&buf, (status > 0) ? status : 0) < 0) {
@@ -571,7 +574,8 @@ PyImaging_ZipEncoderNew(PyObject *self, PyObject *args) {
&compress_level,
&compress_type,
&dictionary,
- &dictionary_size)) {
+ &dictionary_size
+ )) {
return NULL;
}
@@ -652,15 +656,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
PyObject *item;
if (!PyArg_ParseTuple(
- args,
- "sssnsOO",
- &mode,
- &rawmode,
- &compname,
- &fp,
- &filename,
- &tags,
- &types)) {
+ args, "sssnsOO", &mode, &rawmode, &compname, &fp, &filename, &tags, &types
+ )) {
return NULL;
}
@@ -671,11 +668,17 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
tags_size = PyList_Size(tags);
TRACE(("tags size: %d\n", (int)tags_size));
for (pos = 0; pos < tags_size; pos++) {
- item = PyList_GetItem(tags, pos);
+ item = PyList_GetItemRef(tags, pos);
+ if (item == NULL) {
+ return NULL;
+ }
+
if (!PyTuple_Check(item) || PyTuple_Size(item) != 2) {
+ Py_DECREF(item);
PyErr_SetString(PyExc_ValueError, "Invalid tags list");
return NULL;
}
+ Py_DECREF(item);
}
pos = 0;
}
@@ -703,11 +706,17 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
num_core_tags = sizeof(core_tags) / sizeof(int);
for (pos = 0; pos < tags_size; pos++) {
- item = PyList_GetItem(tags, pos);
+ item = PyList_GetItemRef(tags, pos);
+ if (item == NULL) {
+ return NULL;
+ }
+
// We already checked that tags is a 2-tuple list.
- key = PyTuple_GetItem(item, 0);
+ key = PyTuple_GET_ITEM(item, 0);
key_int = (int)PyLong_AsLong(key);
- value = PyTuple_GetItem(item, 1);
+ value = PyTuple_GET_ITEM(item, 1);
+ Py_DECREF(item);
+
status = 0;
is_core_tag = 0;
is_var_length = 0;
@@ -721,10 +730,13 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
}
if (!is_core_tag) {
- PyObject *tag_type = PyDict_GetItem(types, key);
+ PyObject *tag_type;
+ if (PyDict_GetItemRef(types, key, &tag_type) < 0) {
+ return NULL; // Exception has been already set
+ }
if (tag_type) {
int type_int = PyLong_AsLong(tag_type);
- if (type_int >= TIFF_BYTE && type_int <= TIFF_DOUBLE) {
+ if (type_int >= TIFF_BYTE && type_int <= TIFF_LONG8) {
type = (TIFFDataType)type_int;
}
}
@@ -769,7 +781,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
is_var_length = 1;
}
if (ImagingLibTiffMergeFieldInfo(
- &encoder->state, type, key_int, is_var_length)) {
+ &encoder->state, type, key_int, is_var_length
+ )) {
continue;
}
}
@@ -779,7 +792,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
&encoder->state,
(ttag_t)key_int,
PyBytes_Size(value),
- PyBytes_AsString(value));
+ PyBytes_AsString(value)
+ );
} else if (is_var_length) {
Py_ssize_t len, i;
TRACE(("Setting from Tuple: %d \n", key_int));
@@ -789,7 +803,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
int stride = 256;
if (len != 768) {
PyErr_SetString(
- PyExc_ValueError, "Requiring 768 items for Colormap");
+ PyExc_ValueError, "Requiring 768 items for Colormap"
+ );
return NULL;
}
UINT16 *av;
@@ -804,7 +819,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
(ttag_t)key_int,
av,
av + stride,
- av + stride * 2);
+ av + stride * 2
+ );
free(av);
}
} else if (key_int == TIFFTAG_YCBCRSUBSAMPLING) {
@@ -812,7 +828,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
&encoder->state,
(ttag_t)key_int,
(UINT16)PyLong_AsLong(PyTuple_GetItem(value, 0)),
- (UINT16)PyLong_AsLong(PyTuple_GetItem(value, 1)));
+ (UINT16)PyLong_AsLong(PyTuple_GetItem(value, 1))
+ );
} else if (type == TIFF_SHORT) {
UINT16 *av;
/* malloc check ok, calloc checks for overflow */
@@ -822,7 +839,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
av[i] = (UINT16)PyLong_AsLong(PyTuple_GetItem(value, i));
}
status = ImagingLibTiffSetField(
- &encoder->state, (ttag_t)key_int, len, av);
+ &encoder->state, (ttag_t)key_int, len, av
+ );
free(av);
}
} else if (type == TIFF_LONG) {
@@ -834,7 +852,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
av[i] = (UINT32)PyLong_AsLong(PyTuple_GetItem(value, i));
}
status = ImagingLibTiffSetField(
- &encoder->state, (ttag_t)key_int, len, av);
+ &encoder->state, (ttag_t)key_int, len, av
+ );
free(av);
}
} else if (type == TIFF_SBYTE) {
@@ -846,7 +865,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
av[i] = (INT8)PyLong_AsLong(PyTuple_GetItem(value, i));
}
status = ImagingLibTiffSetField(
- &encoder->state, (ttag_t)key_int, len, av);
+ &encoder->state, (ttag_t)key_int, len, av
+ );
free(av);
}
} else if (type == TIFF_SSHORT) {
@@ -858,7 +878,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
av[i] = (INT16)PyLong_AsLong(PyTuple_GetItem(value, i));
}
status = ImagingLibTiffSetField(
- &encoder->state, (ttag_t)key_int, len, av);
+ &encoder->state, (ttag_t)key_int, len, av
+ );
free(av);
}
} else if (type == TIFF_SLONG) {
@@ -870,7 +891,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
av[i] = (INT32)PyLong_AsLong(PyTuple_GetItem(value, i));
}
status = ImagingLibTiffSetField(
- &encoder->state, (ttag_t)key_int, len, av);
+ &encoder->state, (ttag_t)key_int, len, av
+ );
free(av);
}
} else if (type == TIFF_FLOAT) {
@@ -882,7 +904,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
av[i] = (FLOAT32)PyFloat_AsDouble(PyTuple_GetItem(value, i));
}
status = ImagingLibTiffSetField(
- &encoder->state, (ttag_t)key_int, len, av);
+ &encoder->state, (ttag_t)key_int, len, av
+ );
free(av);
}
} else if (type == TIFF_DOUBLE) {
@@ -894,43 +917,58 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
av[i] = PyFloat_AsDouble(PyTuple_GetItem(value, i));
}
status = ImagingLibTiffSetField(
- &encoder->state, (ttag_t)key_int, len, av);
+ &encoder->state, (ttag_t)key_int, len, av
+ );
free(av);
}
}
} else {
if (type == TIFF_SHORT) {
status = ImagingLibTiffSetField(
- &encoder->state, (ttag_t)key_int, (UINT16)PyLong_AsLong(value));
+ &encoder->state, (ttag_t)key_int, (UINT16)PyLong_AsLong(value)
+ );
} else if (type == TIFF_LONG) {
status = ImagingLibTiffSetField(
- &encoder->state, (ttag_t)key_int, PyLong_AsLongLong(value));
+ &encoder->state, (ttag_t)key_int, (UINT32)PyLong_AsLong(value)
+ );
} else if (type == TIFF_SSHORT) {
status = ImagingLibTiffSetField(
- &encoder->state, (ttag_t)key_int, (INT16)PyLong_AsLong(value));
+ &encoder->state, (ttag_t)key_int, (INT16)PyLong_AsLong(value)
+ );
} else if (type == TIFF_SLONG) {
status = ImagingLibTiffSetField(
- &encoder->state, (ttag_t)key_int, (INT32)PyLong_AsLong(value));
+ &encoder->state, (ttag_t)key_int, (INT32)PyLong_AsLong(value)
+ );
} else if (type == TIFF_FLOAT) {
status = ImagingLibTiffSetField(
- &encoder->state, (ttag_t)key_int, (FLOAT32)PyFloat_AsDouble(value));
+ &encoder->state, (ttag_t)key_int, (FLOAT32)PyFloat_AsDouble(value)
+ );
} else if (type == TIFF_DOUBLE) {
status = ImagingLibTiffSetField(
- &encoder->state, (ttag_t)key_int, (FLOAT64)PyFloat_AsDouble(value));
+ &encoder->state, (ttag_t)key_int, (FLOAT64)PyFloat_AsDouble(value)
+ );
} else if (type == TIFF_SBYTE) {
status = ImagingLibTiffSetField(
- &encoder->state, (ttag_t)key_int, (INT8)PyLong_AsLong(value));
+ &encoder->state, (ttag_t)key_int, (INT8)PyLong_AsLong(value)
+ );
} else if (type == TIFF_ASCII) {
status = ImagingLibTiffSetField(
- &encoder->state, (ttag_t)key_int, PyBytes_AsString(value));
+ &encoder->state, (ttag_t)key_int, PyBytes_AsString(value)
+ );
} else if (type == TIFF_RATIONAL) {
status = ImagingLibTiffSetField(
- &encoder->state, (ttag_t)key_int, (FLOAT64)PyFloat_AsDouble(value));
+ &encoder->state, (ttag_t)key_int, (FLOAT64)PyFloat_AsDouble(value)
+ );
+ } else if (type == TIFF_LONG8) {
+ status = ImagingLibTiffSetField(
+ &encoder->state, (ttag_t)key_int, (uint64_t)PyLong_AsLongLong(value)
+ );
} else {
TRACE(
("Unhandled type for key %d : %s \n",
key_int,
- PyBytes_AsString(PyObject_Str(value))));
+ PyBytes_AsString(PyObject_Str(value)))
+ );
}
}
if (!status) {
@@ -991,7 +1029,8 @@ get_qtables_arrays(PyObject *qtables, int *qtablesLen) {
if (num_tables < 1 || num_tables > NUM_QUANT_TBLS) {
PyErr_SetString(
PyExc_ValueError,
- "Not a valid number of quantization tables. Should be between 1 and 4.");
+ "Not a valid number of quantization tables. Should be between 1 and 4."
+ );
Py_DECREF(tables);
return NULL;
}
@@ -1080,7 +1119,8 @@ PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) {
&extra,
&extra_size,
&rawExif,
- &rawExifLen)) {
+ &rawExifLen
+ )) {
return NULL;
}
@@ -1150,29 +1190,27 @@ PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) {
encoder->encode = ImagingJpegEncode;
- strncpy(((JPEGENCODERSTATE *)encoder->state.context)->rawmode, rawmode, 8);
-
- ((JPEGENCODERSTATE *)encoder->state.context)->keep_rgb = keep_rgb;
- ((JPEGENCODERSTATE *)encoder->state.context)->quality = quality;
- ((JPEGENCODERSTATE *)encoder->state.context)->qtables = qarrays;
- ((JPEGENCODERSTATE *)encoder->state.context)->qtablesLen = qtablesLen;
- ((JPEGENCODERSTATE *)encoder->state.context)->subsampling = subsampling;
- ((JPEGENCODERSTATE *)encoder->state.context)->progressive = progressive;
- ((JPEGENCODERSTATE *)encoder->state.context)->smooth = smooth;
- ((JPEGENCODERSTATE *)encoder->state.context)->optimize = optimize;
- ((JPEGENCODERSTATE *)encoder->state.context)->streamtype = streamtype;
- ((JPEGENCODERSTATE *)encoder->state.context)->xdpi = xdpi;
- ((JPEGENCODERSTATE *)encoder->state.context)->ydpi = ydpi;
- ((JPEGENCODERSTATE *)encoder->state.context)->restart_marker_blocks =
- restart_marker_blocks;
- ((JPEGENCODERSTATE *)encoder->state.context)->restart_marker_rows =
- restart_marker_rows;
- ((JPEGENCODERSTATE *)encoder->state.context)->comment = comment;
- ((JPEGENCODERSTATE *)encoder->state.context)->comment_size = comment_size;
- ((JPEGENCODERSTATE *)encoder->state.context)->extra = extra;
- ((JPEGENCODERSTATE *)encoder->state.context)->extra_size = extra_size;
- ((JPEGENCODERSTATE *)encoder->state.context)->rawExif = rawExif;
- ((JPEGENCODERSTATE *)encoder->state.context)->rawExifLen = rawExifLen;
+ JPEGENCODERSTATE *jpeg_encoder_state = (JPEGENCODERSTATE *)encoder->state.context;
+ strncpy(jpeg_encoder_state->rawmode, rawmode, 8);
+ jpeg_encoder_state->keep_rgb = keep_rgb;
+ jpeg_encoder_state->quality = quality;
+ jpeg_encoder_state->qtables = qarrays;
+ jpeg_encoder_state->qtablesLen = qtablesLen;
+ jpeg_encoder_state->subsampling = subsampling;
+ jpeg_encoder_state->progressive = progressive;
+ jpeg_encoder_state->smooth = smooth;
+ jpeg_encoder_state->optimize = optimize;
+ jpeg_encoder_state->streamtype = streamtype;
+ jpeg_encoder_state->xdpi = xdpi;
+ jpeg_encoder_state->ydpi = ydpi;
+ jpeg_encoder_state->restart_marker_blocks = restart_marker_blocks;
+ jpeg_encoder_state->restart_marker_rows = restart_marker_rows;
+ jpeg_encoder_state->comment = comment;
+ jpeg_encoder_state->comment_size = comment_size;
+ jpeg_encoder_state->extra = extra;
+ jpeg_encoder_state->extra_size = extra_size;
+ jpeg_encoder_state->rawExif = rawExif;
+ jpeg_encoder_state->rawExifLen = rawExifLen;
return (PyObject *)encoder;
}
@@ -1250,7 +1288,8 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args) {
&fd,
&comment,
&comment_size,
- &plt)) {
+ &plt
+ )) {
return NULL;
}
@@ -1307,7 +1346,8 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args) {
j2k_decode_coord_tuple(offset, &context->offset_x, &context->offset_y);
j2k_decode_coord_tuple(
- tile_offset, &context->tile_offset_x, &context->tile_offset_y);
+ tile_offset, &context->tile_offset_x, &context->tile_offset_y
+ );
j2k_decode_coord_tuple(tile_size, &context->tile_size_x, &context->tile_size_y);
/* Error on illegal tile offsets */
@@ -1317,7 +1357,8 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args) {
PyErr_SetString(
PyExc_ValueError,
"JPEG 2000 tile offset too small; top left tile must "
- "intersect image area");
+ "intersect image area"
+ );
Py_DECREF(encoder);
return NULL;
}
@@ -1325,8 +1366,8 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args) {
if (context->tile_offset_x > context->offset_x ||
context->tile_offset_y > context->offset_y) {
PyErr_SetString(
- PyExc_ValueError,
- "JPEG 2000 tile offset too large to cover image area");
+ PyExc_ValueError, "JPEG 2000 tile offset too large to cover image area"
+ );
Py_DECREF(encoder);
return NULL;
}
@@ -1360,7 +1401,8 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args) {
j2k_decode_coord_tuple(cblk_size, &context->cblk_width, &context->cblk_height);
j2k_decode_coord_tuple(
- precinct_size, &context->precinct_width, &context->precinct_height);
+ precinct_size, &context->precinct_width, &context->precinct_height
+ );
context->irreversible = PyObject_IsTrue(irreversible);
context->progression = prog_order;
@@ -1373,10 +1415,3 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args) {
}
#endif
-
-/*
- * Local Variables:
- * c-basic-offset: 4
- * End:
- *
- */
diff --git a/src/libImaging/Access.c b/src/libImaging/Access.c
index 3a5e918e8..1c1937105 100644
--- a/src/libImaging/Access.c
+++ b/src/libImaging/Access.c
@@ -36,7 +36,8 @@ add_item(const char *mode) {
"AccessInit: hash collision: %d for both %s and %s\n",
i,
mode,
- access_table[i].mode);
+ access_table[i].mode
+ );
exit(1);
}
access_table[i].mode = mode;
@@ -184,7 +185,7 @@ put_pixel_32(Imaging im, int x, int y, const void *color) {
}
void
-ImagingAccessInit() {
+ImagingAccessInit(void) {
#define ADD(mode_, get_pixel_, put_pixel_) \
{ \
ImagingAccess access = add_item(mode_); \
diff --git a/src/libImaging/BcnDecode.c b/src/libImaging/BcnDecode.c
index 72f478d8d..9a41febc7 100644
--- a/src/libImaging/BcnDecode.c
+++ b/src/libImaging/BcnDecode.c
@@ -243,7 +243,8 @@ static const bc7_mode_info bc7_modes[] = {
{1, 0, 2, 1, 5, 6, 0, 0, 2, 3},
{1, 0, 2, 0, 7, 8, 0, 0, 2, 2},
{1, 0, 0, 0, 7, 7, 1, 0, 4, 0},
- {2, 6, 0, 0, 5, 5, 1, 0, 2, 0}};
+ {2, 6, 0, 0, 5, 5, 1, 0, 2, 0}
+};
/* Subset indices:
Table.P2, 1 bit per index */
@@ -254,7 +255,8 @@ static const UINT16 bc7_si2[] = {
0x718e, 0x399c, 0xaaaa, 0xf0f0, 0x5a5a, 0x33cc, 0x3c3c, 0x55aa, 0x9696, 0xa55a,
0x73ce, 0x13c8, 0x324c, 0x3bdc, 0x6996, 0xc33c, 0x9966, 0x0660, 0x0272, 0x04e4,
0x4e40, 0x2720, 0xc936, 0x936c, 0x39c6, 0x639c, 0x9336, 0x9cc6, 0x817e, 0xe718,
- 0xccf0, 0x0fcc, 0x7744, 0xee22};
+ 0xccf0, 0x0fcc, 0x7744, 0xee22
+};
/* Table.P3, 2 bits per index */
static const UINT32 bc7_si3[] = {
@@ -267,20 +269,23 @@ static const UINT32 bc7_si3[] = {
0x66660000, 0xa5a0a5a0, 0x50a050a0, 0x69286928, 0x44aaaa44, 0x66666600, 0xaa444444,
0x54a854a8, 0x95809580, 0x96969600, 0xa85454a8, 0x80959580, 0xaa141414, 0x96960000,
0xaaaa1414, 0xa05050a0, 0xa0a5a5a0, 0x96000000, 0x40804080, 0xa9a8a9a8, 0xaaaaaa44,
- 0x2a4a5254};
+ 0x2a4a5254
+};
/* Anchor indices:
Table.A2 */
-static const char bc7_ai0[] = {
- 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 2, 8, 2, 2, 8,
- 8, 15, 2, 8, 2, 2, 8, 8, 2, 2, 15, 15, 6, 8, 2, 8, 15, 15, 2, 8, 2, 2,
- 2, 15, 15, 6, 6, 2, 6, 8, 15, 15, 2, 2, 15, 15, 15, 15, 15, 2, 2, 15};
+static const char bc7_ai0[] = {15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+ 15, 15, 15, 15, 2, 8, 2, 2, 8, 8, 15, 2, 8,
+ 2, 2, 8, 8, 2, 2, 15, 15, 6, 8, 2, 8, 15,
+ 15, 2, 8, 2, 2, 2, 15, 15, 6, 6, 2, 6, 8,
+ 15, 15, 2, 2, 15, 15, 15, 15, 15, 2, 2, 15};
/* Table.A3a */
-static const char bc7_ai1[] = {
- 3, 3, 15, 15, 8, 3, 15, 15, 8, 8, 6, 6, 6, 5, 3, 3, 3, 3, 8, 15, 3, 3,
- 6, 10, 5, 8, 8, 6, 8, 5, 15, 15, 8, 15, 3, 5, 6, 10, 8, 15, 15, 3, 15, 5,
- 15, 15, 15, 15, 3, 15, 5, 5, 5, 8, 5, 10, 5, 10, 8, 13, 15, 12, 3, 3};
+static const char bc7_ai1[] = {3, 3, 15, 15, 8, 3, 15, 15, 8, 8, 6, 6, 6,
+ 5, 3, 3, 3, 3, 8, 15, 3, 3, 6, 10, 5, 8,
+ 8, 6, 8, 5, 15, 15, 8, 15, 3, 5, 6, 10, 8,
+ 15, 15, 3, 15, 5, 15, 15, 15, 15, 3, 15, 5, 5,
+ 5, 8, 5, 10, 5, 10, 8, 13, 15, 12, 3, 3};
/* Table.A3b */
static const char bc7_ai2[] = {15, 8, 8, 3, 15, 15, 3, 8, 15, 15, 15, 15, 15,
@@ -293,7 +298,8 @@ static const char bc7_ai2[] = {15, 8, 8, 3, 15, 15, 3, 8, 15, 15, 15, 15, 1
static const char bc7_weights2[] = {0, 21, 43, 64};
static const char bc7_weights3[] = {0, 9, 18, 27, 37, 46, 55, 64};
static const char bc7_weights4[] = {
- 0, 4, 9, 13, 17, 21, 26, 30, 34, 38, 43, 47, 51, 55, 60, 64};
+ 0, 4, 9, 13, 17, 21, 26, 30, 34, 38, 43, 47, 51, 55, 60, 64
+};
static const char *
bc7_get_weights(int n) {
@@ -526,7 +532,8 @@ static const bc6_mode_info bc6_modes[] = {
{1, 0, 0, 10, 10, 10, 10},
{1, 1, 0, 11, 9, 9, 9},
{1, 1, 0, 12, 8, 8, 8},
- {1, 1, 0, 16, 4, 4, 4}};
+ {1, 1, 0, 16, 4, 4, 4}
+};
/* Table.F, encoded as a sequence of bit indices */
static const UINT8 bc6_bit_packings[][75] = {
@@ -591,7 +598,8 @@ static const UINT8 bc6_bit_packings[][75] = {
64, 65, 66, 67, 68, 69, 70, 71, 27, 26, 80, 81, 82, 83, 84, 85, 86, 87, 43, 42},
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 48, 49, 50, 51, 15, 14, 13, 12, 11, 10,
- 64, 65, 66, 67, 31, 30, 29, 28, 27, 26, 80, 81, 82, 83, 47, 46, 45, 44, 43, 42}};
+ 64, 65, 66, 67, 31, 30, 29, 28, 27, 26, 80, 81, 82, 83, 47, 46, 45, 44, 43, 42}
+};
static void
bc6_sign_extend(UINT16 *v, int prec) {
@@ -830,7 +838,8 @@ decode_bcn(
int bytes,
int N,
int C,
- char *pixel_format) {
+ char *pixel_format
+) {
int ymax = state->ysize + state->yoff;
const UINT8 *ptr = src;
switch (N) {
diff --git a/src/libImaging/BoxBlur.c b/src/libImaging/BoxBlur.c
index 4ea9c7717..ed91541fe 100644
--- a/src/libImaging/BoxBlur.c
+++ b/src/libImaging/BoxBlur.c
@@ -13,7 +13,8 @@ void static inline ImagingLineBoxBlur32(
int edgeA,
int edgeB,
UINT32 ww,
- UINT32 fw) {
+ UINT32 fw
+) {
int x;
UINT32 acc[4];
UINT32 bulk[4];
@@ -109,7 +110,8 @@ void static inline ImagingLineBoxBlur8(
int edgeA,
int edgeB,
UINT32 ww,
- UINT32 fw) {
+ UINT32 fw
+) {
int x;
UINT32 acc;
UINT32 bulk;
@@ -198,7 +200,8 @@ ImagingHorizontalBoxBlur(Imaging imOut, Imaging imIn, float floatRadius) {
edgeA,
edgeB,
ww,
- fw);
+ fw
+ );
if (imIn == imOut) {
// Commit.
memcpy(imOut->image8[y], lineOut, imIn->xsize);
@@ -214,7 +217,8 @@ ImagingHorizontalBoxBlur(Imaging imOut, Imaging imIn, float floatRadius) {
edgeA,
edgeB,
ww,
- fw);
+ fw
+ );
if (imIn == imOut) {
// Commit.
memcpy(imOut->image32[y], lineOut, imIn->xsize * 4);
@@ -234,6 +238,9 @@ ImagingBoxBlur(Imaging imOut, Imaging imIn, float xradius, float yradius, int n)
int i;
Imaging imTransposed;
+ if (imOut->xsize == 0 || imOut->ysize == 0) {
+ return imOut;
+ }
if (n < 1) {
return ImagingError_ValueError("number of passes must be greater than zero");
}
@@ -314,11 +321,13 @@ _gaussian_blur_radius(float radius, int passes) {
Imaging
ImagingGaussianBlur(
- Imaging imOut, Imaging imIn, float xradius, float yradius, int passes) {
+ Imaging imOut, Imaging imIn, float xradius, float yradius, int passes
+) {
return ImagingBoxBlur(
imOut,
imIn,
_gaussian_blur_radius(xradius, passes),
_gaussian_blur_radius(yradius, passes),
- passes);
+ passes
+ );
}
diff --git a/src/libImaging/Chops.c b/src/libImaging/Chops.c
index f9c005efe..f326d402f 100644
--- a/src/libImaging/Chops.c
+++ b/src/libImaging/Chops.c
@@ -142,7 +142,8 @@ ImagingChopSoftLight(Imaging imIn1, Imaging imIn2) {
CHOP2(
(((255 - in1[x]) * (in1[x] * in2[x])) / 65536) +
(in1[x] * (255 - ((255 - in1[x]) * (255 - in2[x]) / 255))) / 255,
- NULL);
+ NULL
+ );
}
Imaging
@@ -150,7 +151,8 @@ ImagingChopHardLight(Imaging imIn1, Imaging imIn2) {
CHOP2(
(in2[x] < 128) ? ((in1[x] * in2[x]) / 127)
: 255 - (((255 - in2[x]) * (255 - in1[x])) / 127),
- NULL);
+ NULL
+ );
}
Imaging
@@ -158,5 +160,6 @@ ImagingOverlay(Imaging imIn1, Imaging imIn2) {
CHOP2(
(in1[x] < 128) ? ((in1[x] * in2[x]) / 127)
: 255 - (((255 - in1[x]) * (255 - in2[x])) / 127),
- NULL);
+ NULL
+ );
}
diff --git a/src/libImaging/ColorLUT.c b/src/libImaging/ColorLUT.c
index aee7cda06..5559de689 100644
--- a/src/libImaging/ColorLUT.c
+++ b/src/libImaging/ColorLUT.c
@@ -63,7 +63,8 @@ ImagingColorLUT3D_linear(
int size1D,
int size2D,
int size3D,
- INT16 *table) {
+ INT16 *table
+) {
/* This float to int conversion doesn't have rounding
error compensation (+0.5) for two reasons:
1. As we don't hit the highest value,
@@ -112,7 +113,8 @@ ImagingColorLUT3D_linear(
index2D >> SCALE_BITS,
index3D >> SCALE_BITS,
size1D,
- size1D_2D);
+ size1D_2D
+ );
INT16 result[4], left[4], right[4];
INT16 leftleft[4], leftright[4], rightleft[4], rightright[4];
@@ -123,19 +125,22 @@ ImagingColorLUT3D_linear(
leftright,
&table[idx + size1D * 3],
&table[idx + size1D * 3 + 3],
- shift1D);
+ shift1D
+ );
interpolate3(left, leftleft, leftright, shift2D);
interpolate3(
rightleft,
&table[idx + size1D_2D * 3],
&table[idx + size1D_2D * 3 + 3],
- shift1D);
+ shift1D
+ );
interpolate3(
rightright,
&table[idx + size1D_2D * 3 + size1D * 3],
&table[idx + size1D_2D * 3 + size1D * 3 + 3],
- shift1D);
+ shift1D
+ );
interpolate3(right, rightleft, rightright, shift2D);
interpolate3(result, left, right, shift3D);
@@ -144,7 +149,8 @@ ImagingColorLUT3D_linear(
clip8(result[0]),
clip8(result[1]),
clip8(result[2]),
- rowIn[x * 4 + 3]);
+ rowIn[x * 4 + 3]
+ );
memcpy(rowOut + x * sizeof(v), &v, sizeof(v));
}
@@ -155,19 +161,22 @@ ImagingColorLUT3D_linear(
leftright,
&table[idx + size1D * 4],
&table[idx + size1D * 4 + 4],
- shift1D);
+ shift1D
+ );
interpolate4(left, leftleft, leftright, shift2D);
interpolate4(
rightleft,
&table[idx + size1D_2D * 4],
&table[idx + size1D_2D * 4 + 4],
- shift1D);
+ shift1D
+ );
interpolate4(
rightright,
&table[idx + size1D_2D * 4 + size1D * 4],
&table[idx + size1D_2D * 4 + size1D * 4 + 4],
- shift1D);
+ shift1D
+ );
interpolate4(right, rightleft, rightright, shift2D);
interpolate4(result, left, right, shift3D);
@@ -176,7 +185,8 @@ ImagingColorLUT3D_linear(
clip8(result[0]),
clip8(result[1]),
clip8(result[2]),
- clip8(result[3]));
+ clip8(result[3])
+ );
memcpy(rowOut + x * sizeof(v), &v, sizeof(v));
}
}
diff --git a/src/libImaging/Convert.c b/src/libImaging/Convert.c
index fcb5f7ad9..c8f234261 100644
--- a/src/libImaging/Convert.c
+++ b/src/libImaging/Convert.c
@@ -1044,7 +1044,8 @@ static struct {
{"I;16L", "F", I16L_F},
{"I;16B", "F", I16B_F},
- {NULL}};
+ {NULL}
+};
/* FIXME: translate indexed versions to pointer versions below this line */
@@ -1316,7 +1317,8 @@ frompalette(Imaging imOut, Imaging imIn, const char *mode) {
(UINT8 *)imOut->image[y],
(UINT8 *)imIn->image[y],
imIn->xsize,
- imIn->palette);
+ imIn->palette
+ );
}
ImagingSectionLeave(&cookie);
@@ -1328,11 +1330,8 @@ frompalette(Imaging imOut, Imaging imIn, const char *mode) {
#endif
static Imaging
topalette(
- Imaging imOut,
- Imaging imIn,
- const char *mode,
- ImagingPalette inpalette,
- int dither) {
+ Imaging imOut, Imaging imIn, const char *mode, ImagingPalette inpalette, int dither
+) {
ImagingSectionCookie cookie;
int alpha;
int x, y;
@@ -1623,7 +1622,8 @@ tobilevel(Imaging imOut, Imaging imIn) {
static Imaging
convert(
- Imaging imOut, Imaging imIn, const char *mode, ImagingPalette palette, int dither) {
+ Imaging imOut, Imaging imIn, const char *mode, ImagingPalette palette, int dither
+) {
ImagingSectionCookie cookie;
ImagingShuffler convert;
int y;
@@ -1677,7 +1677,8 @@ convert(
#else
static char buf[100];
snprintf(
- buf, 100, "conversion from %.10s to %.10s not supported", imIn->mode, mode);
+ buf, 100, "conversion from %.10s to %.10s not supported", imIn->mode, mode
+ );
return (Imaging)ImagingError_ValueError(buf);
#endif
}
@@ -1727,18 +1728,16 @@ ImagingConvertTransparent(Imaging imIn, const char *mode, int r, int g, int b) {
if (strcmp(mode, "RGBa") == 0) {
premultiplied = 1;
}
- } else if (
- strcmp(imIn->mode, "RGB") == 0 &&
- (strcmp(mode, "LA") == 0 || strcmp(mode, "La") == 0)) {
+ } else if (strcmp(imIn->mode, "RGB") == 0 &&
+ (strcmp(mode, "LA") == 0 || strcmp(mode, "La") == 0)) {
convert = rgb2la;
source_transparency = 1;
if (strcmp(mode, "La") == 0) {
premultiplied = 1;
}
- } else if (
- (strcmp(imIn->mode, "1") == 0 || strcmp(imIn->mode, "I") == 0 ||
- strcmp(imIn->mode, "I;16") == 0 || strcmp(imIn->mode, "L") == 0) &&
- (strcmp(mode, "RGBA") == 0 || strcmp(mode, "LA") == 0)) {
+ } else if ((strcmp(imIn->mode, "1") == 0 || strcmp(imIn->mode, "I") == 0 ||
+ strcmp(imIn->mode, "I;16") == 0 || strcmp(imIn->mode, "L") == 0) &&
+ (strcmp(mode, "RGBA") == 0 || strcmp(mode, "LA") == 0)) {
if (strcmp(imIn->mode, "1") == 0) {
convert = bit2rgb;
} else if (strcmp(imIn->mode, "I") == 0) {
@@ -1756,7 +1755,8 @@ ImagingConvertTransparent(Imaging imIn, const char *mode, int r, int g, int b) {
100,
"conversion from %.10s to %.10s not supported in convert_transparent",
imIn->mode,
- mode);
+ mode
+ );
return (Imaging)ImagingError_ValueError(buf);
}
diff --git a/src/libImaging/ConvertYCbCr.c b/src/libImaging/ConvertYCbCr.c
index 142f065e5..285b43327 100644
--- a/src/libImaging/ConvertYCbCr.c
+++ b/src/libImaging/ConvertYCbCr.c
@@ -47,7 +47,8 @@ static INT16 Y_R[] = {
4019, 4038, 4057, 4076, 4095, 4114, 4133, 4153, 4172, 4191, 4210, 4229, 4248, 4267,
4286, 4306, 4325, 4344, 4363, 4382, 4401, 4420, 4440, 4459, 4478, 4497, 4516, 4535,
4554, 4574, 4593, 4612, 4631, 4650, 4669, 4688, 4707, 4727, 4746, 4765, 4784, 4803,
- 4822, 4841, 4861, 4880};
+ 4822, 4841, 4861, 4880
+};
static INT16 Y_G[] = {
0, 38, 75, 113, 150, 188, 225, 263, 301, 338, 376, 413, 451, 488,
@@ -68,7 +69,8 @@ static INT16 Y_G[] = {
7889, 7927, 7964, 8002, 8040, 8077, 8115, 8152, 8190, 8227, 8265, 8303, 8340, 8378,
8415, 8453, 8490, 8528, 8566, 8603, 8641, 8678, 8716, 8753, 8791, 8828, 8866, 8904,
8941, 8979, 9016, 9054, 9091, 9129, 9167, 9204, 9242, 9279, 9317, 9354, 9392, 9430,
- 9467, 9505, 9542, 9580};
+ 9467, 9505, 9542, 9580
+};
static INT16 Y_B[] = {
0, 7, 15, 22, 29, 36, 44, 51, 58, 66, 73, 80, 88, 95,
@@ -89,7 +91,8 @@ static INT16 Y_B[] = {
1532, 1539, 1547, 1554, 1561, 1569, 1576, 1583, 1591, 1598, 1605, 1612, 1620, 1627,
1634, 1642, 1649, 1656, 1663, 1671, 1678, 1685, 1693, 1700, 1707, 1715, 1722, 1729,
1736, 1744, 1751, 1758, 1766, 1773, 1780, 1788, 1795, 1802, 1809, 1817, 1824, 1831,
- 1839, 1846, 1853, 1860};
+ 1839, 1846, 1853, 1860
+};
static INT16 Cb_R[] = {
0, -10, -21, -31, -42, -53, -64, -75, -85, -96, -107, -118,
@@ -113,7 +116,8 @@ static INT16 Cb_R[] = {
-2332, -2342, -2353, -2364, -2375, -2386, -2396, -2407, -2418, -2429, -2440, -2450,
-2461, -2472, -2483, -2494, -2504, -2515, -2526, -2537, -2548, -2558, -2569, -2580,
-2591, -2602, -2612, -2623, -2634, -2645, -2656, -2666, -2677, -2688, -2699, -2710,
- -2720, -2731, -2742, -2753};
+ -2720, -2731, -2742, -2753
+};
static INT16 Cb_G[] = {
0, -20, -41, -63, -84, -105, -126, -147, -169, -190, -211, -232,
@@ -137,7 +141,8 @@ static INT16 Cb_G[] = {
-4578, -4600, -4621, -4642, -4663, -4684, -4706, -4727, -4748, -4769, -4790, -4812,
-4833, -4854, -4875, -4896, -4918, -4939, -4960, -4981, -5002, -5024, -5045, -5066,
-5087, -5108, -5130, -5151, -5172, -5193, -5214, -5236, -5257, -5278, -5299, -5320,
- -5342, -5363, -5384, -5405};
+ -5342, -5363, -5384, -5405
+};
static INT16 Cb_B[] = {
0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416,
@@ -158,7 +163,8 @@ static INT16 Cb_B[] = {
6720, 6752, 6784, 6816, 6848, 6880, 6912, 6944, 6976, 7008, 7040, 7072, 7104, 7136,
7168, 7200, 7232, 7264, 7296, 7328, 7360, 7392, 7424, 7456, 7488, 7520, 7552, 7584,
7616, 7648, 7680, 7712, 7744, 7776, 7808, 7840, 7872, 7904, 7936, 7968, 8000, 8032,
- 8064, 8096, 8128, 8160};
+ 8064, 8096, 8128, 8160
+};
#define Cr_R Cb_B
@@ -184,7 +190,8 @@ static INT16 Cr_G[] = {
-5787, -5814, -5841, -5867, -5894, -5921, -5948, -5975, -6001, -6028, -6055, -6082,
-6109, -6135, -6162, -6189, -6216, -6243, -6269, -6296, -6323, -6350, -6376, -6403,
-6430, -6457, -6484, -6510, -6537, -6564, -6591, -6618, -6644, -6671, -6698, -6725,
- -6752, -6778, -6805, -6832};
+ -6752, -6778, -6805, -6832
+};
static INT16 Cr_B[] = {
0, -4, -9, -15, -20, -25, -30, -35, -41, -46, -51, -56,
@@ -208,7 +215,8 @@ static INT16 Cr_B[] = {
-1123, -1128, -1133, -1139, -1144, -1149, -1154, -1159, -1165, -1170, -1175, -1180,
-1185, -1191, -1196, -1201, -1206, -1211, -1217, -1222, -1227, -1232, -1238, -1243,
-1248, -1253, -1258, -1264, -1269, -1274, -1279, -1284, -1290, -1295, -1300, -1305,
- -1310, -1316, -1321, -1326};
+ -1310, -1316, -1321, -1326
+};
static INT16 R_Cr[] = {
-11484, -11394, -11305, -11215, -11125, -11036, -10946, -10856, -10766, -10677,
@@ -236,7 +244,8 @@ static INT16 R_Cr[] = {
8255, 8345, 8434, 8524, 8614, 8704, 8793, 8883, 8973, 9063,
9152, 9242, 9332, 9421, 9511, 9601, 9691, 9780, 9870, 9960,
10050, 10139, 10229, 10319, 10408, 10498, 10588, 10678, 10767, 10857,
- 10947, 11037, 11126, 11216, 11306, 11395};
+ 10947, 11037, 11126, 11216, 11306, 11395
+};
static INT16 G_Cb[] = {
2819, 2797, 2775, 2753, 2731, 2709, 2687, 2665, 2643, 2621, 2599, 2577,
@@ -260,7 +269,8 @@ static INT16 G_Cb[] = {
-1937, -1959, -1981, -2003, -2025, -2047, -2069, -2091, -2113, -2135, -2157, -2179,
-2201, -2224, -2246, -2268, -2290, -2312, -2334, -2356, -2378, -2400, -2422, -2444,
-2466, -2488, -2510, -2532, -2554, -2576, -2598, -2620, -2642, -2664, -2686, -2708,
- -2730, -2752, -2774, -2796};
+ -2730, -2752, -2774, -2796
+};
static INT16 G_Cr[] = {
5850, 5805, 5759, 5713, 5667, 5622, 5576, 5530, 5485, 5439, 5393, 5347,
@@ -284,7 +294,8 @@ static INT16 G_Cr[] = {
-4021, -4067, -4112, -4158, -4204, -4250, -4295, -4341, -4387, -4432, -4478, -4524,
-4569, -4615, -4661, -4707, -4752, -4798, -4844, -4889, -4935, -4981, -5027, -5072,
-5118, -5164, -5209, -5255, -5301, -5346, -5392, -5438, -5484, -5529, -5575, -5621,
- -5666, -5712, -5758, -5804};
+ -5666, -5712, -5758, -5804
+};
static INT16 B_Cb[] = {
-14515, -14402, -14288, -14175, -14062, -13948, -13835, -13721, -13608, -13495,
@@ -312,7 +323,8 @@ static INT16 B_Cb[] = {
10434, 10547, 10660, 10774, 10887, 11001, 11114, 11227, 11341, 11454,
11568, 11681, 11794, 11908, 12021, 12135, 12248, 12361, 12475, 12588,
12702, 12815, 12929, 13042, 13155, 13269, 13382, 13496, 13609, 13722,
- 13836, 13949, 14063, 14176, 14289, 14403};
+ 13836, 13949, 14063, 14176, 14289, 14403
+};
void
ImagingConvertRGB2YCbCr(UINT8 *out, const UINT8 *in, int pixels) {
diff --git a/src/libImaging/Dib.c b/src/libImaging/Dib.c
index 269be1058..c69e9e552 100644
--- a/src/libImaging/Dib.c
+++ b/src/libImaging/Dib.c
@@ -95,7 +95,8 @@ ImagingNewDIB(const char *mode, int xsize, int ysize) {
}
dib->bitmap = CreateDIBSection(
- dib->dc, dib->info, DIB_RGB_COLORS, (void **)&dib->bits, NULL, 0);
+ dib->dc, dib->info, DIB_RGB_COLORS, (void **)&dib->bits, NULL, 0
+ );
if (!dib->bitmap) {
free(dib->info);
free(dib);
@@ -218,7 +219,8 @@ ImagingPasteDIB(ImagingDIB dib, Imaging im, int xy[4]) {
dib->bits + dib->linesize * (dib->ysize - (xy[1] + y) - 1) +
xy[0] * dib->pixelsize,
im->image[y],
- im->xsize);
+ im->xsize
+ );
}
}
@@ -251,7 +253,8 @@ ImagingDrawDIB(ImagingDIB dib, void *dc, int dst[4], int src[4]) {
dib->bits,
dib->info,
DIB_RGB_COLORS,
- SRCCOPY);
+ SRCCOPY
+ );
} else {
/* stretchblt (displays) */
if (dib->palette != 0) {
@@ -268,7 +271,8 @@ ImagingDrawDIB(ImagingDIB dib, void *dc, int dst[4], int src[4]) {
src[1],
src[2] - src[0],
src[3] - src[1],
- SRCCOPY);
+ SRCCOPY
+ );
}
}
diff --git a/src/libImaging/Draw.c b/src/libImaging/Draw.c
index 133696dd8..ea6f8805e 100644
--- a/src/libImaging/Draw.c
+++ b/src/libImaging/Draw.c
@@ -120,9 +120,8 @@ hline8(Imaging im, int x0, int y0, int x1, int ink) {
if (x0 <= x1) {
pixelwidth = strncmp(im->mode, "I;16", 4) == 0 ? 2 : 1;
memset(
- im->image8[y0] + x0 * pixelwidth,
- (UINT8)ink,
- (x1 - x0 + 1) * pixelwidth);
+ im->image8[y0] + x0 * pixelwidth, (UINT8)ink, (x1 - x0 + 1) * pixelwidth
+ );
}
}
}
@@ -408,7 +407,8 @@ x_cmp(const void *x0, const void *x1) {
static void
draw_horizontal_lines(
- Imaging im, int n, Edge *e, int ink, int *x_pos, int y, hline_handler hline) {
+ Imaging im, int n, Edge *e, int ink, int *x_pos, int y, hline_handler hline
+) {
int i;
for (i = 0; i < n; i++) {
if (e[i].ymin == y && e[i].ymin == e[i].ymax) {
@@ -440,13 +440,8 @@ draw_horizontal_lines(
*/
static inline int
polygon_generic(
- Imaging im,
- int n,
- Edge *e,
- int ink,
- int eofill,
- hline_handler hline,
- int hasAlpha) {
+ Imaging im, int n, Edge *e, int ink, int eofill, hline_handler hline, int hasAlpha
+) {
Edge **edge_table;
float *xx;
int edge_count = 0;
@@ -506,7 +501,8 @@ polygon_generic(
// Needed to draw consistent polygons
xx[j] = xx[j - 1];
j++;
- } else if (current->dx != 0 && roundf(xx[j - 1]) == xx[j - 1]) {
+ } else if (current->dx != 0 && j % 2 == 1 &&
+ roundf(xx[j - 1]) == xx[j - 1]) {
// Connect discontiguous corners
for (k = 0; k < i; k++) {
Edge *other_edge = edge_table[k];
@@ -515,10 +511,8 @@ polygon_generic(
continue;
}
// Check if the two edges join to make a corner
- if (((ymin == current->ymin && ymin == other_edge->ymin) ||
- (ymin == current->ymax && ymin == other_edge->ymax)) &&
- xx[j - 1] == (ymin - other_edge->y0) * other_edge->dx +
- other_edge->x0) {
+ if (xx[j - 1] ==
+ (ymin - other_edge->y0) * other_edge->dx + other_edge->x0) {
// Determine points from the edges on the next row
// Or if this is the last row, check the previous row
int offset = ymin == ymax ? -1 : 1;
@@ -530,25 +524,29 @@ polygon_generic(
other_edge->x0;
if (ymin == current->ymax) {
if (current->dx > 0) {
- xx[k] = fmax(
- adjacent_line_x,
- adjacent_line_x_other_edge) +
- 1;
+ xx[k] =
+ fmax(
+ adjacent_line_x, adjacent_line_x_other_edge
+ ) +
+ 1;
} else {
- xx[k] = fmin(
- adjacent_line_x,
- adjacent_line_x_other_edge) -
- 1;
+ xx[k] =
+ fmin(
+ adjacent_line_x, adjacent_line_x_other_edge
+ ) -
+ 1;
}
} else {
if (current->dx > 0) {
xx[k] = fmin(
- adjacent_line_x, adjacent_line_x_other_edge);
+ adjacent_line_x, adjacent_line_x_other_edge
+ );
} else {
- xx[k] = fmax(
- adjacent_line_x,
- adjacent_line_x_other_edge) +
- 1;
+ xx[k] =
+ fmax(
+ adjacent_line_x, adjacent_line_x_other_edge
+ ) +
+ 1;
}
}
break;
@@ -699,7 +697,8 @@ ImagingDrawLine(Imaging im, int x0, int y0, int x1, int y1, const void *ink_, in
int
ImagingDrawWideLine(
- Imaging im, int x0, int y0, int x1, int y1, const void *ink_, int width, int op) {
+ Imaging im, int x0, int y0, int x1, int y1, const void *ink_, int width, int op
+) {
DRAW *draw;
INT32 ink;
int dx, dy;
@@ -730,7 +729,8 @@ ImagingDrawWideLine(
{x0 - dxmin, y0 + dymax},
{x1 - dxmin, y1 + dymax},
{x1 + dxmax, y1 - dymin},
- {x0 + dxmax, y0 - dymin}};
+ {x0 + dxmax, y0 - dymin}
+ };
add_edge(e + 0, vertices[0][0], vertices[0][1], vertices[1][0], vertices[1][1]);
add_edge(e + 1, vertices[1][0], vertices[1][1], vertices[2][0], vertices[2][1]);
@@ -752,7 +752,8 @@ ImagingDrawRectangle(
const void *ink_,
int fill,
int width,
- int op) {
+ int op
+) {
int i;
int y;
int tmp;
@@ -800,7 +801,8 @@ ImagingDrawRectangle(
int
ImagingDrawPolygon(
- Imaging im, int count, int *xy, const void *ink_, int fill, int width, int op) {
+ Imaging im, int count, int *xy, const void *ink_, int fill, int width, int op
+) {
int i, n, x0, y0, x1, y1;
DRAW *draw;
INT32 ink;
@@ -851,7 +853,8 @@ ImagingDrawPolygon(
if (width == 1) {
for (i = 0; i < count - 1; i++) {
draw->line(
- im, xy[i * 2], xy[i * 2 + 1], xy[i * 2 + 2], xy[i * 2 + 3], ink);
+ im, xy[i * 2], xy[i * 2 + 1], xy[i * 2 + 2], xy[i * 2 + 3], ink
+ );
}
draw->line(im, xy[i * 2], xy[i * 2 + 1], xy[0], xy[1], ink);
} else {
@@ -864,10 +867,12 @@ ImagingDrawPolygon(
xy[i * 2 + 3],
ink_,
width,
- op);
+ op
+ );
}
ImagingDrawWideLine(
- im, xy[i * 2], xy[i * 2 + 1], xy[0], xy[1], ink_, width, op);
+ im, xy[i * 2], xy[i * 2 + 1], xy[0], xy[1], ink_, width, op
+ );
}
}
@@ -877,7 +882,8 @@ ImagingDrawPolygon(
int
ImagingDrawBitmap(Imaging im, int x0, int y0, Imaging bitmap, const void *ink, int op) {
return ImagingFill2(
- im, ink, bitmap, x0, y0, x0 + bitmap->xsize, y0 + bitmap->ysize);
+ im, ink, bitmap, x0, y0, x0 + bitmap->xsize, y0 + bitmap->ysize
+ );
}
/* -------------------------------------------------------------------- */
@@ -1086,7 +1092,8 @@ clip_tree_transpose(clip_node *root) {
// segments, i.e. something like correct bracket sequences.
int
clip_tree_do_clip(
- clip_node *root, int32_t x0, int32_t y, int32_t x1, event_list **ret) {
+ clip_node *root, int32_t x0, int32_t y, int32_t x1, event_list **ret
+) {
if (root == NULL) {
event_list *start = malloc(sizeof(event_list));
if (!start) {
@@ -1223,7 +1230,8 @@ typedef struct {
} clip_ellipse_state;
typedef void (*clip_ellipse_init)(
- clip_ellipse_state *, int32_t, int32_t, int32_t, float, float);
+ clip_ellipse_state *, int32_t, int32_t, int32_t, float, float
+);
void
debug_clip_tree(clip_node *root, int space) {
@@ -1335,7 +1343,8 @@ arc_init(clip_ellipse_state *s, int32_t a, int32_t b, int32_t w, float al, float
// A chord line.
void
chord_line_init(
- clip_ellipse_state *s, int32_t a, int32_t b, int32_t w, float al, float ar) {
+ clip_ellipse_state *s, int32_t a, int32_t b, int32_t w, float al, float ar
+) {
ellipse_init(&s->st, a, b, a + b + 1);
s->head = NULL;
@@ -1362,7 +1371,8 @@ chord_line_init(
// Pie side.
void
pie_side_init(
- clip_ellipse_state *s, int32_t a, int32_t b, int32_t w, float al, float _) {
+ clip_ellipse_state *s, int32_t a, int32_t b, int32_t w, float al, float _
+) {
ellipse_init(&s->st, a, b, a + b + 1);
s->head = NULL;
@@ -1478,7 +1488,8 @@ clip_ellipse_free(clip_ellipse_state *s) {
int8_t
clip_ellipse_next(
- clip_ellipse_state *s, int32_t *ret_x0, int32_t *ret_y, int32_t *ret_x1) {
+ clip_ellipse_state *s, int32_t *ret_x0, int32_t *ret_y, int32_t *ret_x1
+) {
int32_t x0, y, x1;
while (s->head == NULL && ellipse_next(&s->st, &x0, &y, &x1) >= 0) {
if (clip_tree_do_clip(s->root, x0, y, x1, &s->head) < 0) {
@@ -1512,7 +1523,8 @@ ellipseNew(
const void *ink_,
int fill,
int width,
- int op) {
+ int op
+) {
DRAW *draw;
INT32 ink;
DRAWINIT();
@@ -1547,7 +1559,8 @@ clipEllipseNew(
const void *ink_,
int width,
int op,
- clip_ellipse_init init) {
+ clip_ellipse_init init
+) {
DRAW *draw;
INT32 ink;
DRAWINIT();
@@ -1580,7 +1593,8 @@ arcNew(
float end,
const void *ink_,
int width,
- int op) {
+ int op
+) {
return clipEllipseNew(im, x0, y0, x1, y1, start, end, ink_, width, op, arc_init);
}
@@ -1595,7 +1609,8 @@ chordNew(
float end,
const void *ink_,
int width,
- int op) {
+ int op
+) {
return clipEllipseNew(im, x0, y0, x1, y1, start, end, ink_, width, op, chord_init);
}
@@ -1610,9 +1625,11 @@ chordLineNew(
float end,
const void *ink_,
int width,
- int op) {
+ int op
+) {
return clipEllipseNew(
- im, x0, y0, x1, y1, start, end, ink_, width, op, chord_line_init);
+ im, x0, y0, x1, y1, start, end, ink_, width, op, chord_line_init
+ );
}
static int
@@ -1626,7 +1643,8 @@ pieNew(
float end,
const void *ink_,
int width,
- int op) {
+ int op
+) {
return clipEllipseNew(im, x0, y0, x1, y1, start, end, ink_, width, op, pie_init);
}
@@ -1640,7 +1658,8 @@ pieSideNew(
float start,
const void *ink_,
int width,
- int op) {
+ int op
+) {
return clipEllipseNew(im, x0, y0, x1, y1, start, 0, ink_, width, op, pie_side_init);
}
@@ -1654,7 +1673,8 @@ ImagingDrawEllipse(
const void *ink,
int fill,
int width,
- int op) {
+ int op
+) {
return ellipseNew(im, x0, y0, x1, y1, ink, fill, width, op);
}
@@ -1669,7 +1689,8 @@ ImagingDrawArc(
float end,
const void *ink,
int width,
- int op) {
+ int op
+) {
normalize_angles(&start, &end);
if (start + 360 == end) {
return ImagingDrawEllipse(im, x0, y0, x1, y1, ink, 0, width, op);
@@ -1692,7 +1713,8 @@ ImagingDrawChord(
const void *ink,
int fill,
int width,
- int op) {
+ int op
+) {
normalize_angles(&start, &end);
if (start + 360 == end) {
return ImagingDrawEllipse(im, x0, y0, x1, y1, ink, fill, width, op);
@@ -1722,7 +1744,8 @@ ImagingDrawPieslice(
const void *ink,
int fill,
int width,
- int op) {
+ int op
+) {
normalize_angles(&start, &end);
if (start + 360 == end) {
return ellipseNew(im, x0, y0, x1, y1, ink, fill, width, op);
@@ -1850,13 +1873,8 @@ ImagingOutlineLine(ImagingOutline outline, float x1, float y1) {
int
ImagingOutlineCurve(
- ImagingOutline outline,
- float x1,
- float y1,
- float x2,
- float y2,
- float x3,
- float y3) {
+ ImagingOutline outline, float x1, float y1, float x2, float y2, float x3, float y3
+) {
Edge *e;
int i;
float xo, yo;
@@ -1970,7 +1988,8 @@ ImagingOutlineTransform(ImagingOutline outline, double a[6]) {
int
ImagingDrawOutline(
- Imaging im, ImagingOutline outline, const void *ink_, int fill, int op) {
+ Imaging im, ImagingOutline outline, const void *ink_, int fill, int op
+) {
DRAW *draw;
INT32 ink;
diff --git a/src/libImaging/Fill.c b/src/libImaging/Fill.c
index 5b6bfb89c..8fb481e7e 100644
--- a/src/libImaging/Fill.c
+++ b/src/libImaging/Fill.c
@@ -118,8 +118,8 @@ ImagingFillRadialGradient(const char *mode) {
for (y = 0; y < 256; y++) {
for (x = 0; x < 256; x++) {
- d = (int)sqrt(
- (double)((x - 128) * (x - 128) + (y - 128) * (y - 128)) * 2.0);
+ d = (int
+ )sqrt((double)((x - 128) * (x - 128) + (y - 128) * (y - 128)) * 2.0);
if (d >= 255) {
d = 255;
}
diff --git a/src/libImaging/Filter.c b/src/libImaging/Filter.c
index 85de77fcb..7b7b2e429 100644
--- a/src/libImaging/Filter.c
+++ b/src/libImaging/Filter.c
@@ -26,6 +26,8 @@
#include "Imaging.h"
+#define ROUND_UP(f) ((int)((f) >= 0.0 ? (f) + 0.5F : (f) - 0.5F))
+
static inline UINT8
clip8(float in) {
if (in <= 0.0) {
@@ -59,7 +61,8 @@ ImagingExpand(Imaging imIn, int xmargin, int ymargin) {
}
imOut = ImagingNewDirty(
- imIn->mode, imIn->xsize + 2 * xmargin, imIn->ysize + 2 * ymargin);
+ imIn->mode, imIn->xsize + 2 * xmargin, imIn->ysize + 2 * ymargin
+ );
if (!imOut) {
return NULL;
}
@@ -104,6 +107,22 @@ ImagingExpand(Imaging imIn, int xmargin, int ymargin) {
return imOut;
}
+float
+kernel_i16(int size, UINT8 *in0, int x, const float *kernel, int bigendian) {
+ int i;
+ float result = 0;
+ int half_size = (size - 1) / 2;
+ for (i = 0; i < size; i++) {
+ int x1 = x + i - half_size;
+ result += _i2f(
+ in0[x1 * 2 + (bigendian ? 1 : 0)] +
+ (in0[x1 * 2 + (bigendian ? 0 : 1)] >> 8)
+ ) *
+ kernel[i];
+ }
+ return result;
+}
+
void
ImagingFilter3x3(Imaging imOut, Imaging im, const float *kernel, float offset) {
#define KERNEL1x3(in0, x, kernel, d) \
@@ -134,6 +153,16 @@ ImagingFilter3x3(Imaging imOut, Imaging im, const float *kernel, float offset) {
out[x] = in0[x];
}
} else {
+ int bigendian = 0;
+ if (im->type == IMAGING_TYPE_SPECIAL) {
+ if (strcmp(im->mode, "I;16B") == 0
+#ifdef WORDS_BIGENDIAN
+ || strcmp(im->mode, "I;16N") == 0
+#endif
+ ) {
+ bigendian = 1;
+ }
+ }
for (y = 1; y < im->ysize - 1; y++) {
UINT8 *in_1 = (UINT8 *)im->image[y - 1];
UINT8 *in0 = (UINT8 *)im->image[y];
@@ -141,14 +170,31 @@ ImagingFilter3x3(Imaging imOut, Imaging im, const float *kernel, float offset) {
UINT8 *out = (UINT8 *)imOut->image[y];
out[0] = in0[0];
+ if (im->type == IMAGING_TYPE_SPECIAL) {
+ out[1] = in0[1];
+ }
for (x = 1; x < im->xsize - 1; x++) {
float ss = offset;
- ss += KERNEL1x3(in1, x, &kernel[0], 1);
- ss += KERNEL1x3(in0, x, &kernel[3], 1);
- ss += KERNEL1x3(in_1, x, &kernel[6], 1);
- out[x] = clip8(ss);
+ if (im->type == IMAGING_TYPE_SPECIAL) {
+ ss += kernel_i16(3, in1, x, &kernel[0], bigendian);
+ ss += kernel_i16(3, in0, x, &kernel[3], bigendian);
+ ss += kernel_i16(3, in_1, x, &kernel[6], bigendian);
+ int ss_int = ROUND_UP(ss);
+ out[x * 2 + (bigendian ? 1 : 0)] = clip8(ss_int % 256);
+ out[x * 2 + (bigendian ? 0 : 1)] = clip8(ss_int >> 8);
+ } else {
+ ss += KERNEL1x3(in1, x, &kernel[0], 1);
+ ss += KERNEL1x3(in0, x, &kernel[3], 1);
+ ss += KERNEL1x3(in_1, x, &kernel[6], 1);
+ out[x] = clip8(ss);
+ }
+ }
+ if (im->type == IMAGING_TYPE_SPECIAL) {
+ out[x * 2] = in0[x * 2];
+ out[x * 2 + 1] = in0[x * 2 + 1];
+ } else {
+ out[x] = in0[x];
}
- out[x] = in0[x];
}
}
} else {
@@ -260,6 +306,16 @@ ImagingFilter5x5(Imaging imOut, Imaging im, const float *kernel, float offset) {
out[x + 1] = in0[x + 1];
}
} else {
+ int bigendian = 0;
+ if (im->type == IMAGING_TYPE_SPECIAL) {
+ if (strcmp(im->mode, "I;16B") == 0
+#ifdef WORDS_BIGENDIAN
+ || strcmp(im->mode, "I;16N") == 0
+#endif
+ ) {
+ bigendian = 1;
+ }
+ }
for (y = 2; y < im->ysize - 2; y++) {
UINT8 *in_2 = (UINT8 *)im->image[y - 2];
UINT8 *in_1 = (UINT8 *)im->image[y - 1];
@@ -270,17 +326,39 @@ ImagingFilter5x5(Imaging imOut, Imaging im, const float *kernel, float offset) {
out[0] = in0[0];
out[1] = in0[1];
+ if (im->type == IMAGING_TYPE_SPECIAL) {
+ out[2] = in0[2];
+ out[3] = in0[3];
+ }
for (x = 2; x < im->xsize - 2; x++) {
float ss = offset;
- ss += KERNEL1x5(in2, x, &kernel[0], 1);
- ss += KERNEL1x5(in1, x, &kernel[5], 1);
- ss += KERNEL1x5(in0, x, &kernel[10], 1);
- ss += KERNEL1x5(in_1, x, &kernel[15], 1);
- ss += KERNEL1x5(in_2, x, &kernel[20], 1);
- out[x] = clip8(ss);
+ if (im->type == IMAGING_TYPE_SPECIAL) {
+ ss += kernel_i16(5, in2, x, &kernel[0], bigendian);
+ ss += kernel_i16(5, in1, x, &kernel[5], bigendian);
+ ss += kernel_i16(5, in0, x, &kernel[10], bigendian);
+ ss += kernel_i16(5, in_1, x, &kernel[15], bigendian);
+ ss += kernel_i16(5, in_2, x, &kernel[20], bigendian);
+ int ss_int = ROUND_UP(ss);
+ out[x * 2 + (bigendian ? 1 : 0)] = clip8(ss_int % 256);
+ out[x * 2 + (bigendian ? 0 : 1)] = clip8(ss_int >> 8);
+ } else {
+ ss += KERNEL1x5(in2, x, &kernel[0], 1);
+ ss += KERNEL1x5(in1, x, &kernel[5], 1);
+ ss += KERNEL1x5(in0, x, &kernel[10], 1);
+ ss += KERNEL1x5(in_1, x, &kernel[15], 1);
+ ss += KERNEL1x5(in_2, x, &kernel[20], 1);
+ out[x] = clip8(ss);
+ }
+ }
+ if (im->type == IMAGING_TYPE_SPECIAL) {
+ out[x * 2 + 0] = in0[x * 2 + 0];
+ out[x * 2 + 1] = in0[x * 2 + 1];
+ out[x * 2 + 2] = in0[x * 2 + 2];
+ out[x * 2 + 3] = in0[x * 2 + 3];
+ } else {
+ out[x + 0] = in0[x + 0];
+ out[x + 1] = in0[x + 1];
}
- out[x + 0] = in0[x + 0];
- out[x + 1] = in0[x + 1];
}
}
} else {
@@ -369,7 +447,8 @@ ImagingFilter5x5(Imaging imOut, Imaging im, const float *kernel, float offset) {
}
}
memcpy(
- out + x * sizeof(UINT32), in0 + x * sizeof(UINT32), sizeof(UINT32) * 2);
+ out + x * sizeof(UINT32), in0 + x * sizeof(UINT32), sizeof(UINT32) * 2
+ );
}
}
memcpy(imOut->image[y], im->image[y], im->linesize);
@@ -381,7 +460,8 @@ ImagingFilter(Imaging im, int xsize, int ysize, const FLOAT32 *kernel, FLOAT32 o
Imaging imOut;
ImagingSectionCookie cookie;
- if (im->type != IMAGING_TYPE_UINT8 && im->type != IMAGING_TYPE_INT32) {
+ if (im->type == IMAGING_TYPE_FLOAT32 ||
+ (im->type == IMAGING_TYPE_SPECIAL && im->bands != 1)) {
return (Imaging)ImagingError_ModeError();
}
diff --git a/src/libImaging/FliDecode.c b/src/libImaging/FliDecode.c
index 6b2518d35..130ecb7f7 100644
--- a/src/libImaging/FliDecode.c
+++ b/src/libImaging/FliDecode.c
@@ -224,7 +224,7 @@ ImagingFliDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t byt
break;
case 16:
/* COPY chunk */
- if (INT32_MAX / state->xsize < state->ysize) {
+ if (INT32_MAX < (uint64_t)state->xsize * state->ysize) {
/* Integer overflow, bail */
state->errcode = IMAGING_CODEC_OVERRUN;
return -1;
diff --git a/src/libImaging/Geometry.c b/src/libImaging/Geometry.c
index cf3bc9979..1e2abd7e7 100644
--- a/src/libImaging/Geometry.c
+++ b/src/libImaging/Geometry.c
@@ -781,7 +781,8 @@ ImagingGenericTransform(
ImagingTransformMap transform,
void *transform_data,
int filterid,
- int fill) {
+ int fill
+) {
/* slow generic transformation. use ImagingTransformAffine or
ImagingScaleAffine where possible. */
@@ -790,15 +791,15 @@ ImagingGenericTransform(
char *out;
double xx, yy;
+ if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) {
+ return (Imaging)ImagingError_ModeError();
+ }
+
ImagingTransformFilter filter = getfilter(imIn, filterid);
if (!filter) {
return (Imaging)ImagingError_ValueError("bad filter number");
}
- if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) {
- return (Imaging)ImagingError_ModeError();
- }
-
ImagingCopyPalette(imOut, imIn);
ImagingSectionEnter(&cookie);
@@ -836,14 +837,8 @@ ImagingGenericTransform(
static Imaging
ImagingScaleAffine(
- Imaging imOut,
- Imaging imIn,
- int x0,
- int y0,
- int x1,
- int y1,
- double a[6],
- int fill) {
+ Imaging imOut, Imaging imIn, int x0, int y0, int x1, int y1, double a[6], int fill
+) {
/* scale, nearest neighbour resampling */
ImagingSectionCookie cookie;
@@ -936,7 +931,8 @@ static inline int
check_fixed(double a[6], int x, int y) {
return (
fabs(x * a[0] + y * a[1] + a[2]) < 32768.0 &&
- fabs(x * a[3] + y * a[4] + a[5]) < 32768.0);
+ fabs(x * a[3] + y * a[4] + a[5]) < 32768.0
+ );
}
static inline Imaging
@@ -949,7 +945,8 @@ affine_fixed(
int y1,
double a[6],
int filterid,
- int fill) {
+ int fill
+) {
/* affine transform, nearest neighbour resampling, fixed point
arithmetics */
@@ -1026,7 +1023,8 @@ ImagingTransformAffine(
int y1,
double a[6],
int filterid,
- int fill) {
+ int fill
+) {
/* affine transform, nearest neighbour resampling, floating point
arithmetics*/
@@ -1037,9 +1035,14 @@ ImagingTransformAffine(
double xx, yy;
double xo, yo;
+ if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) {
+ return (Imaging)ImagingError_ModeError();
+ }
+
if (filterid || imIn->type == IMAGING_TYPE_SPECIAL) {
return ImagingGenericTransform(
- imOut, imIn, x0, y0, x1, y1, affine_transform, a, filterid, fill);
+ imOut, imIn, x0, y0, x1, y1, affine_transform, a, filterid, fill
+ );
}
if (a[1] == 0 && a[3] == 0) {
@@ -1047,10 +1050,6 @@ ImagingTransformAffine(
return ImagingScaleAffine(imOut, imIn, x0, y0, x1, y1, a, fill);
}
- if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) {
- return (Imaging)ImagingError_ModeError();
- }
-
if (x0 < 0) {
x0 = 0;
}
@@ -1134,13 +1133,15 @@ ImagingTransform(
int y1,
double a[8],
int filterid,
- int fill) {
+ int fill
+) {
ImagingTransformMap transform;
switch (method) {
case IMAGING_TRANSFORM_AFFINE:
return ImagingTransformAffine(
- imOut, imIn, x0, y0, x1, y1, a, filterid, fill);
+ imOut, imIn, x0, y0, x1, y1, a, filterid, fill
+ );
break;
case IMAGING_TRANSFORM_PERSPECTIVE:
transform = perspective_transform;
@@ -1153,5 +1154,6 @@ ImagingTransform(
}
return ImagingGenericTransform(
- imOut, imIn, x0, y0, x1, y1, transform, a, filterid, fill);
+ imOut, imIn, x0, y0, x1, y1, transform, a, filterid, fill
+ );
}
diff --git a/src/libImaging/GetBBox.c b/src/libImaging/GetBBox.c
index bd2a2778c..d430893dd 100644
--- a/src/libImaging/GetBBox.c
+++ b/src/libImaging/GetBBox.c
@@ -30,26 +30,57 @@ ImagingGetBBox(Imaging im, int bbox[4], int alpha_only) {
bbox[1] = -1;
bbox[2] = bbox[3] = 0;
-#define GETBBOX(image, mask) \
- for (y = 0; y < im->ysize; y++) { \
- has_data = 0; \
- for (x = 0; x < im->xsize; x++) { \
- if (im->image[y][x] & mask) { \
- has_data = 1; \
- if (x < bbox[0]) { \
- bbox[0] = x; \
- } \
- if (x >= bbox[2]) { \
- bbox[2] = x + 1; \
- } \
- } \
- } \
- if (has_data) { \
- if (bbox[1] < 0) { \
- bbox[1] = y; \
- } \
- bbox[3] = y + 1; \
- } \
+#define GETBBOX(image, mask) \
+ /* first stage: looking for any pixels from top */ \
+ for (y = 0; y < im->ysize; y++) { \
+ has_data = 0; \
+ for (x = 0; x < im->xsize; x++) { \
+ if (im->image[y][x] & mask) { \
+ has_data = 1; \
+ bbox[0] = x; \
+ bbox[1] = y; \
+ break; \
+ } \
+ } \
+ if (has_data) { \
+ break; \
+ } \
+ } \
+ /* Check that we have a box */ \
+ if (bbox[1] < 0) { \
+ return 0; /* no data */ \
+ } \
+ /* second stage: looking for any pixels from bottom */ \
+ for (y = im->ysize - 1; y >= bbox[1]; y--) { \
+ has_data = 0; \
+ for (x = 0; x < im->xsize; x++) { \
+ if (im->image[y][x] & mask) { \
+ has_data = 1; \
+ if (x < bbox[0]) { \
+ bbox[0] = x; \
+ } \
+ bbox[3] = y + 1; \
+ break; \
+ } \
+ } \
+ if (has_data) { \
+ break; \
+ } \
+ } \
+ /* third stage: looking for left and right boundaries */ \
+ for (y = bbox[1]; y < bbox[3]; y++) { \
+ for (x = 0; x < bbox[0]; x++) { \
+ if (im->image[y][x] & mask) { \
+ bbox[0] = x; \
+ break; \
+ } \
+ } \
+ for (x = im->xsize - 1; x >= bbox[2]; x--) { \
+ if (im->image[y][x] & mask) { \
+ bbox[2] = x + 1; \
+ break; \
+ } \
+ } \
}
if (im->image8) {
@@ -58,11 +89,10 @@ ImagingGetBBox(Imaging im, int bbox[4], int alpha_only) {
INT32 mask = 0xffffffff;
if (im->bands == 3) {
((UINT8 *)&mask)[3] = 0;
- } else if (
- alpha_only &&
- (strcmp(im->mode, "RGBa") == 0 || strcmp(im->mode, "RGBA") == 0 ||
- strcmp(im->mode, "La") == 0 || strcmp(im->mode, "LA") == 0 ||
- strcmp(im->mode, "PA") == 0)) {
+ } else if (alpha_only &&
+ (strcmp(im->mode, "RGBa") == 0 || strcmp(im->mode, "RGBA") == 0 ||
+ strcmp(im->mode, "La") == 0 || strcmp(im->mode, "LA") == 0 ||
+ strcmp(im->mode, "PA") == 0)) {
#ifdef WORDS_BIGENDIAN
mask = 0x000000ff;
#else
@@ -72,11 +102,6 @@ ImagingGetBBox(Imaging im, int bbox[4], int alpha_only) {
GETBBOX(image32, mask);
}
- /* Check that we got a box */
- if (bbox[1] < 0) {
- return 0; /* no data */
- }
-
return 1; /* ok */
}
@@ -145,6 +170,9 @@ ImagingGetExtrema(Imaging im, void *extrema) {
imax = in[x];
}
}
+ if (imin == 0 && imax == 255) {
+ break;
+ }
}
((UINT8 *)extrema)[0] = (UINT8)imin;
((UINT8 *)extrema)[1] = (UINT8)imax;
@@ -246,13 +274,14 @@ getcolors32(Imaging im, int maxcolors, int *size) {
code in Python 2.1.3; the exact implementation is borrowed from
Python's Unicode property database (written by yours truly) /F */
- static int SIZES[] = {
- 4, 3, 8, 3, 16, 3, 32, 5, 64, 3,
- 128, 3, 256, 29, 512, 17, 1024, 9, 2048, 5,
- 4096, 83, 8192, 27, 16384, 43, 32768, 3, 65536, 45,
- 131072, 9, 262144, 39, 524288, 39, 1048576, 9, 2097152, 5,
- 4194304, 3, 8388608, 33, 16777216, 27, 33554432, 9, 67108864, 71,
- 134217728, 39, 268435456, 9, 536870912, 5, 1073741824, 83, 0};
+ static int SIZES[] = {4, 3, 8, 3, 16, 3, 32, 5,
+ 64, 3, 128, 3, 256, 29, 512, 17,
+ 1024, 9, 2048, 5, 4096, 83, 8192, 27,
+ 16384, 43, 32768, 3, 65536, 45, 131072, 9,
+ 262144, 39, 524288, 39, 1048576, 9, 2097152, 5,
+ 4194304, 3, 8388608, 33, 16777216, 27, 33554432, 9,
+ 67108864, 71, 134217728, 39, 268435456, 9, 536870912, 5,
+ 1073741824, 83, 0};
code_size = code_poly = code_mask = 0;
diff --git a/src/libImaging/GifEncode.c b/src/libImaging/GifEncode.c
index 45b67616d..203fb9d0a 100644
--- a/src/libImaging/GifEncode.c
+++ b/src/libImaging/GifEncode.c
@@ -79,7 +79,8 @@ glzwe(
UINT8 *out_ptr,
UINT32 *in_avail,
UINT32 *out_avail,
- UINT32 end_of_data) {
+ UINT32 end_of_data
+) {
switch (st->entry_state) {
case LZW_TRY_IN1:
get_first_byte:
@@ -312,7 +313,8 @@ ImagingGifEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
state->buffer,
(UINT8 *)im->image[state->y + state->yoff] +
state->xoff * im->pixelsize,
- state->xsize);
+ state->xsize
+ );
state->x = 0;
/* step forward, according to the interlace settings */
@@ -348,7 +350,8 @@ ImagingGifEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
ptr,
&in_avail,
&out_avail,
- state->state == FINISH);
+ state->state == FINISH
+ );
out_used = sub_block_limit - ptr - out_avail;
*sub_block_ptr += out_used;
ptr += out_used;
diff --git a/src/libImaging/HexDecode.c b/src/libImaging/HexDecode.c
index bd16cdbe1..e26c0e9b3 100644
--- a/src/libImaging/HexDecode.c
+++ b/src/libImaging/HexDecode.c
@@ -49,7 +49,8 @@ ImagingHexDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t byt
if (++state->x >= state->bytes) {
/* Got a full line, unpack it */
state->shuffle(
- (UINT8 *)im->image[state->y], state->buffer, state->xsize);
+ (UINT8 *)im->image[state->y], state->buffer, state->xsize
+ );
state->x = 0;
diff --git a/src/libImaging/ImPlatform.h b/src/libImaging/ImPlatform.h
index f6e7fb6b9..c9b7e43b4 100644
--- a/src/libImaging/ImPlatform.h
+++ b/src/libImaging/ImPlatform.h
@@ -7,6 +7,7 @@
* Copyright (c) Fredrik Lundh 1995-2003.
*/
+#define PY_SSIZE_T_CLEAN
#include "Python.h"
/* Check that we have an ANSI compliant compiler */
diff --git a/src/libImaging/Imaging.h b/src/libImaging/Imaging.h
index 1f2c03e93..31052c68a 100644
--- a/src/libImaging/Imaging.h
+++ b/src/libImaging/Imaging.h
@@ -60,8 +60,8 @@ typedef struct ImagingHistogramInstance *ImagingHistogram;
typedef struct ImagingOutlineInstance *ImagingOutline;
typedef struct ImagingPaletteInstance *ImagingPalette;
-/* handle magics (used with PyCObject). */
-#define IMAGING_MAGIC "PIL Imaging"
+/* handle magics (used with PyCapsule). */
+#define IMAGING_MAGIC "Pillow Imaging"
/* pixel types */
#define IMAGING_TYPE_UINT8 0
@@ -161,6 +161,9 @@ typedef struct ImagingMemoryArena {
int stats_reallocated_blocks; /* Number of blocks which were actually reallocated
after retrieving */
int stats_freed_blocks; /* Number of freed blocks */
+#ifdef Py_GIL_DISABLED
+ PyMutex mutex;
+#endif
} *ImagingMemoryArena;
/* Objects */
@@ -295,7 +298,8 @@ extern Imaging
ImagingFill(Imaging im, const void *ink);
extern int
ImagingFill2(
- Imaging into, const void *ink, Imaging mask, int x0, int y0, int x1, int y1);
+ Imaging into, const void *ink, Imaging mask, int x0, int y0, int x1, int y1
+);
extern Imaging
ImagingFillBand(Imaging im, int band, int color);
extern Imaging
@@ -310,7 +314,8 @@ extern Imaging
ImagingFlipTopBottom(Imaging imOut, Imaging imIn);
extern Imaging
ImagingGaussianBlur(
- Imaging imOut, Imaging imIn, float xradius, float yradius, int passes);
+ Imaging imOut, Imaging imIn, float xradius, float yradius, int passes
+);
extern Imaging
ImagingGetBand(Imaging im, int band);
extern Imaging
@@ -373,7 +378,8 @@ ImagingTransform(
int y1,
double a[8],
int filter,
- int fill);
+ int fill
+);
extern Imaging
ImagingUnsharpMask(Imaging imOut, Imaging im, float radius, int percent, int threshold);
extern Imaging
@@ -386,7 +392,8 @@ ImagingColorLUT3D_linear(
int size1D,
int size2D,
int size3D,
- INT16 *table);
+ INT16 *table
+);
extern Imaging
ImagingCopy2(Imaging imOut, Imaging imIn);
@@ -440,7 +447,8 @@ ImagingDrawArc(
float end,
const void *ink,
int width,
- int op);
+ int op
+);
extern int
ImagingDrawBitmap(Imaging im, int x0, int y0, Imaging bitmap, const void *ink, int op);
extern int
@@ -455,7 +463,8 @@ ImagingDrawChord(
const void *ink,
int fill,
int width,
- int op);
+ int op
+);
extern int
ImagingDrawEllipse(
Imaging im,
@@ -466,12 +475,14 @@ ImagingDrawEllipse(
const void *ink,
int fill,
int width,
- int op);
+ int op
+);
extern int
ImagingDrawLine(Imaging im, int x0, int y0, int x1, int y1, const void *ink, int op);
extern int
ImagingDrawWideLine(
- Imaging im, int x0, int y0, int x1, int y1, const void *ink, int width, int op);
+ Imaging im, int x0, int y0, int x1, int y1, const void *ink, int width, int op
+);
extern int
ImagingDrawPieslice(
Imaging im,
@@ -484,12 +495,14 @@ ImagingDrawPieslice(
const void *ink,
int fill,
int width,
- int op);
+ int op
+);
extern int
ImagingDrawPoint(Imaging im, int x, int y, const void *ink, int op);
extern int
ImagingDrawPolygon(
- Imaging im, int points, int *xy, const void *ink, int fill, int width, int op);
+ Imaging im, int points, int *xy, const void *ink, int fill, int width, int op
+);
extern int
ImagingDrawRectangle(
Imaging im,
@@ -500,7 +513,8 @@ ImagingDrawRectangle(
const void *ink,
int fill,
int width,
- int op);
+ int op
+);
/* Level 2 graphics (WORK IN PROGRESS) */
extern ImagingOutline
@@ -510,7 +524,8 @@ ImagingOutlineDelete(ImagingOutline outline);
extern int
ImagingDrawOutline(
- Imaging im, ImagingOutline outline, const void *ink, int fill, int op);
+ Imaging im, ImagingOutline outline, const void *ink, int fill, int op
+);
extern int
ImagingOutlineMove(ImagingOutline outline, float x, float y);
@@ -518,7 +533,8 @@ extern int
ImagingOutlineLine(ImagingOutline outline, float x, float y);
extern int
ImagingOutlineCurve(
- ImagingOutline outline, float x1, float y1, float x2, float y2, float x3, float y3);
+ ImagingOutline outline, float x1, float y1, float x2, float y2, float x3, float y3
+);
extern int
ImagingOutlineTransform(ImagingOutline outline, double a[6]);
@@ -545,7 +561,8 @@ ImagingSavePPM(Imaging im, const char *filename);
/* Codecs */
typedef struct ImagingCodecStateInstance *ImagingCodecState;
typedef int (*ImagingCodec)(
- Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes);
+ Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes
+);
extern int
ImagingBcnDecode(Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes);
@@ -575,7 +592,8 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes)
#ifdef HAVE_OPENJPEG
extern int
ImagingJpeg2KDecode(
- Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes);
+ Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes
+);
extern int
ImagingJpeg2KDecodeCleanup(ImagingCodecState state);
extern int
@@ -586,7 +604,8 @@ ImagingJpeg2KEncodeCleanup(ImagingCodecState state);
#ifdef HAVE_LIBTIFF
extern int
ImagingLibTiffDecode(
- Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes);
+ Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes
+);
extern int
ImagingLibTiffEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes);
#endif
@@ -598,7 +617,8 @@ extern int
ImagingMspDecode(Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes);
extern int
ImagingPackbitsDecode(
- Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes);
+ Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes
+);
extern int
ImagingPcdDecode(Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes);
extern int
@@ -611,13 +631,16 @@ extern int
ImagingRawEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes);
extern int
ImagingSgiRleDecode(
- Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes);
+ Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes
+);
extern int
ImagingSunRleDecode(
- Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes);
+ Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes
+);
extern int
ImagingTgaRleDecode(
- Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes);
+ Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes
+);
extern int
ImagingTgaRleEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes);
extern int
@@ -690,6 +713,15 @@ _imaging_tell_pyFd(PyObject *fd);
#include "ImagingUtils.h"
extern UINT8 *clip8_lookups;
+/* Mutex lock/unlock helpers */
+#ifdef Py_GIL_DISABLED
+#define MUTEX_LOCK(m) PyMutex_Lock(m)
+#define MUTEX_UNLOCK(m) PyMutex_Unlock(m)
+#else
+#define MUTEX_LOCK(m)
+#define MUTEX_UNLOCK(m)
+#endif
+
#if defined(__cplusplus)
}
#endif
diff --git a/src/libImaging/Jpeg2K.h b/src/libImaging/Jpeg2K.h
index e8d92f7b6..6fbbf7311 100644
--- a/src/libImaging/Jpeg2K.h
+++ b/src/libImaging/Jpeg2K.h
@@ -104,10 +104,3 @@ typedef struct {
int plt;
} JPEG2KENCODESTATE;
-
-/*
- * Local Variables:
- * c-basic-offset: 4
- * End:
- *
- */
diff --git a/src/libImaging/Jpeg2KDecode.c b/src/libImaging/Jpeg2KDecode.c
index dd066c10b..fc927d2f0 100644
--- a/src/libImaging/Jpeg2KDecode.c
+++ b/src/libImaging/Jpeg2KDecode.c
@@ -67,7 +67,8 @@ j2k_skip(OPJ_OFF_T p_nb_bytes, void *p_user_data) {
/* -------------------------------------------------------------------- */
typedef void (*j2k_unpacker_t)(
- opj_image_t *in, const JPEG2KTILEINFO *tileInfo, const UINT8 *data, Imaging im);
+ opj_image_t *in, const JPEG2KTILEINFO *tileInfo, const UINT8 *data, Imaging im
+);
struct j2k_decode_unpacker {
const char *mode;
@@ -89,10 +90,8 @@ j2ku_shift(unsigned x, int n) {
static void
j2ku_gray_l(
- opj_image_t *in,
- const JPEG2KTILEINFO *tileinfo,
- const UINT8 *tiledata,
- Imaging im) {
+ opj_image_t *in, const JPEG2KTILEINFO *tileinfo, const UINT8 *tiledata, Imaging im
+) {
unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0;
unsigned w = tileinfo->x1 - tileinfo->x0;
unsigned h = tileinfo->y1 - tileinfo->y0;
@@ -145,10 +144,8 @@ j2ku_gray_l(
static void
j2ku_gray_i(
- opj_image_t *in,
- const JPEG2KTILEINFO *tileinfo,
- const UINT8 *tiledata,
- Imaging im) {
+ opj_image_t *in, const JPEG2KTILEINFO *tileinfo, const UINT8 *tiledata, Imaging im
+) {
unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0;
unsigned w = tileinfo->x1 - tileinfo->x0;
unsigned h = tileinfo->y1 - tileinfo->y0;
@@ -204,10 +201,8 @@ j2ku_gray_i(
static void
j2ku_gray_rgb(
- opj_image_t *in,
- const JPEG2KTILEINFO *tileinfo,
- const UINT8 *tiledata,
- Imaging im) {
+ opj_image_t *in, const JPEG2KTILEINFO *tileinfo, const UINT8 *tiledata, Imaging im
+) {
unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0;
unsigned w = tileinfo->x1 - tileinfo->x0;
unsigned h = tileinfo->y1 - tileinfo->y0;
@@ -268,10 +263,8 @@ j2ku_gray_rgb(
static void
j2ku_graya_la(
- opj_image_t *in,
- const JPEG2KTILEINFO *tileinfo,
- const UINT8 *tiledata,
- Imaging im) {
+ opj_image_t *in, const JPEG2KTILEINFO *tileinfo, const UINT8 *tiledata, Imaging im
+) {
unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0;
unsigned w = tileinfo->x1 - tileinfo->x0;
unsigned h = tileinfo->y1 - tileinfo->y0;
@@ -347,10 +340,8 @@ j2ku_graya_la(
static void
j2ku_srgb_rgb(
- opj_image_t *in,
- const JPEG2KTILEINFO *tileinfo,
- const UINT8 *tiledata,
- Imaging im) {
+ opj_image_t *in, const JPEG2KTILEINFO *tileinfo, const UINT8 *tiledata, Imaging im
+) {
unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0;
unsigned w = tileinfo->x1 - tileinfo->x0;
unsigned h = tileinfo->y1 - tileinfo->y0;
@@ -413,10 +404,8 @@ j2ku_srgb_rgb(
static void
j2ku_sycc_rgb(
- opj_image_t *in,
- const JPEG2KTILEINFO *tileinfo,
- const UINT8 *tiledata,
- Imaging im) {
+ opj_image_t *in, const JPEG2KTILEINFO *tileinfo, const UINT8 *tiledata, Imaging im
+) {
unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0;
unsigned w = tileinfo->x1 - tileinfo->x0;
unsigned h = tileinfo->y1 - tileinfo->y0;
@@ -482,10 +471,8 @@ j2ku_sycc_rgb(
static void
j2ku_srgba_rgba(
- opj_image_t *in,
- const JPEG2KTILEINFO *tileinfo,
- const UINT8 *tiledata,
- Imaging im) {
+ opj_image_t *in, const JPEG2KTILEINFO *tileinfo, const UINT8 *tiledata, Imaging im
+) {
unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0;
unsigned w = tileinfo->x1 - tileinfo->x0;
unsigned h = tileinfo->y1 - tileinfo->y0;
@@ -547,10 +534,8 @@ j2ku_srgba_rgba(
static void
j2ku_sycca_rgba(
- opj_image_t *in,
- const JPEG2KTILEINFO *tileinfo,
- const UINT8 *tiledata,
- Imaging im) {
+ opj_image_t *in, const JPEG2KTILEINFO *tileinfo, const UINT8 *tiledata, Imaging im
+) {
unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0;
unsigned w = tileinfo->x1 - tileinfo->x0;
unsigned h = tileinfo->y1 - tileinfo->y0;
@@ -713,8 +698,7 @@ j2k_decode_entry(Imaging im, ImagingCodecState state) {
}
/* Check that this image is something we can handle */
- if (image->numcomps < 1 || image->numcomps > 4 ||
- image->color_space == OPJ_CLRSPC_UNKNOWN) {
+ if (image->numcomps < 1 || image->numcomps > 4) {
state->errcode = IMAGING_CODEC_BROKEN;
state->state = J2K_STATE_FAILED;
goto quick_exit;
@@ -759,7 +743,7 @@ j2k_decode_entry(Imaging im, ImagingCodecState state) {
/* Find the correct unpacker */
color_space = image->color_space;
- if (color_space == OPJ_CLRSPC_UNSPECIFIED) {
+ if (color_space == OPJ_CLRSPC_UNKNOWN || color_space == OPJ_CLRSPC_UNSPECIFIED) {
switch (image->numcomps) {
case 1:
case 2:
@@ -815,7 +799,8 @@ j2k_decode_entry(Imaging im, ImagingCodecState state) {
&tile_info.x1,
&tile_info.y1,
&tile_info.nb_comps,
- &should_continue)) {
+ &should_continue
+ )) {
state->errcode = IMAGING_CODEC_BROKEN;
state->state = J2K_STATE_FAILED;
goto quick_exit;
@@ -906,7 +891,8 @@ j2k_decode_entry(Imaging im, ImagingCodecState state) {
tile_info.tile_index,
(OPJ_BYTE *)state->buffer,
tile_info.data_size,
- stream)) {
+ stream
+ )) {
state->errcode = IMAGING_CODEC_BROKEN;
state->state = J2K_STATE_FAILED;
goto quick_exit;
@@ -993,10 +979,3 @@ ImagingJpeg2KVersion(void) {
}
#endif /* HAVE_OPENJPEG */
-
-/*
- * Local Variables:
- * c-basic-offset: 4
- * End:
- *
- */
diff --git a/src/libImaging/Jpeg2KEncode.c b/src/libImaging/Jpeg2KEncode.c
index 7f1aeaddb..d30ccde60 100644
--- a/src/libImaging/Jpeg2KEncode.c
+++ b/src/libImaging/Jpeg2KEncode.c
@@ -89,7 +89,8 @@ j2k_seek(OPJ_OFF_T p_nb_bytes, void *p_user_data) {
/* -------------------------------------------------------------------- */
typedef void (*j2k_pack_tile_t)(
- Imaging im, UINT8 *buf, unsigned x0, unsigned y0, unsigned w, unsigned h);
+ Imaging im, UINT8 *buf, unsigned x0, unsigned y0, unsigned w, unsigned h
+);
static void
j2k_pack_l(Imaging im, UINT8 *buf, unsigned x0, unsigned y0, unsigned w, unsigned h) {
@@ -157,7 +158,8 @@ j2k_pack_rgb(Imaging im, UINT8 *buf, unsigned x0, unsigned y0, unsigned w, unsig
static void
j2k_pack_rgba(
- Imaging im, UINT8 *buf, unsigned x0, unsigned y0, unsigned w, unsigned h) {
+ Imaging im, UINT8 *buf, unsigned x0, unsigned y0, unsigned w, unsigned h
+) {
UINT8 *pr = buf;
UINT8 *pg = pr + w * h;
UINT8 *pb = pg + w * h;
@@ -205,8 +207,8 @@ j2k_set_cinema_params(Imaging im, int components, opj_cparameters_t *params) {
if (params->cp_cinema == OPJ_CINEMA4K_24) {
float max_rate =
- ((float)(components * im->xsize * im->ysize * 8) /
- (CINEMA_24_CS_LENGTH * 8));
+ ((float)(components * im->xsize * im->ysize * 8) / (CINEMA_24_CS_LENGTH * 8)
+ );
params->POC[0].tile = 1;
params->POC[0].resno0 = 0;
@@ -241,8 +243,8 @@ j2k_set_cinema_params(Imaging im, int components, opj_cparameters_t *params) {
params->max_comp_size = COMP_24_CS_MAX_LENGTH;
} else {
float max_rate =
- ((float)(components * im->xsize * im->ysize * 8) /
- (CINEMA_48_CS_LENGTH * 8));
+ ((float)(components * im->xsize * im->ysize * 8) / (CINEMA_48_CS_LENGTH * 8)
+ );
for (n = 0; n < params->tcp_numlayers; ++n) {
rate = 0;
@@ -650,10 +652,3 @@ ImagingJpeg2KEncodeCleanup(ImagingCodecState state) {
}
#endif /* HAVE_OPENJPEG */
-
-/*
- * Local Variables:
- * c-basic-offset: 4
- * End:
- *
- */
diff --git a/src/libImaging/JpegDecode.c b/src/libImaging/JpegDecode.c
index 6f75d8670..2970f56d1 100644
--- a/src/libImaging/JpegDecode.c
+++ b/src/libImaging/JpegDecode.c
@@ -48,7 +48,7 @@ char *libjpeg_turbo_version = NULL;
#endif
int
-ImagingJpegUseJCSExtensions() {
+ImagingJpegUseJCSExtensions(void) {
int use_jcs_extensions = 0;
#ifdef JCS_EXTENSIONS
#if defined(LIBJPEG_TURBO_VERSION_NUMBER)
@@ -206,9 +206,8 @@ ImagingJpegDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t by
context->cinfo.out_color_space = JCS_EXT_RGBX;
}
#endif
- else if (
- strcmp(context->rawmode, "CMYK") == 0 ||
- strcmp(context->rawmode, "CMYK;I") == 0) {
+ else if (strcmp(context->rawmode, "CMYK") == 0 ||
+ strcmp(context->rawmode, "CMYK;I") == 0) {
context->cinfo.out_color_space = JCS_CMYK;
} else if (strcmp(context->rawmode, "YCbCr") == 0) {
context->cinfo.out_color_space = JCS_YCbCr;
@@ -256,7 +255,8 @@ ImagingJpegDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t by
(UINT8 *)im->image[state->y + state->yoff] +
state->xoff * im->pixelsize,
state->buffer,
- state->xsize);
+ state->xsize
+ );
state->y++;
}
if (ok != 1) {
diff --git a/src/libImaging/JpegEncode.c b/src/libImaging/JpegEncode.c
index ba8353c2d..4372d51d5 100644
--- a/src/libImaging/JpegEncode.c
+++ b/src/libImaging/JpegEncode.c
@@ -175,7 +175,8 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
i,
&context->qtables[i * DCTSIZE2],
quality,
- FALSE);
+ FALSE
+ );
context->cinfo.comp_info[i].quant_tbl_no = i;
last_q = i;
}
@@ -183,7 +184,8 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
// jpeg_set_defaults created two qtables internally, but we only
// wanted one.
jpeg_add_quant_table(
- &context->cinfo, 1, &context->qtables[0], quality, FALSE);
+ &context->cinfo, 1, &context->qtables[0], quality, FALSE
+ );
}
for (i = last_q; i < context->cinfo.num_components; i++) {
context->cinfo.comp_info[i].quant_tbl_no = last_q;
@@ -273,7 +275,8 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
&context->cinfo,
JPEG_APP0 + 1,
(unsigned char *)context->rawExif,
- context->rawExifLen);
+ context->rawExifLen
+ );
}
state->state++;
@@ -289,7 +292,8 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
memcpy(
context->destination.pub.next_output_byte,
context->extra + context->extra_offset,
- n);
+ n
+ );
context->destination.pub.next_output_byte += n;
context->destination.pub.free_in_buffer -= n;
context->extra_offset += n;
@@ -309,7 +313,8 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
&context->cinfo,
JPEG_COM,
(unsigned char *)context->comment,
- context->comment_size);
+ context->comment_size
+ );
}
state->state++;
@@ -324,7 +329,8 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
state->buffer,
(UINT8 *)im->image[state->y + state->yoff] +
state->xoff * im->pixelsize,
- state->xsize);
+ state->xsize
+ );
ok = jpeg_write_scanlines(&context->cinfo, &state->buffer, 1);
if (ok != 1) {
break;
diff --git a/src/libImaging/Pack.c b/src/libImaging/Pack.c
index f3b714215..c29473d90 100644
--- a/src/libImaging/Pack.c
+++ b/src/libImaging/Pack.c
@@ -258,16 +258,6 @@ void
ImagingPackRGB(UINT8 *out, const UINT8 *in, int pixels) {
int i = 0;
/* RGB triplets */
-#ifdef __sparc
- /* SPARC CPUs cannot read integers from nonaligned addresses. */
- for (; i < pixels; i++) {
- out[0] = in[R];
- out[1] = in[G];
- out[2] = in[B];
- out += 3;
- in += 4;
- }
-#else
for (; i < pixels - 1; i++) {
memcpy(out, in + i * 4, 4);
out += 3;
@@ -278,7 +268,6 @@ ImagingPackRGB(UINT8 *out, const UINT8 *in, int pixels) {
out[2] = in[i * 4 + B];
out += 3;
}
-#endif
}
void
diff --git a/src/libImaging/PackDecode.c b/src/libImaging/PackDecode.c
index 7dd432b91..52f1ac502 100644
--- a/src/libImaging/PackDecode.c
+++ b/src/libImaging/PackDecode.c
@@ -17,7 +17,8 @@
int
ImagingPackbitsDecode(
- Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t bytes) {
+ Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t bytes
+) {
UINT8 n;
UINT8 *ptr;
int i;
@@ -79,7 +80,8 @@ ImagingPackbitsDecode(
(UINT8 *)im->image[state->y + state->yoff] +
state->xoff * im->pixelsize,
state->buffer,
- state->xsize);
+ state->xsize
+ );
state->x = 0;
diff --git a/src/libImaging/Paste.c b/src/libImaging/Paste.c
index a018225b2..86085942a 100644
--- a/src/libImaging/Paste.c
+++ b/src/libImaging/Paste.c
@@ -33,7 +33,8 @@ paste(
int sy,
int xsize,
int ysize,
- int pixelsize) {
+ int pixelsize
+) {
/* paste opaque region */
int y;
@@ -59,21 +60,39 @@ paste_mask_1(
int sy,
int xsize,
int ysize,
- int pixelsize) {
+ int pixelsize
+) {
/* paste with mode "1" mask */
int x, y;
if (imOut->image8) {
+ int in_i16 = strncmp(imIn->mode, "I;16", 4) == 0;
+ int out_i16 = strncmp(imOut->mode, "I;16", 4) == 0;
for (y = 0; y < ysize; y++) {
UINT8 *out = imOut->image8[y + dy] + dx;
+ if (out_i16) {
+ out += dx;
+ }
UINT8 *in = imIn->image8[y + sy] + sx;
+ if (in_i16) {
+ in += sx;
+ }
UINT8 *mask = imMask->image8[y + sy] + sx;
for (x = 0; x < xsize; x++) {
- if (*mask++) {
+ if (*mask) {
*out = *in;
}
- out++, in++;
+ if (in_i16) {
+ in++;
+ }
+ if (out_i16) {
+ out++;
+ if (*mask) {
+ *out = *in;
+ }
+ }
+ out++, in++, mask++;
}
}
@@ -103,7 +122,8 @@ paste_mask_L(
int sy,
int xsize,
int ysize,
- int pixelsize) {
+ int pixelsize
+) {
/* paste with mode "L" matte */
int x, y;
@@ -150,7 +170,8 @@ paste_mask_RGBA(
int sy,
int xsize,
int ysize,
- int pixelsize) {
+ int pixelsize
+) {
/* paste with mode "RGBA" matte */
int x, y;
@@ -197,7 +218,8 @@ paste_mask_RGBa(
int sy,
int xsize,
int ysize,
- int pixelsize) {
+ int pixelsize
+) {
/* paste with mode "RGBa" matte */
int x, y;
@@ -235,7 +257,8 @@ paste_mask_RGBa(
int
ImagingPaste(
- Imaging imOut, Imaging imIn, Imaging imMask, int dx0, int dy0, int dx1, int dy1) {
+ Imaging imOut, Imaging imIn, Imaging imMask, int dx0, int dy0, int dx1, int dy1
+) {
int xsize, ysize;
int pixelsize;
int sx0, sy0;
@@ -298,13 +321,15 @@ ImagingPaste(
} else if (strcmp(imMask->mode, "LA") == 0 || strcmp(imMask->mode, "RGBA") == 0) {
ImagingSectionEnter(&cookie);
paste_mask_RGBA(
- imOut, imIn, imMask, dx0, dy0, sx0, sy0, xsize, ysize, pixelsize);
+ imOut, imIn, imMask, dx0, dy0, sx0, sy0, xsize, ysize, pixelsize
+ );
ImagingSectionLeave(&cookie);
} else if (strcmp(imMask->mode, "RGBa") == 0) {
ImagingSectionEnter(&cookie);
paste_mask_RGBa(
- imOut, imIn, imMask, dx0, dy0, sx0, sy0, xsize, ysize, pixelsize);
+ imOut, imIn, imMask, dx0, dy0, sx0, sy0, xsize, ysize, pixelsize
+ );
ImagingSectionLeave(&cookie);
} else {
@@ -317,13 +342,8 @@ ImagingPaste(
static inline void
fill(
- Imaging imOut,
- const void *ink_,
- int dx,
- int dy,
- int xsize,
- int ysize,
- int pixelsize) {
+ Imaging imOut, const void *ink_, int dx, int dy, int xsize, int ysize, int pixelsize
+) {
/* fill opaque region */
int x, y;
@@ -361,7 +381,8 @@ fill_mask_1(
int sy,
int xsize,
int ysize,
- int pixelsize) {
+ int pixelsize
+) {
/* fill with mode "1" mask */
int x, y;
@@ -408,22 +429,24 @@ fill_mask_L(
int sy,
int xsize,
int ysize,
- int pixelsize) {
+ int pixelsize
+) {
/* fill with mode "L" matte */
int x, y, i;
unsigned int tmp1;
if (imOut->image8) {
+ int i16 = strncmp(imOut->mode, "I;16", 4) == 0;
for (y = 0; y < ysize; y++) {
UINT8 *out = imOut->image8[y + dy] + dx;
- if (strncmp(imOut->mode, "I;16", 4) == 0) {
+ if (i16) {
out += dx;
}
UINT8 *mask = imMask->image8[y + sy] + sx;
for (x = 0; x < xsize; x++) {
*out = BLEND(*mask, *out, ink[0], tmp1);
- if (strncmp(imOut->mode, "I;16", 4) == 0) {
+ if (i16) {
out++;
*out = BLEND(*mask, *out, ink[1], tmp1);
}
@@ -466,7 +489,8 @@ fill_mask_RGBA(
int sy,
int xsize,
int ysize,
- int pixelsize) {
+ int pixelsize
+) {
/* fill with mode "RGBA" matte */
int x, y, i;
@@ -511,7 +535,8 @@ fill_mask_RGBa(
int sy,
int xsize,
int ysize,
- int pixelsize) {
+ int pixelsize
+) {
/* fill with mode "RGBa" matte */
int x, y, i;
@@ -547,13 +572,8 @@ fill_mask_RGBa(
int
ImagingFill2(
- Imaging imOut,
- const void *ink,
- Imaging imMask,
- int dx0,
- int dy0,
- int dx1,
- int dy1) {
+ Imaging imOut, const void *ink, Imaging imMask, int dx0, int dy0, int dx1, int dy1
+) {
ImagingSectionCookie cookie;
int xsize, ysize;
int pixelsize;
diff --git a/src/libImaging/PcxDecode.c b/src/libImaging/PcxDecode.c
index c95ffc869..942c8dc22 100644
--- a/src/libImaging/PcxDecode.c
+++ b/src/libImaging/PcxDecode.c
@@ -68,7 +68,8 @@ ImagingPcxDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t byt
memmove(
&state->buffer[i * state->xsize],
&state->buffer[i * stride],
- state->xsize);
+ state->xsize
+ );
}
}
/* Got a full line, unpack it */
@@ -76,7 +77,8 @@ ImagingPcxDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t byt
(UINT8 *)im->image[state->y + state->yoff] +
state->xoff * im->pixelsize,
state->buffer,
- state->xsize);
+ state->xsize
+ );
state->x = 0;
diff --git a/src/libImaging/PcxEncode.c b/src/libImaging/PcxEncode.c
index 549614bfd..625cf7ffa 100644
--- a/src/libImaging/PcxEncode.c
+++ b/src/libImaging/PcxEncode.c
@@ -71,7 +71,8 @@ ImagingPcxEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
state->buffer,
(UINT8 *)im->image[state->y + state->yoff] +
state->xoff * im->pixelsize,
- state->xsize);
+ state->xsize
+ );
state->y += 1;
diff --git a/src/libImaging/Point.c b/src/libImaging/Point.c
index dd06f3940..6a4060b4b 100644
--- a/src/libImaging/Point.c
+++ b/src/libImaging/Point.c
@@ -197,8 +197,8 @@ ImagingPoint(Imaging imIn, const char *mode, const void *table) {
return imOut;
mode_mismatch:
- return (Imaging)ImagingError_ValueError(
- "point operation not supported for this mode");
+ return (Imaging
+ )ImagingError_ValueError("point operation not supported for this mode");
}
Imaging
diff --git a/src/libImaging/Quant.c b/src/libImaging/Quant.c
index cdc614536..a489a882d 100644
--- a/src/libImaging/Quant.c
+++ b/src/libImaging/Quant.c
@@ -36,7 +36,8 @@
#define UINT32_MAX 0xffffffff
#endif
-#define NO_OUTPUT
+// #define DEBUG
+// #define TEST_NEAREST_NEIGHBOUR
typedef struct {
uint32_t scale;
@@ -103,7 +104,8 @@ static uint32_t
pixel_hash(const HashTable *h, const Pixel pixel) {
PixelHashData *d = (PixelHashData *)hashtable_get_user_data(h);
return PIXEL_HASH(
- pixel.c.r >> d->scale, pixel.c.g >> d->scale, pixel.c.b >> d->scale);
+ pixel.c.r >> d->scale, pixel.c.g >> d->scale, pixel.c.b >> d->scale
+ );
}
static int
@@ -111,9 +113,11 @@ pixel_cmp(const HashTable *h, const Pixel pixel1, const Pixel pixel2) {
PixelHashData *d = (PixelHashData *)hashtable_get_user_data(h);
uint32_t A, B;
A = PIXEL_HASH(
- pixel1.c.r >> d->scale, pixel1.c.g >> d->scale, pixel1.c.b >> d->scale);
+ pixel1.c.r >> d->scale, pixel1.c.g >> d->scale, pixel1.c.b >> d->scale
+ );
B = PIXEL_HASH(
- pixel2.c.r >> d->scale, pixel2.c.g >> d->scale, pixel2.c.b >> d->scale);
+ pixel2.c.r >> d->scale, pixel2.c.g >> d->scale, pixel2.c.b >> d->scale
+ );
return (A == B) ? 0 : ((A < B) ? -1 : 1);
}
@@ -129,7 +133,8 @@ new_count_func(const HashTable *h, const Pixel key, uint32_t *val) {
static void
rehash_collide(
- const HashTable *h, Pixel *keyp, uint32_t *valp, Pixel newkey, uint32_t newval) {
+ const HashTable *h, Pixel *keyp, uint32_t *valp, Pixel newkey, uint32_t newval
+) {
*valp += newval;
}
@@ -140,7 +145,7 @@ create_pixel_hash(Pixel *pixelData, uint32_t nPixels) {
PixelHashData *d;
HashTable *hash;
uint32_t i;
-#ifndef NO_OUTPUT
+#ifdef DEBUG
uint32_t timer, timer2, timer3;
#endif
@@ -152,32 +157,33 @@ create_pixel_hash(Pixel *pixelData, uint32_t nPixels) {
hash = hashtable_new(pixel_hash, pixel_cmp);
hashtable_set_user_data(hash, d);
d->scale = 0;
-#ifndef NO_OUTPUT
+#ifdef DEBUG
timer = timer3 = clock();
#endif
for (i = 0; i < nPixels; i++) {
if (!hashtable_insert_or_update_computed(
- hash, pixelData[i], new_count_func, exists_count_func)) {
+ hash, pixelData[i], new_count_func, exists_count_func
+ )) {
;
}
while (hashtable_get_count(hash) > MAX_HASH_ENTRIES) {
d->scale++;
-#ifndef NO_OUTPUT
+#ifdef DEBUG
printf("rehashing - new scale: %d\n", (int)d->scale);
timer2 = clock();
#endif
hashtable_rehash_compute(hash, rehash_collide);
-#ifndef NO_OUTPUT
+#ifdef DEBUG
timer2 = clock() - timer2;
printf("rehash took %f sec\n", timer2 / (double)CLOCKS_PER_SEC);
timer += timer2;
#endif
}
}
-#ifndef NO_OUTPUT
+#ifdef DEBUG
printf("inserts took %f sec\n", (clock() - timer) / (double)CLOCKS_PER_SEC);
#endif
-#ifndef NO_OUTPUT
+#ifdef DEBUG
printf("total %f sec\n", (clock() - timer3) / (double)CLOCKS_PER_SEC);
#endif
return hash;
@@ -299,18 +305,18 @@ mergesort_pixels(PixelList *head, int i) {
return head;
}
-#if defined(TEST_MERGESORT) || defined(TEST_SORTED)
+#ifdef DEBUG
static int
test_sorted(PixelList *pl[3]) {
- int i, n, l;
+ int i, l;
PixelList *t;
for (i = 0; i < 3; i++) {
- n = 0;
l = 256;
for (t = pl[i]; t; t = t->next[i]) {
- if (l < t->p.a.v[i])
+ if (l < t->p.a.v[i]) {
return 0;
+ }
l = t->p.a.v[i];
}
}
@@ -335,18 +341,19 @@ splitlists(
PixelList *nt[2][3],
uint32_t nCount[2],
int axis,
- uint32_t pixelCount) {
+ uint32_t pixelCount
+) {
uint32_t left;
PixelList *l, *r, *c, *n;
int i;
int nRight;
-#ifndef NO_OUTPUT
+#ifdef DEBUG
int nLeft;
#endif
int splitColourVal;
-#ifdef TEST_SPLIT
+#ifdef DEBUG
{
PixelList *_prevTest, *_nextTest;
int _i, _nextCount[3], _prevCount[3];
@@ -387,7 +394,8 @@ splitlists(
_prevCount[2],
_nextCount[0],
_nextCount[1],
- _nextCount[2]);
+ _nextCount[2]
+ );
exit(1);
}
}
@@ -395,14 +403,14 @@ splitlists(
#endif
nCount[0] = nCount[1] = 0;
nRight = 0;
-#ifndef NO_OUTPUT
+#ifdef DEBUG
nLeft = 0;
#endif
for (left = 0, c = h[axis]; c;) {
left = left + c->count;
nCount[0] += c->count;
c->flag = 0;
-#ifndef NO_OUTPUT
+#ifdef DEBUG
nLeft++;
#endif
c = c->next[axis];
@@ -417,7 +425,7 @@ splitlists(
break;
}
c->flag = 0;
-#ifndef NO_OUTPUT
+#ifdef DEBUG
nLeft++;
#endif
nCount[0] += c->count;
@@ -435,14 +443,14 @@ splitlists(
}
c->flag = 1;
nRight++;
-#ifndef NO_OUTPUT
+#ifdef DEBUG
nLeft--;
#endif
nCount[0] -= c->count;
nCount[1] += c->count;
}
}
-#ifndef NO_OUTPUT
+#ifdef DEBUG
if (!nLeft) {
for (c = h[axis]; c; c = c->next[axis]) {
printf("[%d %d %d]\n", c->p.c.r, c->p.c.g, c->p.c.b);
@@ -504,7 +512,7 @@ split(BoxNode *node) {
gl = node->tail[1]->p.c.g;
bh = node->head[2]->p.c.b;
bl = node->tail[2]->p.c.b;
-#ifdef TEST_SPLIT
+#ifdef DEBUG
printf("splitting node [%d %d %d] [%d %d %d] ", rl, gl, bl, rh, gh, bh);
#endif
f[0] = (rh - rl) * 77;
@@ -519,11 +527,8 @@ split(BoxNode *node) {
axis = i;
}
}
-#ifdef TEST_SPLIT
+#ifdef DEBUG
printf("along axis %d\n", axis + 1);
-#endif
-
-#ifdef TEST_SPLIT
{
PixelList *_prevTest, *_nextTest;
int _i, _nextCount[3], _prevCount[3];
@@ -531,12 +536,14 @@ split(BoxNode *node) {
if (node->tail[_i]->next[_i]) {
printf("tail is not tail\n");
printf(
- "node->tail[%d]->next[%d]=%p\n", _i, _i, node->tail[_i]->next[_i]);
+ "node->tail[%d]->next[%d]=%p\n", _i, _i, node->tail[_i]->next[_i]
+ );
}
if (node->head[_i]->prev[_i]) {
printf("head is not head\n");
printf(
- "node->head[%d]->prev[%d]=%p\n", _i, _i, node->head[_i]->prev[_i]);
+ "node->head[%d]->prev[%d]=%p\n", _i, _i, node->head[_i]->prev[_i]
+ );
}
}
@@ -573,20 +580,22 @@ split(BoxNode *node) {
_prevCount[2],
_nextCount[0],
_nextCount[1],
- _nextCount[2]);
+ _nextCount[2]
+ );
}
}
}
#endif
node->axis = axis;
if (!splitlists(
- node->head, node->tail, heads, tails, newCounts, axis, node->pixelCount)) {
-#ifndef NO_OUTPUT
+ node->head, node->tail, heads, tails, newCounts, axis, node->pixelCount
+ )) {
+#ifdef DEBUG
printf("list split failed.\n");
#endif
return 0;
}
-#ifdef TEST_SPLIT
+#ifdef DEBUG
if (!test_sorted(heads[0])) {
printf("bug in split");
exit(1);
@@ -612,7 +621,7 @@ split(BoxNode *node) {
node->head[i] = NULL;
node->tail[i] = NULL;
}
-#ifdef TEST_SPLIT
+#ifdef DEBUG
if (left->head[0]) {
rh = left->head[0]->p.c.r;
rl = left->tail[0]->p.c.r;
@@ -676,7 +685,7 @@ median_cut(PixelList *hl[3], uint32_t imPixelCount, int nPixels) {
}
} while (compute_box_volume(thisNode) == 1);
if (!split(thisNode)) {
-#ifndef NO_OUTPUT
+#ifdef DEBUG
printf("Oops, split failed...\n");
#endif
exit(1);
@@ -705,16 +714,14 @@ free_box_tree(BoxNode *n) {
free(n);
}
-#ifdef TEST_SPLIT_INTEGRITY
+#ifdef DEBUG
static int
checkContained(BoxNode *n, Pixel *pp) {
if (n->l && n->r) {
return checkContained(n->l, pp) + checkContained(n->r, pp);
}
if (n->l || n->r) {
-#ifndef NO_OUTPUT
printf("box tree is dead\n");
-#endif
return 0;
}
if (pp->c.r <= n->head[0]->p.c.r && pp->c.r >= n->tail[0]->p.c.r &&
@@ -735,7 +742,7 @@ annotate_hash_table(BoxNode *n, HashTable *h, uint32_t *box) {
return annotate_hash_table(n->l, h, box) && annotate_hash_table(n->r, h, box);
}
if (n->l || n->r) {
-#ifndef NO_OUTPUT
+#ifdef DEBUG
printf("box tree is dead\n");
#endif
return 0;
@@ -743,7 +750,7 @@ annotate_hash_table(BoxNode *n, HashTable *h, uint32_t *box) {
for (p = n->head[0]; p; p = p->next[0]) {
PIXEL_UNSCALE(&(p->p), &q, d->scale);
if (!hashtable_insert(h, q, *box)) {
-#ifndef NO_OUTPUT
+#ifdef DEBUG
printf("hashtable insert failed\n");
#endif
return 0;
@@ -772,7 +779,8 @@ _distance_index_cmp(const void *a, const void *b) {
static int
resort_distance_tables(
- uint32_t *avgDist, uint32_t **avgDistSortKey, Pixel *p, uint32_t nEntries) {
+ uint32_t *avgDist, uint32_t **avgDistSortKey, Pixel *p, uint32_t nEntries
+) {
uint32_t i, j, k;
uint32_t **skRow;
uint32_t *skElt;
@@ -801,7 +809,8 @@ resort_distance_tables(
static int
build_distance_tables(
- uint32_t *avgDist, uint32_t **avgDistSortKey, Pixel *p, uint32_t nEntries) {
+ uint32_t *avgDist, uint32_t **avgDistSortKey, Pixel *p, uint32_t nEntries
+) {
uint32_t i, j;
DistanceWithIndex *dwi;
@@ -841,7 +850,8 @@ map_image_pixels(
uint32_t nPaletteEntries,
uint32_t *avgDist,
uint32_t **avgDistSortKey,
- uint32_t *pixelArray) {
+ uint32_t *pixelArray
+) {
uint32_t *aD, **aDSK;
uint32_t idx;
uint32_t i, j;
@@ -888,7 +898,8 @@ map_image_pixels_from_quantized_pixels(
uint32_t **avgDistSortKey,
uint32_t *pixelArray,
uint32_t *avg[3],
- uint32_t *count) {
+ uint32_t *count
+) {
uint32_t *aD, **aDSK;
uint32_t idx;
uint32_t i, j;
@@ -946,7 +957,8 @@ map_image_pixels_from_median_box(
HashTable *medianBoxHash,
uint32_t *avgDist,
uint32_t **avgDistSortKey,
- uint32_t *pixelArray) {
+ uint32_t *pixelArray
+) {
uint32_t *aD, **aDSK;
uint32_t idx;
uint32_t i, j;
@@ -962,7 +974,7 @@ map_image_pixels_from_median_box(
continue;
}
if (!hashtable_lookup(medianBoxHash, pixelData[i], &pixelVal)) {
-#ifndef NO_OUTPUT
+#ifdef DEBUG
printf("pixel lookup failed\n");
#endif
return 0;
@@ -998,7 +1010,9 @@ compute_palette_from_median_cut(
uint32_t nPixels,
HashTable *medianBoxHash,
Pixel **palette,
- uint32_t nPaletteEntries) {
+ uint32_t nPaletteEntries,
+ BoxNode *root
+) {
uint32_t i;
uint32_t paletteEntry;
Pixel *p;
@@ -1026,7 +1040,7 @@ compute_palette_from_median_cut(
}
}
for (i = 0; i < nPixels; i++) {
-#ifdef TEST_SPLIT_INTEGRITY
+#ifdef DEBUG
if (!(i % 100)) {
printf("%05d\r", i);
fflush(stdout);
@@ -1041,7 +1055,7 @@ compute_palette_from_median_cut(
}
#endif
if (!hashtable_lookup(medianBoxHash, pixelData[i], &paletteEntry)) {
-#ifndef NO_OUTPUT
+#ifdef DEBUG
printf("pixel lookup failed\n");
#endif
for (i = 0; i < 3; i++) {
@@ -1051,11 +1065,12 @@ compute_palette_from_median_cut(
return 0;
}
if (paletteEntry >= nPaletteEntries) {
-#ifndef NO_OUTPUT
+#ifdef DEBUG
printf(
"panic - paletteEntry>=nPaletteEntries (%d>=%d)\n",
(int)paletteEntry,
- (int)nPaletteEntries);
+ (int)nPaletteEntries
+ );
#endif
for (i = 0; i < 3; i++) {
free(avg[i]);
@@ -1092,7 +1107,8 @@ compute_palette_from_median_cut(
static int
recompute_palette_from_averages(
- Pixel *palette, uint32_t nPaletteEntries, uint32_t *avg[3], uint32_t *count) {
+ Pixel *palette, uint32_t nPaletteEntries, uint32_t *avg[3], uint32_t *count
+) {
uint32_t i;
for (i = 0; i < nPaletteEntries; i++) {
@@ -1111,7 +1127,8 @@ compute_palette_from_quantized_pixels(
uint32_t nPaletteEntries,
uint32_t *avg[3],
uint32_t *count,
- uint32_t *qp) {
+ uint32_t *qp
+) {
uint32_t i;
memset(count, 0, sizeof(uint32_t) * nPaletteEntries);
@@ -1120,7 +1137,7 @@ compute_palette_from_quantized_pixels(
}
for (i = 0; i < nPixels; i++) {
if (qp[i] >= nPaletteEntries) {
-#ifndef NO_OUTPUT
+#ifdef DEBUG
printf("scream\n");
#endif
return 0;
@@ -1145,7 +1162,8 @@ k_means(
Pixel *paletteData,
uint32_t nPaletteEntries,
uint32_t *qp,
- int threshold) {
+ int threshold
+) {
uint32_t *avg[3];
uint32_t *count;
uint32_t i;
@@ -1187,23 +1205,26 @@ k_means(
goto error_2;
}
-#ifndef NO_OUTPUT
+#ifdef DEBUG
printf("[");
fflush(stdout);
#endif
while (1) {
if (!built) {
compute_palette_from_quantized_pixels(
- pixelData, nPixels, paletteData, nPaletteEntries, avg, count, qp);
+ pixelData, nPixels, paletteData, nPaletteEntries, avg, count, qp
+ );
if (!build_distance_tables(
- avgDist, avgDistSortKey, paletteData, nPaletteEntries)) {
+ avgDist, avgDistSortKey, paletteData, nPaletteEntries
+ )) {
goto error_3;
}
built = 1;
} else {
recompute_palette_from_averages(paletteData, nPaletteEntries, avg, count);
resort_distance_tables(
- avgDist, avgDistSortKey, paletteData, nPaletteEntries);
+ avgDist, avgDistSortKey, paletteData, nPaletteEntries
+ );
}
changes = map_image_pixels_from_quantized_pixels(
pixelData,
@@ -1214,11 +1235,12 @@ k_means(
avgDistSortKey,
qp,
avg,
- count);
+ count
+ );
if (changes < 0) {
goto error_3;
}
-#ifndef NO_OUTPUT
+#ifdef DEBUG
printf(".(%d)", changes);
fflush(stdout);
#endif
@@ -1226,7 +1248,7 @@ k_means(
break;
}
}
-#ifndef NO_OUTPUT
+#ifdef DEBUG
printf("]\n");
#endif
if (avgDistSortKey) {
@@ -1273,7 +1295,8 @@ quantize(
Pixel **palette,
uint32_t *paletteLength,
uint32_t **quantizedPixels,
- int kmeans) {
+ int kmeans
+) {
PixelList *hl[3];
HashTable *h;
BoxNode *root;
@@ -1285,32 +1308,32 @@ quantize(
uint32_t **avgDistSortKey;
Pixel *p;
-#ifndef NO_OUTPUT
+#ifdef DEBUG
uint32_t timer, timer2;
#endif
-#ifndef NO_OUTPUT
+#ifdef DEBUG
timer2 = clock();
printf("create hash table...");
fflush(stdout);
timer = clock();
#endif
h = create_pixel_hash(pixelData, nPixels);
-#ifndef NO_OUTPUT
+#ifdef DEBUG
printf("done (%f)\n", (clock() - timer) / (double)CLOCKS_PER_SEC);
#endif
if (!h) {
goto error_0;
}
-#ifndef NO_OUTPUT
+#ifdef DEBUG
printf("create lists from hash table...");
fflush(stdout);
timer = clock();
#endif
hl[0] = hl[1] = hl[2] = NULL;
hashtable_foreach(h, hash_to_list, hl);
-#ifndef NO_OUTPUT
+#ifdef DEBUG
printf("done (%f)\n", (clock() - timer) / (double)CLOCKS_PER_SEC);
#endif
@@ -1318,7 +1341,7 @@ quantize(
goto error_1;
}
-#ifndef NO_OUTPUT
+#ifdef DEBUG
printf("mergesort lists...");
fflush(stdout);
timer = clock();
@@ -1326,47 +1349,47 @@ quantize(
for (i = 0; i < 3; i++) {
hl[i] = mergesort_pixels(hl[i], i);
}
-#ifdef TEST_MERGESORT
+#ifdef DEBUG
if (!test_sorted(hl)) {
printf("bug in mergesort\n");
goto error_1;
}
-#endif
-#ifndef NO_OUTPUT
printf("done (%f)\n", (clock() - timer) / (double)CLOCKS_PER_SEC);
#endif
-#ifndef NO_OUTPUT
+#ifdef DEBUG
printf("median cut...");
fflush(stdout);
timer = clock();
#endif
root = median_cut(hl, nPixels, nQuantPixels);
-#ifndef NO_OUTPUT
+#ifdef DEBUG
printf("done (%f)\n", (clock() - timer) / (double)CLOCKS_PER_SEC);
#endif
if (!root) {
goto error_1;
}
nPaletteEntries = 0;
-#ifndef NO_OUTPUT
+#ifdef DEBUG
printf("median cut tree to hash table...");
fflush(stdout);
timer = clock();
#endif
annotate_hash_table(root, h, &nPaletteEntries);
-#ifndef NO_OUTPUT
+#ifdef DEBUG
printf("done (%f)\n", (clock() - timer) / (double)CLOCKS_PER_SEC);
#endif
-#ifndef NO_OUTPUT
+#ifdef DEBUG
printf("compute palette...\n");
fflush(stdout);
timer = clock();
#endif
- if (!compute_palette_from_median_cut(pixelData, nPixels, h, &p, nPaletteEntries)) {
+ if (!compute_palette_from_median_cut(
+ pixelData, nPixels, h, &p, nPaletteEntries, root
+ )) {
goto error_3;
}
-#ifndef NO_OUTPUT
+#ifdef DEBUG
printf("done (%f)\n", (clock() - timer) / (double)CLOCKS_PER_SEC);
#endif
@@ -1399,7 +1422,8 @@ quantize(
}
if (!map_image_pixels_from_median_box(
- pixelData, nPixels, p, nPaletteEntries, h, avgDist, avgDistSortKey, qp)) {
+ pixelData, nPixels, p, nPaletteEntries, h, avgDist, avgDistSortKey, qp
+ )) {
goto error_7;
}
@@ -1445,13 +1469,14 @@ quantize(
_SQR(pixelData[i].c.b - p[qp[i]].c.b))),
sqrt((double)(_SQR(pixelData[i].c.r - p[bestmatch].c.r) +
_SQR(pixelData[i].c.g - p[bestmatch].c.g) +
- _SQR(pixelData[i].c.b - p[bestmatch].c.b))));
+ _SQR(pixelData[i].c.b - p[bestmatch].c.b)))
+ );
}
}
hashtable_free(h2);
}
#endif
-#ifndef NO_OUTPUT
+#ifdef DEBUG
printf("k means...\n");
fflush(stdout);
timer = clock();
@@ -1459,7 +1484,7 @@ quantize(
if (kmeans > 0) {
k_means(pixelData, nPixels, p, nPaletteEntries, qp, kmeans - 1);
}
-#ifndef NO_OUTPUT
+#ifdef DEBUG
printf("done (%f)\n", (clock() - timer) / (double)CLOCKS_PER_SEC);
#endif
@@ -1467,7 +1492,7 @@ quantize(
*palette = p;
*paletteLength = nPaletteEntries;
-#ifndef NO_OUTPUT
+#ifdef DEBUG
printf("cleanup...");
fflush(stdout);
timer = clock();
@@ -1479,7 +1504,7 @@ quantize(
free(avgDistSortKey);
}
destroy_pixel_hash(h);
-#ifndef NO_OUTPUT
+#ifdef DEBUG
printf("done (%f)\n", (clock() - timer) / (double)CLOCKS_PER_SEC);
printf("-----\ntotal time %f\n", (clock() - timer2) / (double)CLOCKS_PER_SEC);
#endif
@@ -1545,7 +1570,8 @@ quantize2(
Pixel **palette,
uint32_t *paletteLength,
uint32_t **quantizedPixels,
- int kmeans) {
+ int kmeans
+) {
HashTable *h;
uint32_t i;
uint32_t mean[3];
@@ -1609,7 +1635,8 @@ quantize2(
}
if (!map_image_pixels(
- pixelData, nPixels, p, nQuantPixels, avgDist, avgDistSortKey, qp)) {
+ pixelData, nPixels, p, nQuantPixels, avgDist, avgDistSortKey, qp
+ )) {
goto error_4;
}
if (kmeans > 0) {
@@ -1752,7 +1779,8 @@ ImagingQuantize(Imaging im, int colors, int mode, int kmeans) {
&palette,
&paletteLength,
&newData,
- kmeans);
+ kmeans
+ );
break;
case 1:
/* maximum coverage */
@@ -1763,7 +1791,8 @@ ImagingQuantize(Imaging im, int colors, int mode, int kmeans) {
&palette,
&paletteLength,
&newData,
- kmeans);
+ kmeans
+ );
break;
case 2:
result = quantize_octree(
@@ -1773,7 +1802,8 @@ ImagingQuantize(Imaging im, int colors, int mode, int kmeans) {
&palette,
&paletteLength,
&newData,
- withAlpha);
+ withAlpha
+ );
break;
case 3:
#ifdef HAVE_LIBIMAGEQUANT
@@ -1785,7 +1815,8 @@ ImagingQuantize(Imaging im, int colors, int mode, int kmeans) {
&palette,
&paletteLength,
&newData,
- withAlpha);
+ withAlpha
+ );
#else
result = -1;
#endif
@@ -1836,7 +1867,8 @@ ImagingQuantize(Imaging im, int colors, int mode, int kmeans) {
if (result == -1) {
return (Imaging)ImagingError_ValueError(
"dependency required by this method was not "
- "enabled at compile time");
+ "enabled at compile time"
+ );
}
return (Imaging)ImagingError_ValueError("quantization error");
diff --git a/src/libImaging/QuantHash.c b/src/libImaging/QuantHash.c
index ea75d6037..bf2f29fde 100644
--- a/src/libImaging/QuantHash.c
+++ b/src/libImaging/QuantHash.c
@@ -132,7 +132,8 @@ _hashtable_resize(HashTable *h) {
static int
_hashtable_insert_node(
- HashTable *h, HashNode *node, int resize, int update, CollisionFunc cf) {
+ HashTable *h, HashNode *node, int resize, int update, CollisionFunc cf
+) {
uint32_t hash = h->hashFunc(h, node->key) % h->length;
HashNode **n, *nv;
int i;
@@ -207,7 +208,8 @@ _hashtable_insert(HashTable *h, HashKey_t key, HashVal_t val, int resize, int up
int
hashtable_insert_or_update_computed(
- HashTable *h, HashKey_t key, ComputeFunc newFunc, ComputeFunc existsFunc) {
+ HashTable *h, HashKey_t key, ComputeFunc newFunc, ComputeFunc existsFunc
+) {
HashNode **n, *nv;
HashNode *t;
int i;
diff --git a/src/libImaging/QuantHash.h b/src/libImaging/QuantHash.h
index fc1a99003..0462cfd49 100644
--- a/src/libImaging/QuantHash.h
+++ b/src/libImaging/QuantHash.h
@@ -20,13 +20,12 @@ typedef uint32_t HashVal_t;
typedef uint32_t (*HashFunc)(const HashTable *, const HashKey_t);
typedef int (*HashCmpFunc)(const HashTable *, const HashKey_t, const HashKey_t);
-typedef void (*IteratorFunc)(
- const HashTable *, const HashKey_t, const HashVal_t, void *);
-typedef void (*IteratorUpdateFunc)(
- const HashTable *, const HashKey_t, HashVal_t *, void *);
+typedef void (*IteratorFunc)(const HashTable *, const HashKey_t, const HashVal_t, void *);
+typedef void (*IteratorUpdateFunc)(const HashTable *, const HashKey_t, HashVal_t *, void *);
typedef void (*ComputeFunc)(const HashTable *, const HashKey_t, HashVal_t *);
typedef void (*CollisionFunc)(
- const HashTable *, HashKey_t *, HashVal_t *, HashKey_t, HashVal_t);
+ const HashTable *, HashKey_t *, HashVal_t *, HashKey_t, HashVal_t
+);
HashTable *
hashtable_new(HashFunc hf, HashCmpFunc cf);
@@ -42,7 +41,8 @@ int
hashtable_lookup(const HashTable *h, const HashKey_t key, HashVal_t *valp);
int
hashtable_insert_or_update_computed(
- HashTable *h, HashKey_t key, ComputeFunc newFunc, ComputeFunc existsFunc);
+ HashTable *h, HashKey_t key, ComputeFunc newFunc, ComputeFunc existsFunc
+);
void *
hashtable_set_user_data(HashTable *h, void *data);
void *
diff --git a/src/libImaging/QuantOctree.c b/src/libImaging/QuantOctree.c
index 1331a30ad..ec60b214e 100644
--- a/src/libImaging/QuantOctree.c
+++ b/src/libImaging/QuantOctree.c
@@ -107,11 +107,8 @@ free_color_cube(ColorCube cube) {
static long
color_bucket_offset_pos(
- const ColorCube cube,
- unsigned int r,
- unsigned int g,
- unsigned int b,
- unsigned int a) {
+ const ColorCube cube, unsigned int r, unsigned int g, unsigned int b, unsigned int a
+) {
return r << cube->rOffset | g << cube->gOffset | b << cube->bOffset |
a << cube->aOffset;
}
@@ -191,7 +188,8 @@ create_sorted_color_palette(const ColorCube cube) {
buckets,
cube->size,
sizeof(struct _ColorBucket),
- (int (*)(void const *, void const *)) & compare_bucket_count);
+ (int (*)(void const *, void const *))&compare_bucket_count
+ );
return buckets;
}
@@ -212,7 +210,8 @@ copy_color_cube(
unsigned int rBits,
unsigned int gBits,
unsigned int bBits,
- unsigned int aBits) {
+ unsigned int aBits
+) {
unsigned int r, g, b, a;
long src_pos, dst_pos;
unsigned int src_reduce[4] = {0}, dst_reduce[4] = {0};
@@ -262,15 +261,18 @@ copy_color_cube(
r >> src_reduce[0],
g >> src_reduce[1],
b >> src_reduce[2],
- a >> src_reduce[3]);
+ a >> src_reduce[3]
+ );
dst_pos = color_bucket_offset_pos(
result,
r >> dst_reduce[0],
g >> dst_reduce[1],
b >> dst_reduce[2],
- a >> dst_reduce[3]);
+ a >> dst_reduce[3]
+ );
add_bucket_values(
- &cube->buckets[src_pos], &result->buckets[dst_pos]);
+ &cube->buckets[src_pos], &result->buckets[dst_pos]
+ );
}
}
}
@@ -328,7 +330,8 @@ combined_palette(
ColorBucket bucketsA,
unsigned long nBucketsA,
ColorBucket bucketsB,
- unsigned long nBucketsB) {
+ unsigned long nBucketsB
+) {
ColorBucket result;
if (nBucketsA > LONG_MAX - nBucketsB ||
(nBucketsA + nBucketsB) > LONG_MAX / sizeof(struct _ColorBucket)) {
@@ -366,7 +369,8 @@ map_image_pixels(
const Pixel *pixelData,
uint32_t nPixels,
const ColorCube lookupCube,
- uint32_t *pixelArray) {
+ uint32_t *pixelArray
+) {
long i;
for (i = 0; i < nPixels; i++) {
pixelArray[i] = lookup_color(lookupCube, &pixelData[i]);
@@ -384,7 +388,8 @@ quantize_octree(
Pixel **palette,
uint32_t *paletteLength,
uint32_t **quantizedPixels,
- int withAlpha) {
+ int withAlpha
+) {
ColorCube fineCube = NULL;
ColorCube coarseCube = NULL;
ColorCube lookupCube = NULL;
@@ -461,7 +466,8 @@ quantize_octree(
subtract_color_buckets(
coarseCube,
&paletteBucketsFine[nAlreadySubtracted],
- nFineColors - nAlreadySubtracted);
+ nFineColors - nAlreadySubtracted
+ );
}
/* create our palette buckets with fine and coarse combined */
@@ -470,7 +476,8 @@ quantize_octree(
goto error;
}
paletteBuckets = combined_palette(
- paletteBucketsCoarse, nCoarseColors, paletteBucketsFine, nFineColors);
+ paletteBucketsCoarse, nCoarseColors, paletteBucketsFine, nFineColors
+ );
free(paletteBucketsFine);
paletteBucketsFine = NULL;
@@ -491,7 +498,8 @@ quantize_octree(
/* expand coarse cube (64) to larger fine cube (4k). the value of each
coarse bucket is then present in the according 64 fine buckets. */
lookupCube = copy_color_cube(
- coarseLookupCube, cubeBits[0], cubeBits[1], cubeBits[2], cubeBits[3]);
+ coarseLookupCube, cubeBits[0], cubeBits[1], cubeBits[2], cubeBits[3]
+ );
if (!lookupCube) {
goto error;
}
diff --git a/src/libImaging/QuantPngQuant.c b/src/libImaging/QuantPngQuant.c
index 7a36300e4..a2258c3a2 100644
--- a/src/libImaging/QuantPngQuant.c
+++ b/src/libImaging/QuantPngQuant.c
@@ -26,7 +26,8 @@ quantize_pngquant(
Pixel **palette,
uint32_t *paletteLength,
uint32_t **quantizedPixels,
- int withAlpha) {
+ int withAlpha
+) {
int result = 0;
liq_image *image = NULL;
liq_attr *attr = NULL;
diff --git a/src/libImaging/QuantPngQuant.h b/src/libImaging/QuantPngQuant.h
index d65e42590..ae96a52f3 100644
--- a/src/libImaging/QuantPngQuant.h
+++ b/src/libImaging/QuantPngQuant.h
@@ -12,6 +12,7 @@ quantize_pngquant(
Pixel **,
uint32_t *,
uint32_t **,
- int);
+ int
+);
#endif
diff --git a/src/libImaging/RankFilter.c b/src/libImaging/RankFilter.c
index 73a6baecb..899b1fd3a 100644
--- a/src/libImaging/RankFilter.c
+++ b/src/libImaging/RankFilter.c
@@ -102,7 +102,8 @@ MakeRankFunction(UINT8) MakeRankFunction(INT32) MakeRankFunction(FLOAT32)
memcpy( \
buf + i * size, \
&IMAGING_PIXEL_##type(im, x, y + i), \
- size * sizeof(type)); \
+ size * sizeof(type) \
+ ); \
} \
IMAGING_PIXEL_##type(imOut, x, y) = Rank##type(buf, size2, rank); \
} \
diff --git a/src/libImaging/RawDecode.c b/src/libImaging/RawDecode.c
index 24abe4804..80ed0d688 100644
--- a/src/libImaging/RawDecode.c
+++ b/src/libImaging/RawDecode.c
@@ -74,7 +74,8 @@ ImagingRawDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t byt
state->shuffle(
(UINT8 *)im->image[state->y + state->yoff] + state->xoff * im->pixelsize,
ptr,
- state->xsize);
+ state->xsize
+ );
ptr += state->bytes;
bytes -= state->bytes;
diff --git a/src/libImaging/RawEncode.c b/src/libImaging/RawEncode.c
index 50de8d982..5e60e1106 100644
--- a/src/libImaging/RawEncode.c
+++ b/src/libImaging/RawEncode.c
@@ -65,7 +65,8 @@ ImagingRawEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
state->shuffle(
ptr,
(UINT8 *)im->image[state->y + state->yoff] + state->xoff * im->pixelsize,
- state->xsize);
+ state->xsize
+ );
if (state->bytes > state->count) {
/* zero-pad the buffer, if necessary */
diff --git a/src/libImaging/Reduce.c b/src/libImaging/Reduce.c
index 61566f0c5..022daa000 100644
--- a/src/libImaging/Reduce.c
+++ b/src/libImaging/Reduce.c
@@ -82,7 +82,8 @@ ImagingReduceNxN(Imaging imOut, Imaging imIn, int box[4], int xscale, int yscale
}
}
v = MAKE_UINT32(
- (ss0 * multiplier) >> 24, 0, 0, (ss3 * multiplier) >> 24);
+ (ss0 * multiplier) >> 24, 0, 0, (ss3 * multiplier) >> 24
+ );
memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
}
} else if (imIn->bands == 3) {
@@ -124,7 +125,8 @@ ImagingReduceNxN(Imaging imOut, Imaging imIn, int box[4], int xscale, int yscale
(ss0 * multiplier) >> 24,
(ss1 * multiplier) >> 24,
(ss2 * multiplier) >> 24,
- 0);
+ 0
+ );
memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
}
} else { // bands == 4
@@ -171,7 +173,8 @@ ImagingReduceNxN(Imaging imOut, Imaging imIn, int box[4], int xscale, int yscale
(ss0 * multiplier) >> 24,
(ss1 * multiplier) >> 24,
(ss2 * multiplier) >> 24,
- (ss3 * multiplier) >> 24);
+ (ss3 * multiplier) >> 24
+ );
memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
}
}
@@ -226,7 +229,8 @@ ImagingReduce1xN(Imaging imOut, Imaging imIn, int box[4], int yscale) {
ss3 += line[xx * 4 + 3];
}
v = MAKE_UINT32(
- (ss0 * multiplier) >> 24, 0, 0, (ss3 * multiplier) >> 24);
+ (ss0 * multiplier) >> 24, 0, 0, (ss3 * multiplier) >> 24
+ );
memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
}
} else if (imIn->bands == 3) {
@@ -251,7 +255,8 @@ ImagingReduce1xN(Imaging imOut, Imaging imIn, int box[4], int yscale) {
(ss0 * multiplier) >> 24,
(ss1 * multiplier) >> 24,
(ss2 * multiplier) >> 24,
- 0);
+ 0
+ );
memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
}
} else { // bands == 4
@@ -278,7 +283,8 @@ ImagingReduce1xN(Imaging imOut, Imaging imIn, int box[4], int yscale) {
(ss0 * multiplier) >> 24,
(ss1 * multiplier) >> 24,
(ss2 * multiplier) >> 24,
- (ss3 * multiplier) >> 24);
+ (ss3 * multiplier) >> 24
+ );
memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
}
}
@@ -329,7 +335,8 @@ ImagingReduceNx1(Imaging imOut, Imaging imIn, int box[4], int xscale) {
ss3 += line[xx * 4 + 3];
}
v = MAKE_UINT32(
- (ss0 * multiplier) >> 24, 0, 0, (ss3 * multiplier) >> 24);
+ (ss0 * multiplier) >> 24, 0, 0, (ss3 * multiplier) >> 24
+ );
memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
}
} else if (imIn->bands == 3) {
@@ -351,7 +358,8 @@ ImagingReduceNx1(Imaging imOut, Imaging imIn, int box[4], int xscale) {
(ss0 * multiplier) >> 24,
(ss1 * multiplier) >> 24,
(ss2 * multiplier) >> 24,
- 0);
+ 0
+ );
memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
}
} else { // bands == 4
@@ -375,7 +383,8 @@ ImagingReduceNx1(Imaging imOut, Imaging imIn, int box[4], int xscale) {
(ss0 * multiplier) >> 24,
(ss1 * multiplier) >> 24,
(ss2 * multiplier) >> 24,
- (ss3 * multiplier) >> 24);
+ (ss3 * multiplier) >> 24
+ );
memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
}
}
@@ -425,7 +434,8 @@ ImagingReduce1x2(Imaging imOut, Imaging imIn, int box[4]) {
ss1 = line0[xx * 4 + 1] + line1[xx * 4 + 1];
ss2 = line0[xx * 4 + 2] + line1[xx * 4 + 2];
v = MAKE_UINT32(
- (ss0 + amend) >> 1, (ss1 + amend) >> 1, (ss2 + amend) >> 1, 0);
+ (ss0 + amend) >> 1, (ss1 + amend) >> 1, (ss2 + amend) >> 1, 0
+ );
memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
}
} else { // bands == 4
@@ -440,7 +450,8 @@ ImagingReduce1x2(Imaging imOut, Imaging imIn, int box[4]) {
(ss0 + amend) >> 1,
(ss1 + amend) >> 1,
(ss2 + amend) >> 1,
- (ss3 + amend) >> 1);
+ (ss3 + amend) >> 1
+ );
memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
}
}
@@ -488,7 +499,8 @@ ImagingReduce2x1(Imaging imOut, Imaging imIn, int box[4]) {
ss1 = line0[xx * 4 + 1] + line0[xx * 4 + 5];
ss2 = line0[xx * 4 + 2] + line0[xx * 4 + 6];
v = MAKE_UINT32(
- (ss0 + amend) >> 1, (ss1 + amend) >> 1, (ss2 + amend) >> 1, 0);
+ (ss0 + amend) >> 1, (ss1 + amend) >> 1, (ss2 + amend) >> 1, 0
+ );
memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
}
} else { // bands == 4
@@ -503,7 +515,8 @@ ImagingReduce2x1(Imaging imOut, Imaging imIn, int box[4]) {
(ss0 + amend) >> 1,
(ss1 + amend) >> 1,
(ss2 + amend) >> 1,
- (ss3 + amend) >> 1);
+ (ss3 + amend) >> 1
+ );
memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
}
}
@@ -558,7 +571,8 @@ ImagingReduce2x2(Imaging imOut, Imaging imIn, int box[4]) {
ss2 = line0[xx * 4 + 2] + line0[xx * 4 + 6] + line1[xx * 4 + 2] +
line1[xx * 4 + 6];
v = MAKE_UINT32(
- (ss0 + amend) >> 2, (ss1 + amend) >> 2, (ss2 + amend) >> 2, 0);
+ (ss0 + amend) >> 2, (ss1 + amend) >> 2, (ss2 + amend) >> 2, 0
+ );
memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
}
} else { // bands == 4
@@ -577,7 +591,8 @@ ImagingReduce2x2(Imaging imOut, Imaging imIn, int box[4]) {
(ss0 + amend) >> 2,
(ss1 + amend) >> 2,
(ss2 + amend) >> 2,
- (ss3 + amend) >> 2);
+ (ss3 + amend) >> 2
+ );
memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
}
}
@@ -623,7 +638,8 @@ ImagingReduce1x3(Imaging imOut, Imaging imIn, int box[4]) {
((ss0 + amend) * multiplier) >> 24,
0,
0,
- ((ss3 + amend) * multiplier) >> 24);
+ ((ss3 + amend) * multiplier) >> 24
+ );
memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
}
} else if (imIn->bands == 3) {
@@ -637,7 +653,8 @@ ImagingReduce1x3(Imaging imOut, Imaging imIn, int box[4]) {
((ss0 + amend) * multiplier) >> 24,
((ss1 + amend) * multiplier) >> 24,
((ss2 + amend) * multiplier) >> 24,
- 0);
+ 0
+ );
memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
}
} else { // bands == 4
@@ -652,7 +669,8 @@ ImagingReduce1x3(Imaging imOut, Imaging imIn, int box[4]) {
((ss0 + amend) * multiplier) >> 24,
((ss1 + amend) * multiplier) >> 24,
((ss2 + amend) * multiplier) >> 24,
- ((ss3 + amend) * multiplier) >> 24);
+ ((ss3 + amend) * multiplier) >> 24
+ );
memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
}
}
@@ -694,7 +712,8 @@ ImagingReduce3x1(Imaging imOut, Imaging imIn, int box[4]) {
((ss0 + amend) * multiplier) >> 24,
0,
0,
- ((ss3 + amend) * multiplier) >> 24);
+ ((ss3 + amend) * multiplier) >> 24
+ );
memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
}
} else if (imIn->bands == 3) {
@@ -708,7 +727,8 @@ ImagingReduce3x1(Imaging imOut, Imaging imIn, int box[4]) {
((ss0 + amend) * multiplier) >> 24,
((ss1 + amend) * multiplier) >> 24,
((ss2 + amend) * multiplier) >> 24,
- 0);
+ 0
+ );
memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
}
} else { // bands == 4
@@ -723,7 +743,8 @@ ImagingReduce3x1(Imaging imOut, Imaging imIn, int box[4]) {
((ss0 + amend) * multiplier) >> 24,
((ss1 + amend) * multiplier) >> 24,
((ss2 + amend) * multiplier) >> 24,
- ((ss3 + amend) * multiplier) >> 24);
+ ((ss3 + amend) * multiplier) >> 24
+ );
memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
}
}
@@ -775,7 +796,8 @@ ImagingReduce3x3(Imaging imOut, Imaging imIn, int box[4]) {
((ss0 + amend) * multiplier) >> 24,
0,
0,
- ((ss3 + amend) * multiplier) >> 24);
+ ((ss3 + amend) * multiplier) >> 24
+ );
memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
}
} else if (imIn->bands == 3) {
@@ -795,7 +817,8 @@ ImagingReduce3x3(Imaging imOut, Imaging imIn, int box[4]) {
((ss0 + amend) * multiplier) >> 24,
((ss1 + amend) * multiplier) >> 24,
((ss2 + amend) * multiplier) >> 24,
- 0);
+ 0
+ );
memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
}
} else { // bands == 4
@@ -818,7 +841,8 @@ ImagingReduce3x3(Imaging imOut, Imaging imIn, int box[4]) {
((ss0 + amend) * multiplier) >> 24,
((ss1 + amend) * multiplier) >> 24,
((ss2 + amend) * multiplier) >> 24,
- ((ss3 + amend) * multiplier) >> 24);
+ ((ss3 + amend) * multiplier) >> 24
+ );
memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
}
}
@@ -900,7 +924,8 @@ ImagingReduce4x4(Imaging imOut, Imaging imIn, int box[4]) {
line3[xx * 4 + 2] + line3[xx * 4 + 6] + line3[xx * 4 + 10] +
line3[xx * 4 + 14];
v = MAKE_UINT32(
- (ss0 + amend) >> 4, (ss1 + amend) >> 4, (ss2 + amend) >> 4, 0);
+ (ss0 + amend) >> 4, (ss1 + amend) >> 4, (ss2 + amend) >> 4, 0
+ );
memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
}
} else { // bands == 4
@@ -935,7 +960,8 @@ ImagingReduce4x4(Imaging imOut, Imaging imIn, int box[4]) {
(ss0 + amend) >> 4,
(ss1 + amend) >> 4,
(ss2 + amend) >> 4,
- (ss3 + amend) >> 4);
+ (ss3 + amend) >> 4
+ );
memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
}
}
@@ -1007,7 +1033,8 @@ ImagingReduce5x5(Imaging imOut, Imaging imIn, int box[4]) {
((ss0 + amend) * multiplier) >> 24,
0,
0,
- ((ss3 + amend) * multiplier) >> 24);
+ ((ss3 + amend) * multiplier) >> 24
+ );
memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
}
} else if (imIn->bands == 3) {
@@ -1045,7 +1072,8 @@ ImagingReduce5x5(Imaging imOut, Imaging imIn, int box[4]) {
((ss0 + amend) * multiplier) >> 24,
((ss1 + amend) * multiplier) >> 24,
((ss2 + amend) * multiplier) >> 24,
- 0);
+ 0
+ );
memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
}
} else { // bands == 4
@@ -1092,7 +1120,8 @@ ImagingReduce5x5(Imaging imOut, Imaging imIn, int box[4]) {
((ss0 + amend) * multiplier) >> 24,
((ss1 + amend) * multiplier) >> 24,
((ss2 + amend) * multiplier) >> 24,
- ((ss3 + amend) * multiplier) >> 24);
+ ((ss3 + amend) * multiplier) >> 24
+ );
memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
}
}
@@ -1181,7 +1210,8 @@ ImagingReduceCorners(Imaging imOut, Imaging imIn, int box[4], int xscale, int ys
(ss0 * multiplier) >> 24,
(ss1 * multiplier) >> 24,
(ss2 * multiplier) >> 24,
- (ss3 * multiplier) >> 24);
+ (ss3 * multiplier) >> 24
+ );
memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
}
}
@@ -1207,7 +1237,8 @@ ImagingReduceCorners(Imaging imOut, Imaging imIn, int box[4], int xscale, int ys
(ss0 * multiplier) >> 24,
(ss1 * multiplier) >> 24,
(ss2 * multiplier) >> 24,
- (ss3 * multiplier) >> 24);
+ (ss3 * multiplier) >> 24
+ );
memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
}
}
@@ -1232,7 +1263,8 @@ ImagingReduceCorners(Imaging imOut, Imaging imIn, int box[4], int xscale, int ys
(ss0 * multiplier) >> 24,
(ss1 * multiplier) >> 24,
(ss2 * multiplier) >> 24,
- (ss3 * multiplier) >> 24);
+ (ss3 * multiplier) >> 24
+ );
memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v));
}
}
@@ -1240,7 +1272,8 @@ ImagingReduceCorners(Imaging imOut, Imaging imIn, int box[4], int xscale, int ys
void
ImagingReduceNxN_32bpc(
- Imaging imOut, Imaging imIn, int box[4], int xscale, int yscale) {
+ Imaging imOut, Imaging imIn, int box[4], int xscale, int yscale
+) {
/* The most general implementation for any xscale and yscale
*/
int x, y, xx, yy;
@@ -1313,7 +1346,8 @@ ImagingReduceNxN_32bpc(
void
ImagingReduceCorners_32bpc(
- Imaging imOut, Imaging imIn, int box[4], int xscale, int yscale) {
+ Imaging imOut, Imaging imIn, int box[4], int xscale, int yscale
+) {
/* Fill the last row and the last column for any xscale and yscale.
*/
int x, y, xx, yy;
@@ -1427,7 +1461,8 @@ ImagingReduce(Imaging imIn, int xscale, int yscale, int box[4]) {
}
imOut = ImagingNewDirty(
- imIn->mode, (box[2] + xscale - 1) / xscale, (box[3] + yscale - 1) / yscale);
+ imIn->mode, (box[2] + xscale - 1) / xscale, (box[3] + yscale - 1) / yscale
+ );
if (!imOut) {
return NULL;
}
diff --git a/src/libImaging/Resample.c b/src/libImaging/Resample.c
index 59c27b3f4..f5e386dc2 100644
--- a/src/libImaging/Resample.c
+++ b/src/libImaging/Resample.c
@@ -186,7 +186,8 @@ precompute_coeffs(
int outSize,
struct filter *filterp,
int **boundsp,
- double **kkp) {
+ double **kkp
+) {
double support, scale, filterscale;
double center, ww, ss;
int xx, x, ksize, xmin, xmax;
@@ -284,7 +285,8 @@ normalize_coeffs_8bpc(int outSize, int ksize, double *prekk) {
void
ImagingResampleHorizontal_8bpc(
- Imaging imOut, Imaging imIn, int offset, int ksize, int *bounds, double *prekk) {
+ Imaging imOut, Imaging imIn, int offset, int ksize, int *bounds, double *prekk
+) {
ImagingSectionCookie cookie;
int ss0, ss1, ss2, ss3;
int xx, yy, x, xmin, xmax;
@@ -376,7 +378,8 @@ ImagingResampleHorizontal_8bpc(
void
ImagingResampleVertical_8bpc(
- Imaging imOut, Imaging imIn, int offset, int ksize, int *bounds, double *prekk) {
+ Imaging imOut, Imaging imIn, int offset, int ksize, int *bounds, double *prekk
+) {
ImagingSectionCookie cookie;
int ss0, ss1, ss2, ss3;
int xx, yy, y, ymin, ymax;
@@ -457,9 +460,87 @@ ImagingResampleVertical_8bpc(
ImagingSectionLeave(&cookie);
}
+void
+ImagingResampleHorizontal_16bpc(
+ Imaging imOut, Imaging imIn, int offset, int ksize, int *bounds, double *kk
+) {
+ ImagingSectionCookie cookie;
+ double ss;
+ int xx, yy, x, xmin, xmax, ss_int;
+ double *k;
+
+ int bigendian = 0;
+ if (strcmp(imIn->mode, "I;16N") == 0
+#ifdef WORDS_BIGENDIAN
+ || strcmp(imIn->mode, "I;16B") == 0
+#endif
+ ) {
+ bigendian = 1;
+ }
+
+ ImagingSectionEnter(&cookie);
+ for (yy = 0; yy < imOut->ysize; yy++) {
+ for (xx = 0; xx < imOut->xsize; xx++) {
+ xmin = bounds[xx * 2 + 0];
+ xmax = bounds[xx * 2 + 1];
+ k = &kk[xx * ksize];
+ ss = 0.0;
+ for (x = 0; x < xmax; x++) {
+ ss += (imIn->image8[yy + offset][(x + xmin) * 2 + (bigendian ? 1 : 0)] +
+ (imIn->image8[yy + offset][(x + xmin) * 2 + (bigendian ? 0 : 1)]
+ << 8)) *
+ k[x];
+ }
+ ss_int = ROUND_UP(ss);
+ imOut->image8[yy][xx * 2 + (bigendian ? 1 : 0)] = CLIP8(ss_int % 256);
+ imOut->image8[yy][xx * 2 + (bigendian ? 0 : 1)] = CLIP8(ss_int >> 8);
+ }
+ }
+ ImagingSectionLeave(&cookie);
+}
+
+void
+ImagingResampleVertical_16bpc(
+ Imaging imOut, Imaging imIn, int offset, int ksize, int *bounds, double *kk
+) {
+ ImagingSectionCookie cookie;
+ double ss;
+ int xx, yy, y, ymin, ymax, ss_int;
+ double *k;
+
+ int bigendian = 0;
+ if (strcmp(imIn->mode, "I;16N") == 0
+#ifdef WORDS_BIGENDIAN
+ || strcmp(imIn->mode, "I;16B") == 0
+#endif
+ ) {
+ bigendian = 1;
+ }
+
+ ImagingSectionEnter(&cookie);
+ for (yy = 0; yy < imOut->ysize; yy++) {
+ ymin = bounds[yy * 2 + 0];
+ ymax = bounds[yy * 2 + 1];
+ k = &kk[yy * ksize];
+ for (xx = 0; xx < imOut->xsize; xx++) {
+ ss = 0.0;
+ for (y = 0; y < ymax; y++) {
+ ss += (imIn->image8[y + ymin][xx * 2 + (bigendian ? 1 : 0)] +
+ (imIn->image8[y + ymin][xx * 2 + (bigendian ? 0 : 1)] << 8)) *
+ k[y];
+ }
+ ss_int = ROUND_UP(ss);
+ imOut->image8[yy][xx * 2 + (bigendian ? 1 : 0)] = CLIP8(ss_int % 256);
+ imOut->image8[yy][xx * 2 + (bigendian ? 0 : 1)] = CLIP8(ss_int >> 8);
+ }
+ }
+ ImagingSectionLeave(&cookie);
+}
+
void
ImagingResampleHorizontal_32bpc(
- Imaging imOut, Imaging imIn, int offset, int ksize, int *bounds, double *kk) {
+ Imaging imOut, Imaging imIn, int offset, int ksize, int *bounds, double *kk
+) {
ImagingSectionCookie cookie;
double ss;
int xx, yy, x, xmin, xmax;
@@ -502,7 +583,8 @@ ImagingResampleHorizontal_32bpc(
void
ImagingResampleVertical_32bpc(
- Imaging imOut, Imaging imIn, int offset, int ksize, int *bounds, double *kk) {
+ Imaging imOut, Imaging imIn, int offset, int ksize, int *bounds, double *kk
+) {
ImagingSectionCookie cookie;
double ss;
int xx, yy, y, ymin, ymax;
@@ -544,7 +626,8 @@ ImagingResampleVertical_32bpc(
}
typedef void (*ResampleFunction)(
- Imaging imOut, Imaging imIn, int offset, int ksize, int *bounds, double *kk);
+ Imaging imOut, Imaging imIn, int offset, int ksize, int *bounds, double *kk
+);
Imaging
ImagingResampleInner(
@@ -554,7 +637,8 @@ ImagingResampleInner(
struct filter *filterp,
float box[4],
ResampleFunction ResampleHorizontal,
- ResampleFunction ResampleVertical);
+ ResampleFunction ResampleVertical
+);
Imaging
ImagingResample(Imaging imIn, int xsize, int ysize, int filter, float box[4]) {
@@ -567,7 +651,12 @@ ImagingResample(Imaging imIn, int xsize, int ysize, int filter, float box[4]) {
}
if (imIn->type == IMAGING_TYPE_SPECIAL) {
- return (Imaging)ImagingError_ModeError();
+ if (strncmp(imIn->mode, "I;16", 4) == 0) {
+ ResampleHorizontal = ImagingResampleHorizontal_16bpc;
+ ResampleVertical = ImagingResampleVertical_16bpc;
+ } else {
+ return (Imaging)ImagingError_ModeError();
+ }
} else if (imIn->image8) {
ResampleHorizontal = ImagingResampleHorizontal_8bpc;
ResampleVertical = ImagingResampleVertical_8bpc;
@@ -609,7 +698,8 @@ ImagingResample(Imaging imIn, int xsize, int ysize, int filter, float box[4]) {
}
return ImagingResampleInner(
- imIn, xsize, ysize, filterp, box, ResampleHorizontal, ResampleVertical);
+ imIn, xsize, ysize, filterp, box, ResampleHorizontal, ResampleVertical
+ );
}
Imaging
@@ -620,7 +710,8 @@ ImagingResampleInner(
struct filter *filterp,
float box[4],
ResampleFunction ResampleHorizontal,
- ResampleFunction ResampleVertical) {
+ ResampleFunction ResampleVertical
+) {
Imaging imTemp = NULL;
Imaging imOut = NULL;
@@ -634,13 +725,15 @@ ImagingResampleInner(
need_vertical = ysize != imIn->ysize || box[1] || box[3] != ysize;
ksize_horiz = precompute_coeffs(
- imIn->xsize, box[0], box[2], xsize, filterp, &bounds_horiz, &kk_horiz);
+ imIn->xsize, box[0], box[2], xsize, filterp, &bounds_horiz, &kk_horiz
+ );
if (!ksize_horiz) {
return NULL;
}
ksize_vert = precompute_coeffs(
- imIn->ysize, box[1], box[3], ysize, filterp, &bounds_vert, &kk_vert);
+ imIn->ysize, box[1], box[3], ysize, filterp, &bounds_vert, &kk_vert
+ );
if (!ksize_vert) {
free(bounds_horiz);
free(kk_horiz);
@@ -662,7 +755,8 @@ ImagingResampleInner(
imTemp = ImagingNewDirty(imIn->mode, xsize, ybox_last - ybox_first);
if (imTemp) {
ResampleHorizontal(
- imTemp, imIn, ybox_first, ksize_horiz, bounds_horiz, kk_horiz);
+ imTemp, imIn, ybox_first, ksize_horiz, bounds_horiz, kk_horiz
+ );
}
free(bounds_horiz);
free(kk_horiz);
diff --git a/src/libImaging/SgiRleDecode.c b/src/libImaging/SgiRleDecode.c
index 89dedb525..e60468990 100644
--- a/src/libImaging/SgiRleDecode.c
+++ b/src/libImaging/SgiRleDecode.c
@@ -114,7 +114,8 @@ expandrow(UINT8 *dest, UINT8 *src, int n, int z, int xsize, UINT8 *end_of_buffer
static int
expandrow2(
- UINT8 *dest, const UINT8 *src, int n, int z, int xsize, UINT8 *end_of_buffer) {
+ UINT8 *dest, const UINT8 *src, int n, int z, int xsize, UINT8 *end_of_buffer
+) {
UINT8 pixel, count;
int x = 0;
@@ -182,7 +183,7 @@ ImagingSgiRleDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t
each with 4 bytes per element of tablen
Check here before we allocate any memory
*/
- if (c->bufsize < 8 * c->tablen) {
+ if (c->bufsize < 8 * (int64_t)c->tablen) {
state->errcode = IMAGING_CODEC_OVERRUN;
return -1;
}
@@ -194,6 +195,7 @@ ImagingSgiRleDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t
}
_imaging_seek_pyFd(state->fd, SGI_HEADER_SIZE, SEEK_SET);
if (_imaging_read_pyFd(state->fd, (char *)ptr, c->bufsize) != c->bufsize) {
+ free(ptr);
state->errcode = IMAGING_CODEC_UNKNOWN;
return -1;
}
@@ -252,7 +254,8 @@ ImagingSgiRleDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t
c->rlelength,
im->bands,
im->xsize,
- &ptr[c->bufsize - 1]);
+ &ptr[c->bufsize - 1]
+ );
} else {
status = expandrow2(
&state->buffer[c->channo * 2],
@@ -260,7 +263,8 @@ ImagingSgiRleDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t
c->rlelength,
im->bands,
im->xsize,
- &ptr[c->bufsize - 1]);
+ &ptr[c->bufsize - 1]
+ );
}
if (status == -1) {
state->errcode = IMAGING_CODEC_OVERRUN;
diff --git a/src/libImaging/Storage.c b/src/libImaging/Storage.c
index b27195a35..522e9f375 100644
--- a/src/libImaging/Storage.c
+++ b/src/libImaging/Storage.c
@@ -110,9 +110,8 @@ ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int size) {
im->linesize = xsize * 4;
im->type = IMAGING_TYPE_INT32;
- } else if (
- strcmp(mode, "I;16") == 0 || strcmp(mode, "I;16L") == 0 ||
- strcmp(mode, "I;16B") == 0 || strcmp(mode, "I;16N") == 0) {
+ } else if (strcmp(mode, "I;16") == 0 || strcmp(mode, "I;16L") == 0 ||
+ strcmp(mode, "I;16B") == 0 || strcmp(mode, "I;16N") == 0) {
/* EXPERIMENTAL */
/* 16-bit raw integer images */
im->bands = 1;
@@ -219,7 +218,9 @@ ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int size) {
break;
}
+ MUTEX_LOCK(&ImagingDefaultArena.mutex);
ImagingDefaultArena.stats_new_count += 1;
+ MUTEX_UNLOCK(&ImagingDefaultArena.mutex);
return im;
}
@@ -227,7 +228,8 @@ ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int size) {
Imaging
ImagingNewPrologue(const char *mode, int xsize, int ysize) {
return ImagingNewPrologueSubtype(
- mode, xsize, ysize, sizeof(struct ImagingMemoryInstance));
+ mode, xsize, ysize, sizeof(struct ImagingMemoryInstance)
+ );
}
void
@@ -267,7 +269,10 @@ struct ImagingMemoryArena ImagingDefaultArena = {
0,
0,
0,
- 0 // Stats
+ 0, // Stats
+#ifdef Py_GIL_DISABLED
+ {0},
+#endif
};
int
@@ -364,18 +369,19 @@ ImagingDestroyArray(Imaging im) {
int y = 0;
if (im->blocks) {
+ MUTEX_LOCK(&ImagingDefaultArena.mutex);
while (im->blocks[y].ptr) {
memory_return_block(&ImagingDefaultArena, im->blocks[y]);
y += 1;
}
+ MUTEX_UNLOCK(&ImagingDefaultArena.mutex);
free(im->blocks);
}
}
Imaging
-ImagingAllocateArray(Imaging im, int dirty, int block_size) {
+ImagingAllocateArray(Imaging im, ImagingMemoryArena arena, int dirty, int block_size) {
int y, line_in_block, current_block;
- ImagingMemoryArena arena = &ImagingDefaultArena;
ImagingMemoryBlock block = {NULL, 0};
int aligned_linesize, lines_per_block, blocks_count;
char *aligned_ptr = NULL;
@@ -498,14 +504,22 @@ ImagingNewInternal(const char *mode, int xsize, int ysize, int dirty) {
return NULL;
}
- if (ImagingAllocateArray(im, dirty, ImagingDefaultArena.block_size)) {
+ MUTEX_LOCK(&ImagingDefaultArena.mutex);
+ Imaging tmp = ImagingAllocateArray(
+ im, &ImagingDefaultArena, dirty, ImagingDefaultArena.block_size
+ );
+ MUTEX_UNLOCK(&ImagingDefaultArena.mutex);
+ if (tmp) {
return im;
}
ImagingError_Clear();
// Try to allocate the image once more with smallest possible block size
- if (ImagingAllocateArray(im, dirty, IMAGING_PAGE_SIZE)) {
+ MUTEX_LOCK(&ImagingDefaultArena.mutex);
+ tmp = ImagingAllocateArray(im, &ImagingDefaultArena, dirty, IMAGING_PAGE_SIZE);
+ MUTEX_UNLOCK(&ImagingDefaultArena.mutex);
+ if (tmp) {
return im;
}
diff --git a/src/libImaging/SunRleDecode.c b/src/libImaging/SunRleDecode.c
index 9d8e1292a..d3231ad90 100644
--- a/src/libImaging/SunRleDecode.c
+++ b/src/libImaging/SunRleDecode.c
@@ -107,7 +107,8 @@ ImagingSunRleDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t
(UINT8 *)im->image[state->y + state->yoff] +
state->xoff * im->pixelsize,
state->buffer,
- state->xsize);
+ state->xsize
+ );
state->x = 0;
diff --git a/src/libImaging/TgaRleDecode.c b/src/libImaging/TgaRleDecode.c
index 95ae9b622..fbf29452c 100644
--- a/src/libImaging/TgaRleDecode.c
+++ b/src/libImaging/TgaRleDecode.c
@@ -93,7 +93,8 @@ ImagingTgaRleDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t
(UINT8 *)im->image[state->y + state->yoff] +
state->xoff * im->pixelsize,
state->buffer,
- state->xsize);
+ state->xsize
+ );
state->x = 0;
diff --git a/src/libImaging/TgaRleEncode.c b/src/libImaging/TgaRleEncode.c
index aa7e7b96d..dde476614 100644
--- a/src/libImaging/TgaRleEncode.c
+++ b/src/libImaging/TgaRleEncode.c
@@ -63,7 +63,8 @@ ImagingTgaRleEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes)
state->buffer,
(UINT8 *)im->image[state->y + state->yoff] +
state->xoff * im->pixelsize,
- state->xsize);
+ state->xsize
+ );
}
row = state->buffer;
@@ -146,7 +147,8 @@ ImagingTgaRleEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes)
}
memcpy(
- dst, state->buffer + (state->x * bytesPerPixel - state->count), flushCount);
+ dst, state->buffer + (state->x * bytesPerPixel - state->count), flushCount
+ );
dst += flushCount;
bytes -= flushCount;
diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c
index abffdeabc..e4da9162d 100644
--- a/src/libImaging/TiffDecode.c
+++ b/src/libImaging/TiffDecode.c
@@ -44,7 +44,8 @@ dump_state(const TIFFSTATE *state) {
(int)state->size,
(uint)state->eof,
state->data,
- state->ifd));
+ state->ifd)
+ );
}
/*
@@ -64,7 +65,8 @@ _tiffReadProc(thandle_t hdata, tdata_t buf, tsize_t size) {
"_tiffReadProc",
"Invalid Read at loc %" PRIu64 ", eof: %" PRIu64,
state->loc,
- state->eof);
+ state->eof
+ );
return 0;
}
to_read = min(size, min(state->size, (tsize_t)state->eof) - (tsize_t)state->loc);
@@ -200,13 +202,15 @@ ImagingLibTiffInit(ImagingCodecState state, int fp, uint32_t offset) {
state->state,
state->x,
state->y,
- state->ystep));
+ state->ystep)
+ );
TRACE(
("State: xsize %d, ysize %d, xoff %d, yoff %d \n",
state->xsize,
state->ysize,
state->xoff,
- state->yoff));
+ state->yoff)
+ );
TRACE(("State: bits %d, bytes %d \n", state->bits, state->bytes));
TRACE(("State: context %p \n", state->context));
@@ -226,7 +230,8 @@ _pickUnpackers(
ImagingCodecState state,
TIFF *tiff,
uint16_t planarconfig,
- ImagingShuffler *unpackers) {
+ ImagingShuffler *unpackers
+) {
// if number of bands is 1, there is no difference with contig case
if (planarconfig == PLANARCONFIG_SEPARATE && im->bands > 1) {
uint16_t bits_per_sample = 8;
@@ -356,7 +361,8 @@ _decodeAsRGBA(Imaging im, ImagingCodecState state, TIFF *tiff) {
(UINT8 *)im->image[state->y + state->yoff + current_row] +
state->xoff * im->pixelsize,
state->buffer + current_row * row_byte_size,
- state->xsize);
+ state->xsize
+ );
}
}
@@ -374,7 +380,8 @@ _decodeTile(
ImagingCodecState state,
TIFF *tiff,
int planes,
- ImagingShuffler *unpackers) {
+ ImagingShuffler *unpackers
+) {
INT32 x, y, tile_y, current_tile_length, current_tile_width;
UINT32 tile_width, tile_length;
tsize_t tile_bytes_size, row_byte_size;
@@ -453,7 +460,8 @@ _decodeTile(
("Writing tile data at %dx%d using tile_width: %d; \n",
tile_y + y,
x,
- current_tile_width));
+ current_tile_width)
+ );
// UINT8 * bbb = state->buffer + tile_y * row_byte_size;
// TRACE(("chars: %x%x%x%x\n", ((UINT8 *)bbb)[0], ((UINT8 *)bbb)[1],
@@ -462,7 +470,8 @@ _decodeTile(
shuffler(
(UINT8 *)im->image[tile_y + y] + x * im->pixelsize,
state->buffer + tile_y * row_byte_size,
- current_tile_width);
+ current_tile_width
+ );
}
}
}
@@ -477,7 +486,8 @@ _decodeStrip(
ImagingCodecState state,
TIFF *tiff,
int planes,
- ImagingShuffler *unpackers) {
+ ImagingShuffler *unpackers
+) {
INT32 strip_row = 0;
UINT8 *new_data;
UINT32 rows_per_strip;
@@ -544,9 +554,10 @@ _decodeStrip(
tiff,
TIFFComputeStrip(tiff, state->y, plane),
(tdata_t)state->buffer,
- strip_size) == -1) {
- TRACE(
- ("Decode Error, strip %d\n", TIFFComputeStrip(tiff, state->y, 0)));
+ strip_size
+ ) == -1) {
+ TRACE(("Decode Error, strip %d\n", TIFFComputeStrip(tiff, state->y, 0))
+ );
state->errcode = IMAGING_CODEC_BROKEN;
return -1;
}
@@ -567,7 +578,8 @@ _decodeStrip(
(UINT8 *)im->image[state->y + state->yoff + strip_row] +
state->xoff * im->pixelsize,
state->buffer + strip_row * row_byte_size,
- state->xsize);
+ state->xsize
+ );
}
}
}
@@ -577,7 +589,8 @@ _decodeStrip(
int
ImagingLibTiffDecode(
- Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes) {
+ Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes
+) {
TIFFSTATE *clientstate = (TIFFSTATE *)state->context;
char *filename = "tempfile.tif";
char *mode = "rC";
@@ -602,13 +615,15 @@ ImagingLibTiffDecode(
state->state,
state->x,
state->y,
- state->ystep));
+ state->ystep)
+ );
TRACE(
("State: xsize %d, ysize %d, xoff %d, yoff %d \n",
state->xsize,
state->ysize,
state->xoff,
- state->yoff));
+ state->yoff)
+ );
TRACE(("State: bits %d, bytes %d \n", state->bits, state->bytes));
TRACE(
("Buffer: %p: %c%c%c%c\n",
@@ -616,26 +631,30 @@ ImagingLibTiffDecode(
(char)buffer[0],
(char)buffer[1],
(char)buffer[2],
- (char)buffer[3]));
+ (char)buffer[3])
+ );
TRACE(
("State->Buffer: %c%c%c%c\n",
(char)state->buffer[0],
(char)state->buffer[1],
(char)state->buffer[2],
- (char)state->buffer[3]));
+ (char)state->buffer[3])
+ );
TRACE(
("Image: mode %s, type %d, bands: %d, xsize %d, ysize %d \n",
im->mode,
im->type,
im->bands,
im->xsize,
- im->ysize));
+ im->ysize)
+ );
TRACE(
("Image: image8 %p, image32 %p, image %p, block %p \n",
im->image8,
im->image32,
im->image,
- im->block));
+ im->block)
+ );
TRACE(("Image: pixelsize: %d, linesize %d \n", im->pixelsize, im->linesize));
dump_state(clientstate);
@@ -665,7 +684,8 @@ ImagingLibTiffDecode(
_tiffCloseProc,
_tiffSizeProc,
_tiffMapProc,
- _tiffUnmapProc);
+ _tiffUnmapProc
+ );
}
if (!tiff) {
@@ -694,7 +714,8 @@ ImagingLibTiffDecode(
state->xsize,
img_width,
state->ysize,
- img_height));
+ img_height)
+ );
state->errcode = IMAGING_CODEC_BROKEN;
goto decode_err;
}
@@ -739,7 +760,8 @@ ImagingLibTiffDecode(
INT32 y;
TIFFGetFieldDefaulted(
- tiff, TIFFTAG_EXTRASAMPLES, &extrasamples, &sampleinfo);
+ tiff, TIFFTAG_EXTRASAMPLES, &extrasamples, &sampleinfo
+ );
if (extrasamples >= 1 && (sampleinfo[0] == EXTRASAMPLE_UNSPECIFIED ||
sampleinfo[0] == EXTRASAMPLE_ASSOCALPHA)) {
@@ -758,7 +780,7 @@ ImagingLibTiffDecode(
decode_err:
// TIFFClose in libtiff calls tif_closeproc and TIFFCleanup
if (clientstate->fp) {
- // Pillow will manage the closing of the file rather than libtiff
+ // Python will manage the closing of the file rather than libtiff
// So only call TIFFCleanup
TIFFCleanup(tiff);
} else {
@@ -793,13 +815,15 @@ ImagingLibTiffEncodeInit(ImagingCodecState state, char *filename, int fp) {
state->state,
state->x,
state->y,
- state->ystep));
+ state->ystep)
+ );
TRACE(
("State: xsize %d, ysize %d, xoff %d, yoff %d \n",
state->xsize,
state->ysize,
state->xoff,
- state->yoff));
+ state->yoff)
+ );
TRACE(("State: bits %d, bytes %d \n", state->bits, state->bytes));
TRACE(("State: context %p \n", state->context));
@@ -839,7 +863,8 @@ ImagingLibTiffEncodeInit(ImagingCodecState state, char *filename, int fp) {
_tiffCloseProc,
_tiffSizeProc,
_tiffNullMapProc,
- _tiffUnmapProc); /*force no mmap*/
+ _tiffUnmapProc
+ ); /*force no mmap*/
}
if (!clientstate->tiff) {
@@ -852,7 +877,8 @@ ImagingLibTiffEncodeInit(ImagingCodecState state, char *filename, int fp) {
int
ImagingLibTiffMergeFieldInfo(
- ImagingCodecState state, TIFFDataType field_type, int key, int is_var_length) {
+ ImagingCodecState state, TIFFDataType field_type, int key, int is_var_length
+) {
// Refer to libtiff docs (http://www.simplesystems.org/libtiff/addingtags.html)
TIFFSTATE *clientstate = (TIFFSTATE *)state->context;
uint32_t n;
@@ -874,7 +900,8 @@ ImagingLibTiffMergeFieldInfo(
FIELD_CUSTOM,
1,
passcount,
- "CustomField"}};
+ "CustomField"}
+ };
n = sizeof(info) / sizeof(info[0]);
@@ -922,13 +949,15 @@ ImagingLibTiffEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int byt
state->state,
state->x,
state->y,
- state->ystep));
+ state->ystep)
+ );
TRACE(
("State: xsize %d, ysize %d, xoff %d, yoff %d \n",
state->xsize,
state->ysize,
state->xoff,
- state->yoff));
+ state->yoff)
+ );
TRACE(("State: bits %d, bytes %d \n", state->bits, state->bytes));
TRACE(
("Buffer: %p: %c%c%c%c\n",
@@ -936,26 +965,30 @@ ImagingLibTiffEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int byt
(char)buffer[0],
(char)buffer[1],
(char)buffer[2],
- (char)buffer[3]));
+ (char)buffer[3])
+ );
TRACE(
("State->Buffer: %c%c%c%c\n",
(char)state->buffer[0],
(char)state->buffer[1],
(char)state->buffer[2],
- (char)state->buffer[3]));
+ (char)state->buffer[3])
+ );
TRACE(
("Image: mode %s, type %d, bands: %d, xsize %d, ysize %d \n",
im->mode,
im->type,
im->bands,
im->xsize,
- im->ysize));
+ im->ysize)
+ );
TRACE(
("Image: image8 %p, image32 %p, image %p, block %p \n",
im->image8,
im->image32,
im->image,
- im->block));
+ im->block)
+ );
TRACE(("Image: pixelsize: %d, linesize %d \n", im->pixelsize, im->linesize));
dump_state(clientstate);
@@ -967,13 +1000,25 @@ ImagingLibTiffEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int byt
state->buffer,
(UINT8 *)im->image[state->y + state->yoff] +
state->xoff * im->pixelsize,
- state->xsize);
+ state->xsize
+ );
if (TIFFWriteScanline(
- tiff, (tdata_t)(state->buffer), (uint32_t)state->y, 0) == -1) {
+ tiff, (tdata_t)(state->buffer), (uint32_t)state->y, 0
+ ) == -1) {
TRACE(("Encode Error, row %d\n", state->y));
state->errcode = IMAGING_CODEC_BROKEN;
- TIFFClose(tiff);
+
+ // TIFFClose in libtiff calls tif_closeproc and TIFFCleanup
+ if (clientstate->fp) {
+ // Python will manage the closing of the file rather than libtiff
+ // So only call TIFFCleanup
+ TIFFCleanup(tiff);
+ } else {
+ // When tif_closeproc refers to our custom _tiffCloseProc though,
+ // that is fine, as it does not close the file
+ TIFFClose(tiff);
+ }
if (!clientstate->fp) {
free(clientstate->data);
}
@@ -990,14 +1035,22 @@ ImagingLibTiffEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int byt
TRACE(("Error flushing the tiff"));
// likely reason is memory.
state->errcode = IMAGING_CODEC_MEMORY;
- TIFFClose(tiff);
+ if (clientstate->fp) {
+ TIFFCleanup(tiff);
+ } else {
+ TIFFClose(tiff);
+ }
if (!clientstate->fp) {
free(clientstate->data);
}
return -1;
}
TRACE(("Closing \n"));
- TIFFClose(tiff);
+ if (clientstate->fp) {
+ TIFFCleanup(tiff);
+ } else {
+ TIFFClose(tiff);
+ }
// reset the clientstate metadata to use it to read out the buffer.
clientstate->loc = 0;
clientstate->size = clientstate->eof; // redundant?
@@ -1013,7 +1066,8 @@ ImagingLibTiffEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int byt
(char)buffer[0],
(char)buffer[1],
(char)buffer[2],
- (char)buffer[3]));
+ (char)buffer[3])
+ );
if (clientstate->loc == clientstate->eof) {
TRACE(("Hit EOF, calling an end, freeing data"));
state->errcode = IMAGING_CODEC_END;
diff --git a/src/libImaging/TiffDecode.h b/src/libImaging/TiffDecode.h
index 212b7dee6..22361210d 100644
--- a/src/libImaging/TiffDecode.h
+++ b/src/libImaging/TiffDecode.h
@@ -41,7 +41,8 @@ extern int
ImagingLibTiffEncodeInit(ImagingCodecState state, char *filename, int fp);
extern int
ImagingLibTiffMergeFieldInfo(
- ImagingCodecState state, TIFFDataType field_type, int key, int is_var_length);
+ ImagingCodecState state, TIFFDataType field_type, int key, int is_var_length
+);
extern int
ImagingLibTiffSetField(ImagingCodecState state, ttag_t tag, ...);
diff --git a/src/libImaging/Unpack.c b/src/libImaging/Unpack.c
index 1b84cd68f..c23d5d889 100644
--- a/src/libImaging/Unpack.c
+++ b/src/libImaging/Unpack.c
@@ -104,7 +104,8 @@ static UINT8 BITFLIP[] = {
3, 131, 67, 195, 35, 163, 99, 227, 19, 147, 83, 211, 51, 179, 115, 243,
11, 139, 75, 203, 43, 171, 107, 235, 27, 155, 91, 219, 59, 187, 123, 251,
7, 135, 71, 199, 39, 167, 103, 231, 23, 151, 87, 215, 55, 183, 119, 247,
- 15, 143, 79, 207, 47, 175, 111, 239, 31, 159, 95, 223, 63, 191, 127, 255};
+ 15, 143, 79, 207, 47, 175, 111, 239, 31, 159, 95, 223, 63, 191, 127, 255
+};
/* Unpack to "1" image */
@@ -718,6 +719,21 @@ ImagingUnpackBGRA15(UINT8 *out, const UINT8 *in, int pixels) {
}
}
+void
+ImagingUnpackBGRA15Z(UINT8 *out, const UINT8 *in, int pixels) {
+ int i, pixel;
+ /* RGB, rearranged channels, 5/5/5/1 bits per pixel, inverted alpha */
+ for (i = 0; i < pixels; i++) {
+ pixel = in[0] + (in[1] << 8);
+ out[B] = (pixel & 31) * 255 / 31;
+ out[G] = ((pixel >> 5) & 31) * 255 / 31;
+ out[R] = ((pixel >> 10) & 31) * 255 / 31;
+ out[A] = ~((pixel >> 15) * 255);
+ out += 4;
+ in += 2;
+ }
+}
+
void
ImagingUnpackRGB16(UINT8 *out, const UINT8 *in, int pixels) {
int i, pixel;
@@ -867,7 +883,8 @@ unpackRGBa16L(UINT8 *_out, const UINT8 *in, int pixels) {
CLIP8(in[1] * 255 / a),
CLIP8(in[3] * 255 / a),
CLIP8(in[5] * 255 / a),
- a);
+ a
+ );
}
memcpy(_out, &iv, sizeof(iv));
in += 8;
@@ -891,7 +908,8 @@ unpackRGBa16B(UINT8 *_out, const UINT8 *in, int pixels) {
CLIP8(in[0] * 255 / a),
CLIP8(in[2] * 255 / a),
CLIP8(in[4] * 255 / a),
- a);
+ a
+ );
}
memcpy(_out, &iv, sizeof(iv));
in += 8;
@@ -915,7 +933,8 @@ unpackRGBa(UINT8 *_out, const UINT8 *in, int pixels) {
CLIP8(in[0] * 255 / a),
CLIP8(in[1] * 255 / a),
CLIP8(in[2] * 255 / a),
- a);
+ a
+ );
}
memcpy(_out, &iv, sizeof(iv));
in += 4;
@@ -939,7 +958,8 @@ unpackRGBaskip1(UINT8 *_out, const UINT8 *in, int pixels) {
CLIP8(in[0] * 255 / a),
CLIP8(in[1] * 255 / a),
CLIP8(in[2] * 255 / a),
- a);
+ a
+ );
}
in += 5;
}
@@ -961,7 +981,8 @@ unpackRGBaskip2(UINT8 *_out, const UINT8 *in, int pixels) {
CLIP8(in[0] * 255 / a),
CLIP8(in[1] * 255 / a),
CLIP8(in[2] * 255 / a),
- a);
+ a
+ );
}
in += 6;
}
@@ -983,7 +1004,8 @@ unpackBGRa(UINT8 *_out, const UINT8 *in, int pixels) {
CLIP8(in[2] * 255 / a),
CLIP8(in[1] * 255 / a),
CLIP8(in[0] * 255 / a),
- a);
+ a
+ );
}
memcpy(_out, &iv, sizeof(iv));
in += 4;
@@ -1014,7 +1036,8 @@ unpackRGBAL(UINT8 *_out, const UINT8 *in, int pixels) {
in[i],
in[i + pixels],
in[i + pixels + pixels],
- in[i + pixels + pixels + pixels]);
+ in[i + pixels + pixels + pixels]
+ );
memcpy(_out, &iv, sizeof(iv));
}
}
@@ -1538,7 +1561,7 @@ static struct {
/* flags: "I" inverted data; "R" reversed bit order; "B" big
endian byte order (default is little endian); "L" line
- interleave, "S" signed, "F" floating point */
+ interleave, "S" signed, "F" floating point, "Z" inverted alpha */
/* exception: rawmodes "I" and "F" are always native endian byte order */
@@ -1600,10 +1623,14 @@ static struct {
{"RGB", "BGR;15", 16, ImagingUnpackBGR15},
{"RGB", "RGB;16", 16, ImagingUnpackRGB16},
{"RGB", "BGR;16", 16, ImagingUnpackBGR16},
+ {"RGB", "RGBX;16L", 64, unpackRGBA16L},
+ {"RGB", "RGBX;16B", 64, unpackRGBA16B},
{"RGB", "RGB;4B", 16, ImagingUnpackRGB4B},
{"RGB", "BGR;5", 16, ImagingUnpackBGR15}, /* compat */
{"RGB", "RGBX", 32, copy4},
{"RGB", "RGBX;L", 32, unpackRGBAL},
+ {"RGB", "RGBXX", 40, copy4skip1},
+ {"RGB", "RGBXXX", 48, copy4skip2},
{"RGB", "RGBA;L", 32, unpackRGBAL},
{"RGB", "RGBA;15", 16, ImagingUnpackRGBA15},
{"RGB", "BGRX", 32, ImagingUnpackBGRX},
@@ -1642,6 +1669,7 @@ static struct {
{"RGBA", "RGBA;L", 32, unpackRGBAL},
{"RGBA", "RGBA;15", 16, ImagingUnpackRGBA15},
{"RGBA", "BGRA;15", 16, ImagingUnpackBGRA15},
+ {"RGBA", "BGRA;15Z", 16, ImagingUnpackBGRA15Z},
{"RGBA", "RGBA;4B", 16, ImagingUnpackRGBA4B},
{"RGBA", "RGBA;16L", 64, unpackRGBA16L},
{"RGBA", "RGBA;16B", 64, unpackRGBA16B},
diff --git a/src/libImaging/UnpackYCC.c b/src/libImaging/UnpackYCC.c
index 0b177bdd4..35b0c3b69 100644
--- a/src/libImaging/UnpackYCC.c
+++ b/src/libImaging/UnpackYCC.c
@@ -34,7 +34,8 @@ static INT16 L[] = {
261, 262, 264, 265, 266, 268, 269, 270, 272, 273, 274, 276, 277, 278, 280, 281,
283, 284, 285, 287, 288, 289, 291, 292, 293, 295, 296, 297, 299, 300, 302, 303,
304, 306, 307, 308, 310, 311, 312, 314, 315, 317, 318, 319, 321, 322, 323, 325,
- 326, 327, 329, 330, 331, 333, 334, 336, 337, 338, 340, 341, 342, 344, 345, 346};
+ 326, 327, 329, 330, 331, 333, 334, 336, 337, 338, 340, 341, 342, 344, 345, 346
+};
static INT16 CB[] = {
-345, -343, -341, -338, -336, -334, -332, -329, -327, -325, -323, -321, -318, -316,
@@ -55,7 +56,8 @@ static INT16 CB[] = {
120, 122, 124, 126, 129, 131, 133, 135, 138, 140, 142, 144, 146, 149,
151, 153, 155, 157, 160, 162, 164, 166, 169, 171, 173, 175, 177, 180,
182, 184, 186, 189, 191, 193, 195, 197, 200, 202, 204, 206, 208, 211,
- 213, 215, 217, 220};
+ 213, 215, 217, 220
+};
static INT16 GB[] = {
67, 67, 66, 66, 65, 65, 65, 64, 64, 63, 63, 62, 62, 62, 61, 61,
@@ -73,7 +75,8 @@ static INT16 GB[] = {
-14, -15, -15, -16, -16, -17, -17, -18, -18, -18, -19, -19, -20, -20, -21, -21,
-21, -22, -22, -23, -23, -24, -24, -24, -25, -25, -26, -26, -27, -27, -27, -28,
-28, -29, -29, -30, -30, -30, -31, -31, -32, -32, -33, -33, -33, -34, -34, -35,
- -35, -36, -36, -36, -37, -37, -38, -38, -39, -39, -39, -40, -40, -41, -41, -42};
+ -35, -36, -36, -36, -37, -37, -38, -38, -39, -39, -39, -40, -40, -41, -41, -42
+};
static INT16 CR[] = {
-249, -247, -245, -243, -241, -239, -238, -236, -234, -232, -230, -229, -227, -225,
@@ -94,7 +97,8 @@ static INT16 CR[] = {
133, 135, 137, 138, 140, 142, 144, 146, 148, 149, 151, 153, 155, 157,
158, 160, 162, 164, 166, 168, 169, 171, 173, 175, 177, 179, 180, 182,
184, 186, 188, 189, 191, 193, 195, 197, 199, 200, 202, 204, 206, 208,
- 209, 211, 213, 215};
+ 209, 211, 213, 215
+};
static INT16 GR[] = {
127, 126, 125, 124, 123, 122, 121, 121, 120, 119, 118, 117, 116, 115, 114,
@@ -114,7 +118,8 @@ static INT16 GR[] = {
-67, -68, -69, -69, -70, -71, -72, -73, -74, -75, -76, -77, -78, -79, -80,
-81, -82, -82, -83, -84, -85, -86, -87, -88, -89, -90, -91, -92, -93, -94,
-94, -95, -96, -97, -98, -99, -100, -101, -102, -103, -104, -105, -106, -107, -107,
- -108};
+ -108
+};
#define R 0
#define G 1
diff --git a/src/libImaging/UnsharpMask.c b/src/libImaging/UnsharpMask.c
index 2853ce903..e714749ef 100644
--- a/src/libImaging/UnsharpMask.c
+++ b/src/libImaging/UnsharpMask.c
@@ -23,7 +23,8 @@ clip8(int in) {
Imaging
ImagingUnsharpMask(
- Imaging imOut, Imaging imIn, float radius, int percent, int threshold) {
+ Imaging imOut, Imaging imIn, float radius, int percent, int threshold
+) {
ImagingSectionCookie cookie;
Imaging result;
diff --git a/src/libImaging/XbmEncode.c b/src/libImaging/XbmEncode.c
index eec4c0d84..65cc3c633 100644
--- a/src/libImaging/XbmEncode.c
+++ b/src/libImaging/XbmEncode.c
@@ -40,7 +40,8 @@ ImagingXbmEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
state->shuffle(
state->buffer,
(UINT8 *)im->image[state->y + state->yoff] + state->xoff * im->pixelsize,
- state->xsize);
+ state->xsize
+ );
if (state->y < state->ysize - 1) {
/* any line but the last */
diff --git a/src/libImaging/ZipDecode.c b/src/libImaging/ZipDecode.c
index 874967834..d964ff2ca 100644
--- a/src/libImaging/ZipDecode.c
+++ b/src/libImaging/ZipDecode.c
@@ -217,7 +217,8 @@ ImagingZipDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t byt
state->shuffle(
(UINT8 *)im->image[state->y] + col * im->pixelsize,
state->buffer + context->prefix + i,
- 1);
+ 1
+ );
col += COL_INCREMENT[context->pass];
}
} else {
@@ -229,7 +230,8 @@ ImagingZipDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t byt
UINT8 byte = *(state->buffer + context->prefix + (i / 8));
byte <<= (i % 8);
state->shuffle(
- (UINT8 *)im->image[state->y] + col * im->pixelsize, &byte, 1);
+ (UINT8 *)im->image[state->y] + col * im->pixelsize, &byte, 1
+ );
col += COL_INCREMENT[context->pass];
}
}
@@ -253,7 +255,8 @@ ImagingZipDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t byt
(UINT8 *)im->image[state->y + state->yoff] +
state->xoff * im->pixelsize,
state->buffer + context->prefix,
- state->xsize);
+ state->xsize
+ );
state->y++;
}
diff --git a/src/libImaging/ZipEncode.c b/src/libImaging/ZipEncode.c
index edbce3682..44f2629cc 100644
--- a/src/libImaging/ZipEncode.c
+++ b/src/libImaging/ZipEncode.c
@@ -98,7 +98,8 @@ ImagingZipEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
15,
9,
/* compression strategy (image data are filtered)*/
- compress_type);
+ compress_type
+ );
if (err < 0) {
state->errcode = IMAGING_CODEC_CONFIG;
return -1;
@@ -108,7 +109,8 @@ ImagingZipEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
err = deflateSetDictionary(
&context->z_stream,
(unsigned char *)context->dictionary,
- context->dictionary_size);
+ context->dictionary_size
+ );
if (err < 0) {
state->errcode = IMAGING_CODEC_CONFIG;
return -1;
@@ -163,7 +165,8 @@ ImagingZipEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
state->buffer + 1,
(UINT8 *)im->image[state->y + state->yoff] +
state->xoff * im->pixelsize,
- state->xsize);
+ state->xsize
+ );
state->y++;
diff --git a/src/map.c b/src/map.c
index c298bd148..c66702981 100644
--- a/src/map.c
+++ b/src/map.c
@@ -72,7 +72,8 @@ PyImaging_MapBuffer(PyObject *self, PyObject *args) {
&offset,
&mode,
&stride,
- &ystep)) {
+ &ystep
+ )) {
return NULL;
}
diff --git a/src/path.c b/src/path.c
index 6bc90abed..067f42f62 100644
--- a/src/path.c
+++ b/src/path.c
@@ -26,6 +26,7 @@
*/
#include "Python.h"
+#include "thirdparty/pythoncapi_compat.h"
#include "libImaging/Imaging.h"
#include
@@ -43,7 +44,7 @@ PyImaging_GetBuffer(PyObject *buffer, Py_buffer *view);
typedef struct {
PyObject_HEAD Py_ssize_t count;
double *xy;
- int index; /* temporary use, e.g. in decimate */
+ int mapping;
} PyPathObject;
static PyTypeObject PyPathType;
@@ -91,6 +92,7 @@ path_new(Py_ssize_t count, double *xy, int duplicate) {
path->count = count;
path->xy = xy;
+ path->mapping = 0;
return path;
}
@@ -179,14 +181,21 @@ PyPath_Flatten(PyObject *data, double **pxy) {
} \
free(xy); \
return -1; \
+ } \
+ if (decref) { \
+ Py_DECREF(op); \
}
/* Copy table to path array */
if (PyList_Check(data)) {
for (i = 0; i < n; i++) {
double x, y;
- PyObject *op = PyList_GET_ITEM(data, i);
- assign_item_to_array(op, 0);
+ PyObject *op = PyList_GetItemRef(data, i);
+ if (op == NULL) {
+ free(xy);
+ return -1;
+ }
+ assign_item_to_array(op, 1);
}
} else if (PyTuple_Check(data)) {
for (i = 0; i < n; i++) {
@@ -209,7 +218,6 @@ PyPath_Flatten(PyObject *data, double **pxy) {
}
}
assign_item_to_array(op, 1);
- Py_DECREF(op);
}
}
@@ -270,6 +278,10 @@ path_compact(PyPathObject *self, PyObject *args) {
double cityblock = 2.0;
+ if (self->mapping) {
+ PyErr_SetString(PyExc_ValueError, "Path compacted during mapping");
+ return NULL;
+ }
if (!PyArg_ParseTuple(args, "|d:compact", &cityblock)) {
return NULL;
}
@@ -387,11 +399,13 @@ path_map(PyPathObject *self, PyObject *args) {
xy = self->xy;
/* apply function to coordinate set */
+ self->mapping = 1;
for (i = 0; i < self->count; i++) {
double x = xy[i + i];
double y = xy[i + i + 1];
PyObject *item = PyObject_CallFunction(function, "dd", x, y);
if (!item || !PyArg_ParseTuple(item, "dd", &x, &y)) {
+ self->mapping = 0;
Py_XDECREF(item);
return NULL;
}
@@ -399,6 +413,7 @@ path_map(PyPathObject *self, PyObject *args) {
xy[i + i + 1] = y;
Py_DECREF(item);
}
+ self->mapping = 0;
Py_INCREF(Py_None);
return Py_None;
@@ -482,7 +497,8 @@ path_transform(PyPathObject *self, PyObject *args) {
double wrap = 0.0;
if (!PyArg_ParseTuple(
- args, "(dddddd)|d:transform", &a, &b, &c, &d, &e, &f, &wrap)) {
+ args, "(dddddd)|d:transform", &a, &b, &c, &d, &e, &f, &wrap
+ )) {
return NULL;
}
@@ -563,7 +579,8 @@ path_subscript(PyPathObject *self, PyObject *item) {
PyErr_Format(
PyExc_TypeError,
"Path indices must be integers, not %.200s",
- Py_TYPE(item)->tp_name);
+ Py_TYPE(item)->tp_name
+ );
return NULL;
}
}
@@ -579,7 +596,8 @@ static PySequenceMethods path_as_sequence = {
};
static PyMappingMethods path_as_mapping = {
- (lenfunc)path_len, (binaryfunc)path_subscript, NULL};
+ (lenfunc)path_len, (binaryfunc)path_subscript, NULL
+};
static PyTypeObject PyPathType = {
PyVarObject_HEAD_INIT(NULL, 0) "Path", /*tp_name*/
diff --git a/src/thirdparty/pythoncapi_compat.h b/src/thirdparty/pythoncapi_compat.h
new file mode 100644
index 000000000..ca23d5ffa
--- /dev/null
+++ b/src/thirdparty/pythoncapi_compat.h
@@ -0,0 +1,1699 @@
+// Header file providing new C API functions to old Python versions.
+//
+// File distributed under the Zero Clause BSD (0BSD) license.
+// Copyright Contributors to the pythoncapi_compat project.
+//
+// Homepage:
+// https://github.com/python/pythoncapi_compat
+//
+// Latest version:
+// https://raw.githubusercontent.com/python/pythoncapi-compat/main/pythoncapi_compat.h
+//
+// This file was vendored from the following commit:
+// https://github.com/python/pythoncapi-compat/commit/0041177c4f348c8952b4c8980b2c90856e61c7c7
+//
+// SPDX-License-Identifier: 0BSD
+
+#ifndef PYTHONCAPI_COMPAT
+#define PYTHONCAPI_COMPAT
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include
+
+// Python 3.11.0b4 added PyFrame_Back() to Python.h
+#if PY_VERSION_HEX < 0x030b00B4 && !defined(PYPY_VERSION)
+# include "frameobject.h" // PyFrameObject, PyFrame_GetBack()
+#endif
+
+
+#ifndef _Py_CAST
+# define _Py_CAST(type, expr) ((type)(expr))
+#endif
+
+// Static inline functions should use _Py_NULL rather than using directly NULL
+// to prevent C++ compiler warnings. On C23 and newer and on C++11 and newer,
+// _Py_NULL is defined as nullptr.
+#if (defined (__STDC_VERSION__) && __STDC_VERSION__ > 201710L) \
+ || (defined(__cplusplus) && __cplusplus >= 201103)
+# define _Py_NULL nullptr
+#else
+# define _Py_NULL NULL
+#endif
+
+// Cast argument to PyObject* type.
+#ifndef _PyObject_CAST
+# define _PyObject_CAST(op) _Py_CAST(PyObject*, op)
+#endif
+
+#ifndef Py_BUILD_ASSERT
+# define Py_BUILD_ASSERT(cond) \
+ do { \
+ (void)sizeof(char [1 - 2 * !(cond)]); \
+ } while(0)
+#endif
+
+
+// bpo-42262 added Py_NewRef() to Python 3.10.0a3
+#if PY_VERSION_HEX < 0x030A00A3 && !defined(Py_NewRef)
+static inline PyObject* _Py_NewRef(PyObject *obj)
+{
+ Py_INCREF(obj);
+ return obj;
+}
+#define Py_NewRef(obj) _Py_NewRef(_PyObject_CAST(obj))
+#endif
+
+
+// bpo-42262 added Py_XNewRef() to Python 3.10.0a3
+#if PY_VERSION_HEX < 0x030A00A3 && !defined(Py_XNewRef)
+static inline PyObject* _Py_XNewRef(PyObject *obj)
+{
+ Py_XINCREF(obj);
+ return obj;
+}
+#define Py_XNewRef(obj) _Py_XNewRef(_PyObject_CAST(obj))
+#endif
+
+
+// bpo-39573 added Py_SET_REFCNT() to Python 3.9.0a4
+#if PY_VERSION_HEX < 0x030900A4 && !defined(Py_SET_REFCNT)
+static inline void _Py_SET_REFCNT(PyObject *ob, Py_ssize_t refcnt)
+{
+ ob->ob_refcnt = refcnt;
+}
+#define Py_SET_REFCNT(ob, refcnt) _Py_SET_REFCNT(_PyObject_CAST(ob), refcnt)
+#endif
+
+
+// Py_SETREF() and Py_XSETREF() were added to Python 3.5.2.
+// It is excluded from the limited C API.
+#if (PY_VERSION_HEX < 0x03050200 && !defined(Py_SETREF)) && !defined(Py_LIMITED_API)
+#define Py_SETREF(dst, src) \
+ do { \
+ PyObject **_tmp_dst_ptr = _Py_CAST(PyObject**, &(dst)); \
+ PyObject *_tmp_dst = (*_tmp_dst_ptr); \
+ *_tmp_dst_ptr = _PyObject_CAST(src); \
+ Py_DECREF(_tmp_dst); \
+ } while (0)
+
+#define Py_XSETREF(dst, src) \
+ do { \
+ PyObject **_tmp_dst_ptr = _Py_CAST(PyObject**, &(dst)); \
+ PyObject *_tmp_dst = (*_tmp_dst_ptr); \
+ *_tmp_dst_ptr = _PyObject_CAST(src); \
+ Py_XDECREF(_tmp_dst); \
+ } while (0)
+#endif
+
+
+// bpo-43753 added Py_Is(), Py_IsNone(), Py_IsTrue() and Py_IsFalse()
+// to Python 3.10.0b1.
+#if PY_VERSION_HEX < 0x030A00B1 && !defined(Py_Is)
+# define Py_Is(x, y) ((x) == (y))
+#endif
+#if PY_VERSION_HEX < 0x030A00B1 && !defined(Py_IsNone)
+# define Py_IsNone(x) Py_Is(x, Py_None)
+#endif
+#if (PY_VERSION_HEX < 0x030A00B1 || defined(PYPY_VERSION)) && !defined(Py_IsTrue)
+# define Py_IsTrue(x) Py_Is(x, Py_True)
+#endif
+#if (PY_VERSION_HEX < 0x030A00B1 || defined(PYPY_VERSION)) && !defined(Py_IsFalse)
+# define Py_IsFalse(x) Py_Is(x, Py_False)
+#endif
+
+
+// bpo-39573 added Py_SET_TYPE() to Python 3.9.0a4
+#if PY_VERSION_HEX < 0x030900A4 && !defined(Py_SET_TYPE)
+static inline void _Py_SET_TYPE(PyObject *ob, PyTypeObject *type)
+{
+ ob->ob_type = type;
+}
+#define Py_SET_TYPE(ob, type) _Py_SET_TYPE(_PyObject_CAST(ob), type)
+#endif
+
+
+// bpo-39573 added Py_SET_SIZE() to Python 3.9.0a4
+#if PY_VERSION_HEX < 0x030900A4 && !defined(Py_SET_SIZE)
+static inline void _Py_SET_SIZE(PyVarObject *ob, Py_ssize_t size)
+{
+ ob->ob_size = size;
+}
+#define Py_SET_SIZE(ob, size) _Py_SET_SIZE((PyVarObject*)(ob), size)
+#endif
+
+
+// bpo-40421 added PyFrame_GetCode() to Python 3.9.0b1
+#if PY_VERSION_HEX < 0x030900B1 || defined(PYPY_VERSION)
+static inline PyCodeObject* PyFrame_GetCode(PyFrameObject *frame)
+{
+ assert(frame != _Py_NULL);
+ assert(frame->f_code != _Py_NULL);
+ return _Py_CAST(PyCodeObject*, Py_NewRef(frame->f_code));
+}
+#endif
+
+static inline PyCodeObject* _PyFrame_GetCodeBorrow(PyFrameObject *frame)
+{
+ PyCodeObject *code = PyFrame_GetCode(frame);
+ Py_DECREF(code);
+ return code;
+}
+
+
+// bpo-40421 added PyFrame_GetBack() to Python 3.9.0b1
+#if PY_VERSION_HEX < 0x030900B1 && !defined(PYPY_VERSION)
+static inline PyFrameObject* PyFrame_GetBack(PyFrameObject *frame)
+{
+ assert(frame != _Py_NULL);
+ return _Py_CAST(PyFrameObject*, Py_XNewRef(frame->f_back));
+}
+#endif
+
+#if !defined(PYPY_VERSION)
+static inline PyFrameObject* _PyFrame_GetBackBorrow(PyFrameObject *frame)
+{
+ PyFrameObject *back = PyFrame_GetBack(frame);
+ Py_XDECREF(back);
+ return back;
+}
+#endif
+
+
+// bpo-40421 added PyFrame_GetLocals() to Python 3.11.0a7
+#if PY_VERSION_HEX < 0x030B00A7 && !defined(PYPY_VERSION)
+static inline PyObject* PyFrame_GetLocals(PyFrameObject *frame)
+{
+#if PY_VERSION_HEX >= 0x030400B1
+ if (PyFrame_FastToLocalsWithError(frame) < 0) {
+ return NULL;
+ }
+#else
+ PyFrame_FastToLocals(frame);
+#endif
+ return Py_NewRef(frame->f_locals);
+}
+#endif
+
+
+// bpo-40421 added PyFrame_GetGlobals() to Python 3.11.0a7
+#if PY_VERSION_HEX < 0x030B00A7 && !defined(PYPY_VERSION)
+static inline PyObject* PyFrame_GetGlobals(PyFrameObject *frame)
+{
+ return Py_NewRef(frame->f_globals);
+}
+#endif
+
+
+// bpo-40421 added PyFrame_GetBuiltins() to Python 3.11.0a7
+#if PY_VERSION_HEX < 0x030B00A7 && !defined(PYPY_VERSION)
+static inline PyObject* PyFrame_GetBuiltins(PyFrameObject *frame)
+{
+ return Py_NewRef(frame->f_builtins);
+}
+#endif
+
+
+// bpo-40421 added PyFrame_GetLasti() to Python 3.11.0b1
+#if PY_VERSION_HEX < 0x030B00B1 && !defined(PYPY_VERSION)
+static inline int PyFrame_GetLasti(PyFrameObject *frame)
+{
+#if PY_VERSION_HEX >= 0x030A00A7
+ // bpo-27129: Since Python 3.10.0a7, f_lasti is an instruction offset,
+ // not a bytes offset anymore. Python uses 16-bit "wordcode" (2 bytes)
+ // instructions.
+ if (frame->f_lasti < 0) {
+ return -1;
+ }
+ return frame->f_lasti * 2;
+#else
+ return frame->f_lasti;
+#endif
+}
+#endif
+
+
+// gh-91248 added PyFrame_GetVar() to Python 3.12.0a2
+#if PY_VERSION_HEX < 0x030C00A2 && !defined(PYPY_VERSION)
+static inline PyObject* PyFrame_GetVar(PyFrameObject *frame, PyObject *name)
+{
+ PyObject *locals, *value;
+
+ locals = PyFrame_GetLocals(frame);
+ if (locals == NULL) {
+ return NULL;
+ }
+#if PY_VERSION_HEX >= 0x03000000
+ value = PyDict_GetItemWithError(locals, name);
+#else
+ value = _PyDict_GetItemWithError(locals, name);
+#endif
+ Py_DECREF(locals);
+
+ if (value == NULL) {
+ if (PyErr_Occurred()) {
+ return NULL;
+ }
+#if PY_VERSION_HEX >= 0x03000000
+ PyErr_Format(PyExc_NameError, "variable %R does not exist", name);
+#else
+ PyErr_SetString(PyExc_NameError, "variable does not exist");
+#endif
+ return NULL;
+ }
+ return Py_NewRef(value);
+}
+#endif
+
+
+// gh-91248 added PyFrame_GetVarString() to Python 3.12.0a2
+#if PY_VERSION_HEX < 0x030C00A2 && !defined(PYPY_VERSION)
+static inline PyObject*
+PyFrame_GetVarString(PyFrameObject *frame, const char *name)
+{
+ PyObject *name_obj, *value;
+#if PY_VERSION_HEX >= 0x03000000
+ name_obj = PyUnicode_FromString(name);
+#else
+ name_obj = PyString_FromString(name);
+#endif
+ if (name_obj == NULL) {
+ return NULL;
+ }
+ value = PyFrame_GetVar(frame, name_obj);
+ Py_DECREF(name_obj);
+ return value;
+}
+#endif
+
+
+// bpo-39947 added PyThreadState_GetInterpreter() to Python 3.9.0a5
+#if PY_VERSION_HEX < 0x030900A5 || defined(PYPY_VERSION)
+static inline PyInterpreterState *
+PyThreadState_GetInterpreter(PyThreadState *tstate)
+{
+ assert(tstate != _Py_NULL);
+ return tstate->interp;
+}
+#endif
+
+
+// bpo-40429 added PyThreadState_GetFrame() to Python 3.9.0b1
+#if PY_VERSION_HEX < 0x030900B1 && !defined(PYPY_VERSION)
+static inline PyFrameObject* PyThreadState_GetFrame(PyThreadState *tstate)
+{
+ assert(tstate != _Py_NULL);
+ return _Py_CAST(PyFrameObject *, Py_XNewRef(tstate->frame));
+}
+#endif
+
+#if !defined(PYPY_VERSION)
+static inline PyFrameObject*
+_PyThreadState_GetFrameBorrow(PyThreadState *tstate)
+{
+ PyFrameObject *frame = PyThreadState_GetFrame(tstate);
+ Py_XDECREF(frame);
+ return frame;
+}
+#endif
+
+
+// bpo-39947 added PyInterpreterState_Get() to Python 3.9.0a5
+#if PY_VERSION_HEX < 0x030900A5 || defined(PYPY_VERSION)
+static inline PyInterpreterState* PyInterpreterState_Get(void)
+{
+ PyThreadState *tstate;
+ PyInterpreterState *interp;
+
+ tstate = PyThreadState_GET();
+ if (tstate == _Py_NULL) {
+ Py_FatalError("GIL released (tstate is NULL)");
+ }
+ interp = tstate->interp;
+ if (interp == _Py_NULL) {
+ Py_FatalError("no current interpreter");
+ }
+ return interp;
+}
+#endif
+
+
+// bpo-39947 added PyInterpreterState_Get() to Python 3.9.0a6
+#if 0x030700A1 <= PY_VERSION_HEX && PY_VERSION_HEX < 0x030900A6 && !defined(PYPY_VERSION)
+static inline uint64_t PyThreadState_GetID(PyThreadState *tstate)
+{
+ assert(tstate != _Py_NULL);
+ return tstate->id;
+}
+#endif
+
+// bpo-43760 added PyThreadState_EnterTracing() to Python 3.11.0a2
+#if PY_VERSION_HEX < 0x030B00A2 && !defined(PYPY_VERSION)
+static inline void PyThreadState_EnterTracing(PyThreadState *tstate)
+{
+ tstate->tracing++;
+#if PY_VERSION_HEX >= 0x030A00A1
+ tstate->cframe->use_tracing = 0;
+#else
+ tstate->use_tracing = 0;
+#endif
+}
+#endif
+
+// bpo-43760 added PyThreadState_LeaveTracing() to Python 3.11.0a2
+#if PY_VERSION_HEX < 0x030B00A2 && !defined(PYPY_VERSION)
+static inline void PyThreadState_LeaveTracing(PyThreadState *tstate)
+{
+ int use_tracing = (tstate->c_tracefunc != _Py_NULL
+ || tstate->c_profilefunc != _Py_NULL);
+ tstate->tracing--;
+#if PY_VERSION_HEX >= 0x030A00A1
+ tstate->cframe->use_tracing = use_tracing;
+#else
+ tstate->use_tracing = use_tracing;
+#endif
+}
+#endif
+
+
+// bpo-37194 added PyObject_CallNoArgs() to Python 3.9.0a1
+// PyObject_CallNoArgs() added to PyPy 3.9.16-v7.3.11
+#if !defined(PyObject_CallNoArgs) && PY_VERSION_HEX < 0x030900A1
+static inline PyObject* PyObject_CallNoArgs(PyObject *func)
+{
+ return PyObject_CallFunctionObjArgs(func, NULL);
+}
+#endif
+
+
+// bpo-39245 made PyObject_CallOneArg() public (previously called
+// _PyObject_CallOneArg) in Python 3.9.0a4
+// PyObject_CallOneArg() added to PyPy 3.9.16-v7.3.11
+#if !defined(PyObject_CallOneArg) && PY_VERSION_HEX < 0x030900A4
+static inline PyObject* PyObject_CallOneArg(PyObject *func, PyObject *arg)
+{
+ return PyObject_CallFunctionObjArgs(func, arg, NULL);
+}
+#endif
+
+
+// bpo-1635741 added PyModule_AddObjectRef() to Python 3.10.0a3
+#if PY_VERSION_HEX < 0x030A00A3
+static inline int
+PyModule_AddObjectRef(PyObject *module, const char *name, PyObject *value)
+{
+ int res;
+
+ if (!value && !PyErr_Occurred()) {
+ // PyModule_AddObject() raises TypeError in this case
+ PyErr_SetString(PyExc_SystemError,
+ "PyModule_AddObjectRef() must be called "
+ "with an exception raised if value is NULL");
+ return -1;
+ }
+
+ Py_XINCREF(value);
+ res = PyModule_AddObject(module, name, value);
+ if (res < 0) {
+ Py_XDECREF(value);
+ }
+ return res;
+}
+#endif
+
+
+// bpo-40024 added PyModule_AddType() to Python 3.9.0a5
+#if PY_VERSION_HEX < 0x030900A5
+static inline int PyModule_AddType(PyObject *module, PyTypeObject *type)
+{
+ const char *name, *dot;
+
+ if (PyType_Ready(type) < 0) {
+ return -1;
+ }
+
+ // inline _PyType_Name()
+ name = type->tp_name;
+ assert(name != _Py_NULL);
+ dot = strrchr(name, '.');
+ if (dot != _Py_NULL) {
+ name = dot + 1;
+ }
+
+ return PyModule_AddObjectRef(module, name, _PyObject_CAST(type));
+}
+#endif
+
+
+// bpo-40241 added PyObject_GC_IsTracked() to Python 3.9.0a6.
+// bpo-4688 added _PyObject_GC_IS_TRACKED() to Python 2.7.0a2.
+#if PY_VERSION_HEX < 0x030900A6 && !defined(PYPY_VERSION)
+static inline int PyObject_GC_IsTracked(PyObject* obj)
+{
+ return (PyObject_IS_GC(obj) && _PyObject_GC_IS_TRACKED(obj));
+}
+#endif
+
+// bpo-40241 added PyObject_GC_IsFinalized() to Python 3.9.0a6.
+// bpo-18112 added _PyGCHead_FINALIZED() to Python 3.4.0 final.
+#if PY_VERSION_HEX < 0x030900A6 && PY_VERSION_HEX >= 0x030400F0 && !defined(PYPY_VERSION)
+static inline int PyObject_GC_IsFinalized(PyObject *obj)
+{
+ PyGC_Head *gc = _Py_CAST(PyGC_Head*, obj) - 1;
+ return (PyObject_IS_GC(obj) && _PyGCHead_FINALIZED(gc));
+}
+#endif
+
+
+// bpo-39573 added Py_IS_TYPE() to Python 3.9.0a4
+#if PY_VERSION_HEX < 0x030900A4 && !defined(Py_IS_TYPE)
+static inline int _Py_IS_TYPE(PyObject *ob, PyTypeObject *type) {
+ return Py_TYPE(ob) == type;
+}
+#define Py_IS_TYPE(ob, type) _Py_IS_TYPE(_PyObject_CAST(ob), type)
+#endif
+
+
+// bpo-46906 added PyFloat_Pack2() and PyFloat_Unpack2() to Python 3.11a7.
+// bpo-11734 added _PyFloat_Pack2() and _PyFloat_Unpack2() to Python 3.6.0b1.
+// Python 3.11a2 moved _PyFloat_Pack2() and _PyFloat_Unpack2() to the internal
+// C API: Python 3.11a2-3.11a6 versions are not supported.
+#if 0x030600B1 <= PY_VERSION_HEX && PY_VERSION_HEX <= 0x030B00A1 && !defined(PYPY_VERSION)
+static inline int PyFloat_Pack2(double x, char *p, int le)
+{ return _PyFloat_Pack2(x, (unsigned char*)p, le); }
+
+static inline double PyFloat_Unpack2(const char *p, int le)
+{ return _PyFloat_Unpack2((const unsigned char *)p, le); }
+#endif
+
+
+// bpo-46906 added PyFloat_Pack4(), PyFloat_Pack8(), PyFloat_Unpack4() and
+// PyFloat_Unpack8() to Python 3.11a7.
+// Python 3.11a2 moved _PyFloat_Pack4(), _PyFloat_Pack8(), _PyFloat_Unpack4()
+// and _PyFloat_Unpack8() to the internal C API: Python 3.11a2-3.11a6 versions
+// are not supported.
+#if PY_VERSION_HEX <= 0x030B00A1 && !defined(PYPY_VERSION)
+static inline int PyFloat_Pack4(double x, char *p, int le)
+{ return _PyFloat_Pack4(x, (unsigned char*)p, le); }
+
+static inline int PyFloat_Pack8(double x, char *p, int le)
+{ return _PyFloat_Pack8(x, (unsigned char*)p, le); }
+
+static inline double PyFloat_Unpack4(const char *p, int le)
+{ return _PyFloat_Unpack4((const unsigned char *)p, le); }
+
+static inline double PyFloat_Unpack8(const char *p, int le)
+{ return _PyFloat_Unpack8((const unsigned char *)p, le); }
+#endif
+
+
+// gh-92154 added PyCode_GetCode() to Python 3.11.0b1
+#if PY_VERSION_HEX < 0x030B00B1 && !defined(PYPY_VERSION)
+static inline PyObject* PyCode_GetCode(PyCodeObject *code)
+{
+ return Py_NewRef(code->co_code);
+}
+#endif
+
+
+// gh-95008 added PyCode_GetVarnames() to Python 3.11.0rc1
+#if PY_VERSION_HEX < 0x030B00C1 && !defined(PYPY_VERSION)
+static inline PyObject* PyCode_GetVarnames(PyCodeObject *code)
+{
+ return Py_NewRef(code->co_varnames);
+}
+#endif
+
+// gh-95008 added PyCode_GetFreevars() to Python 3.11.0rc1
+#if PY_VERSION_HEX < 0x030B00C1 && !defined(PYPY_VERSION)
+static inline PyObject* PyCode_GetFreevars(PyCodeObject *code)
+{
+ return Py_NewRef(code->co_freevars);
+}
+#endif
+
+// gh-95008 added PyCode_GetCellvars() to Python 3.11.0rc1
+#if PY_VERSION_HEX < 0x030B00C1 && !defined(PYPY_VERSION)
+static inline PyObject* PyCode_GetCellvars(PyCodeObject *code)
+{
+ return Py_NewRef(code->co_cellvars);
+}
+#endif
+
+
+// Py_UNUSED() was added to Python 3.4.0b2.
+#if PY_VERSION_HEX < 0x030400B2 && !defined(Py_UNUSED)
+# if defined(__GNUC__) || defined(__clang__)
+# define Py_UNUSED(name) _unused_ ## name __attribute__((unused))
+# else
+# define Py_UNUSED(name) _unused_ ## name
+# endif
+#endif
+
+
+// gh-105922 added PyImport_AddModuleRef() to Python 3.13.0a1
+#if PY_VERSION_HEX < 0x030D00A0
+static inline PyObject* PyImport_AddModuleRef(const char *name)
+{
+ return Py_XNewRef(PyImport_AddModule(name));
+}
+#endif
+
+
+// gh-105927 added PyWeakref_GetRef() to Python 3.13.0a1
+#if PY_VERSION_HEX < 0x030D0000
+static inline int PyWeakref_GetRef(PyObject *ref, PyObject **pobj)
+{
+ PyObject *obj;
+ if (ref != NULL && !PyWeakref_Check(ref)) {
+ *pobj = NULL;
+ PyErr_SetString(PyExc_TypeError, "expected a weakref");
+ return -1;
+ }
+ obj = PyWeakref_GetObject(ref);
+ if (obj == NULL) {
+ // SystemError if ref is NULL
+ *pobj = NULL;
+ return -1;
+ }
+ if (obj == Py_None) {
+ *pobj = NULL;
+ return 0;
+ }
+ *pobj = Py_NewRef(obj);
+ return (*pobj != NULL);
+}
+#endif
+
+
+// bpo-36974 added PY_VECTORCALL_ARGUMENTS_OFFSET to Python 3.8b1
+#ifndef PY_VECTORCALL_ARGUMENTS_OFFSET
+# define PY_VECTORCALL_ARGUMENTS_OFFSET (_Py_CAST(size_t, 1) << (8 * sizeof(size_t) - 1))
+#endif
+
+// bpo-36974 added PyVectorcall_NARGS() to Python 3.8b1
+#if PY_VERSION_HEX < 0x030800B1
+static inline Py_ssize_t PyVectorcall_NARGS(size_t n)
+{
+ return n & ~PY_VECTORCALL_ARGUMENTS_OFFSET;
+}
+#endif
+
+
+// gh-105922 added PyObject_Vectorcall() to Python 3.9.0a4
+#if PY_VERSION_HEX < 0x030900A4
+static inline PyObject*
+PyObject_Vectorcall(PyObject *callable, PyObject *const *args,
+ size_t nargsf, PyObject *kwnames)
+{
+#if PY_VERSION_HEX >= 0x030800B1 && !defined(PYPY_VERSION)
+ // bpo-36974 added _PyObject_Vectorcall() to Python 3.8.0b1
+ return _PyObject_Vectorcall(callable, args, nargsf, kwnames);
+#else
+ PyObject *posargs = NULL, *kwargs = NULL;
+ PyObject *res;
+ Py_ssize_t nposargs, nkwargs, i;
+
+ if (nargsf != 0 && args == NULL) {
+ PyErr_BadInternalCall();
+ goto error;
+ }
+ if (kwnames != NULL && !PyTuple_Check(kwnames)) {
+ PyErr_BadInternalCall();
+ goto error;
+ }
+
+ nposargs = (Py_ssize_t)PyVectorcall_NARGS(nargsf);
+ if (kwnames) {
+ nkwargs = PyTuple_GET_SIZE(kwnames);
+ }
+ else {
+ nkwargs = 0;
+ }
+
+ posargs = PyTuple_New(nposargs);
+ if (posargs == NULL) {
+ goto error;
+ }
+ if (nposargs) {
+ for (i=0; i < nposargs; i++) {
+ PyTuple_SET_ITEM(posargs, i, Py_NewRef(*args));
+ args++;
+ }
+ }
+
+ if (nkwargs) {
+ kwargs = PyDict_New();
+ if (kwargs == NULL) {
+ goto error;
+ }
+
+ for (i = 0; i < nkwargs; i++) {
+ PyObject *key = PyTuple_GET_ITEM(kwnames, i);
+ PyObject *value = *args;
+ args++;
+ if (PyDict_SetItem(kwargs, key, value) < 0) {
+ goto error;
+ }
+ }
+ }
+ else {
+ kwargs = NULL;
+ }
+
+ res = PyObject_Call(callable, posargs, kwargs);
+ Py_DECREF(posargs);
+ Py_XDECREF(kwargs);
+ return res;
+
+error:
+ Py_DECREF(posargs);
+ Py_XDECREF(kwargs);
+ return NULL;
+#endif
+}
+#endif
+
+
+// gh-106521 added PyObject_GetOptionalAttr() and
+// PyObject_GetOptionalAttrString() to Python 3.13.0a1
+#if PY_VERSION_HEX < 0x030D00A1
+static inline int
+PyObject_GetOptionalAttr(PyObject *obj, PyObject *attr_name, PyObject **result)
+{
+ // bpo-32571 added _PyObject_LookupAttr() to Python 3.7.0b1
+#if PY_VERSION_HEX >= 0x030700B1 && !defined(PYPY_VERSION)
+ return _PyObject_LookupAttr(obj, attr_name, result);
+#else
+ *result = PyObject_GetAttr(obj, attr_name);
+ if (*result != NULL) {
+ return 1;
+ }
+ if (!PyErr_Occurred()) {
+ return 0;
+ }
+ if (PyErr_ExceptionMatches(PyExc_AttributeError)) {
+ PyErr_Clear();
+ return 0;
+ }
+ return -1;
+#endif
+}
+
+static inline int
+PyObject_GetOptionalAttrString(PyObject *obj, const char *attr_name, PyObject **result)
+{
+ PyObject *name_obj;
+ int rc;
+#if PY_VERSION_HEX >= 0x03000000
+ name_obj = PyUnicode_FromString(attr_name);
+#else
+ name_obj = PyString_FromString(attr_name);
+#endif
+ if (name_obj == NULL) {
+ *result = NULL;
+ return -1;
+ }
+ rc = PyObject_GetOptionalAttr(obj, name_obj, result);
+ Py_DECREF(name_obj);
+ return rc;
+}
+#endif
+
+
+// gh-106307 added PyObject_GetOptionalAttr() and
+// PyMapping_GetOptionalItemString() to Python 3.13.0a1
+#if PY_VERSION_HEX < 0x030D00A1
+static inline int
+PyMapping_GetOptionalItem(PyObject *obj, PyObject *key, PyObject **result)
+{
+ *result = PyObject_GetItem(obj, key);
+ if (*result) {
+ return 1;
+ }
+ if (!PyErr_ExceptionMatches(PyExc_KeyError)) {
+ return -1;
+ }
+ PyErr_Clear();
+ return 0;
+}
+
+static inline int
+PyMapping_GetOptionalItemString(PyObject *obj, const char *key, PyObject **result)
+{
+ PyObject *key_obj;
+ int rc;
+#if PY_VERSION_HEX >= 0x03000000
+ key_obj = PyUnicode_FromString(key);
+#else
+ key_obj = PyString_FromString(key);
+#endif
+ if (key_obj == NULL) {
+ *result = NULL;
+ return -1;
+ }
+ rc = PyMapping_GetOptionalItem(obj, key_obj, result);
+ Py_DECREF(key_obj);
+ return rc;
+}
+#endif
+
+// gh-108511 added PyMapping_HasKeyWithError() and
+// PyMapping_HasKeyStringWithError() to Python 3.13.0a1
+#if PY_VERSION_HEX < 0x030D00A1
+static inline int
+PyMapping_HasKeyWithError(PyObject *obj, PyObject *key)
+{
+ PyObject *res;
+ int rc = PyMapping_GetOptionalItem(obj, key, &res);
+ Py_XDECREF(res);
+ return rc;
+}
+
+static inline int
+PyMapping_HasKeyStringWithError(PyObject *obj, const char *key)
+{
+ PyObject *res;
+ int rc = PyMapping_GetOptionalItemString(obj, key, &res);
+ Py_XDECREF(res);
+ return rc;
+}
+#endif
+
+
+// gh-108511 added PyObject_HasAttrWithError() and
+// PyObject_HasAttrStringWithError() to Python 3.13.0a1
+#if PY_VERSION_HEX < 0x030D00A1
+static inline int
+PyObject_HasAttrWithError(PyObject *obj, PyObject *attr)
+{
+ PyObject *res;
+ int rc = PyObject_GetOptionalAttr(obj, attr, &res);
+ Py_XDECREF(res);
+ return rc;
+}
+
+static inline int
+PyObject_HasAttrStringWithError(PyObject *obj, const char *attr)
+{
+ PyObject *res;
+ int rc = PyObject_GetOptionalAttrString(obj, attr, &res);
+ Py_XDECREF(res);
+ return rc;
+}
+#endif
+
+
+// gh-106004 added PyDict_GetItemRef() and PyDict_GetItemStringRef()
+// to Python 3.13.0a1
+#if PY_VERSION_HEX < 0x030D00A1
+static inline int
+PyDict_GetItemRef(PyObject *mp, PyObject *key, PyObject **result)
+{
+#if PY_VERSION_HEX >= 0x03000000
+ PyObject *item = PyDict_GetItemWithError(mp, key);
+#else
+ PyObject *item = _PyDict_GetItemWithError(mp, key);
+#endif
+ if (item != NULL) {
+ *result = Py_NewRef(item);
+ return 1; // found
+ }
+ if (!PyErr_Occurred()) {
+ *result = NULL;
+ return 0; // not found
+ }
+ *result = NULL;
+ return -1;
+}
+
+static inline int
+PyDict_GetItemStringRef(PyObject *mp, const char *key, PyObject **result)
+{
+ int res;
+#if PY_VERSION_HEX >= 0x03000000
+ PyObject *key_obj = PyUnicode_FromString(key);
+#else
+ PyObject *key_obj = PyString_FromString(key);
+#endif
+ if (key_obj == NULL) {
+ *result = NULL;
+ return -1;
+ }
+ res = PyDict_GetItemRef(mp, key_obj, result);
+ Py_DECREF(key_obj);
+ return res;
+}
+#endif
+
+
+// gh-106307 added PyModule_Add() to Python 3.13.0a1
+#if PY_VERSION_HEX < 0x030D00A1
+static inline int
+PyModule_Add(PyObject *mod, const char *name, PyObject *value)
+{
+ int res = PyModule_AddObjectRef(mod, name, value);
+ Py_XDECREF(value);
+ return res;
+}
+#endif
+
+
+// gh-108014 added Py_IsFinalizing() to Python 3.13.0a1
+// bpo-1856 added _Py_Finalizing to Python 3.2.1b1.
+// _Py_IsFinalizing() was added to PyPy 7.3.0.
+#if (0x030201B1 <= PY_VERSION_HEX && PY_VERSION_HEX < 0x030D00A1) \
+ && (!defined(PYPY_VERSION_NUM) || PYPY_VERSION_NUM >= 0x7030000)
+static inline int Py_IsFinalizing(void)
+{
+#if PY_VERSION_HEX >= 0x030700A1
+ // _Py_IsFinalizing() was added to Python 3.7.0a1.
+ return _Py_IsFinalizing();
+#else
+ return (_Py_Finalizing != NULL);
+#endif
+}
+#endif
+
+
+// gh-108323 added PyDict_ContainsString() to Python 3.13.0a1
+#if PY_VERSION_HEX < 0x030D00A1
+static inline int PyDict_ContainsString(PyObject *op, const char *key)
+{
+ PyObject *key_obj = PyUnicode_FromString(key);
+ if (key_obj == NULL) {
+ return -1;
+ }
+ int res = PyDict_Contains(op, key_obj);
+ Py_DECREF(key_obj);
+ return res;
+}
+#endif
+
+
+// gh-108445 added PyLong_AsInt() to Python 3.13.0a1
+#if PY_VERSION_HEX < 0x030D00A1
+static inline int PyLong_AsInt(PyObject *obj)
+{
+#ifdef PYPY_VERSION
+ long value = PyLong_AsLong(obj);
+ if (value == -1 && PyErr_Occurred()) {
+ return -1;
+ }
+ if (value < (long)INT_MIN || (long)INT_MAX < value) {
+ PyErr_SetString(PyExc_OverflowError,
+ "Python int too large to convert to C int");
+ return -1;
+ }
+ return (int)value;
+#else
+ return _PyLong_AsInt(obj);
+#endif
+}
+#endif
+
+
+// gh-107073 added PyObject_VisitManagedDict() to Python 3.13.0a1
+#if PY_VERSION_HEX < 0x030D00A1
+static inline int
+PyObject_VisitManagedDict(PyObject *obj, visitproc visit, void *arg)
+{
+ PyObject **dict = _PyObject_GetDictPtr(obj);
+ if (*dict == NULL) {
+ return -1;
+ }
+ Py_VISIT(*dict);
+ return 0;
+}
+
+static inline void
+PyObject_ClearManagedDict(PyObject *obj)
+{
+ PyObject **dict = _PyObject_GetDictPtr(obj);
+ if (*dict == NULL) {
+ return;
+ }
+ Py_CLEAR(*dict);
+}
+#endif
+
+// gh-108867 added PyThreadState_GetUnchecked() to Python 3.13.0a1
+// Python 3.5.2 added _PyThreadState_UncheckedGet().
+#if PY_VERSION_HEX >= 0x03050200 && PY_VERSION_HEX < 0x030D00A1
+static inline PyThreadState*
+PyThreadState_GetUnchecked(void)
+{
+ return _PyThreadState_UncheckedGet();
+}
+#endif
+
+// gh-110289 added PyUnicode_EqualToUTF8() and PyUnicode_EqualToUTF8AndSize()
+// to Python 3.13.0a1
+#if PY_VERSION_HEX < 0x030D00A1
+static inline int
+PyUnicode_EqualToUTF8AndSize(PyObject *unicode, const char *str, Py_ssize_t str_len)
+{
+ Py_ssize_t len;
+ const void *utf8;
+ PyObject *exc_type, *exc_value, *exc_tb;
+ int res;
+
+ // API cannot report errors so save/restore the exception
+ PyErr_Fetch(&exc_type, &exc_value, &exc_tb);
+
+ // Python 3.3.0a1 added PyUnicode_AsUTF8AndSize()
+#if PY_VERSION_HEX >= 0x030300A1
+ if (PyUnicode_IS_ASCII(unicode)) {
+ utf8 = PyUnicode_DATA(unicode);
+ len = PyUnicode_GET_LENGTH(unicode);
+ }
+ else {
+ utf8 = PyUnicode_AsUTF8AndSize(unicode, &len);
+ if (utf8 == NULL) {
+ // Memory allocation failure. The API cannot report error,
+ // so ignore the exception and return 0.
+ res = 0;
+ goto done;
+ }
+ }
+
+ if (len != str_len) {
+ res = 0;
+ goto done;
+ }
+ res = (memcmp(utf8, str, (size_t)len) == 0);
+#else
+ PyObject *bytes = PyUnicode_AsUTF8String(unicode);
+ if (bytes == NULL) {
+ // Memory allocation failure. The API cannot report error,
+ // so ignore the exception and return 0.
+ res = 0;
+ goto done;
+ }
+
+#if PY_VERSION_HEX >= 0x03000000
+ len = PyBytes_GET_SIZE(bytes);
+ utf8 = PyBytes_AS_STRING(bytes);
+#else
+ len = PyString_GET_SIZE(bytes);
+ utf8 = PyString_AS_STRING(bytes);
+#endif
+ if (len != str_len) {
+ Py_DECREF(bytes);
+ res = 0;
+ goto done;
+ }
+
+ res = (memcmp(utf8, str, (size_t)len) == 0);
+ Py_DECREF(bytes);
+#endif
+
+done:
+ PyErr_Restore(exc_type, exc_value, exc_tb);
+ return res;
+}
+
+static inline int
+PyUnicode_EqualToUTF8(PyObject *unicode, const char *str)
+{
+ return PyUnicode_EqualToUTF8AndSize(unicode, str, (Py_ssize_t)strlen(str));
+}
+#endif
+
+
+// gh-111138 added PyList_Extend() and PyList_Clear() to Python 3.13.0a2
+#if PY_VERSION_HEX < 0x030D00A2
+static inline int
+PyList_Extend(PyObject *list, PyObject *iterable)
+{
+ return PyList_SetSlice(list, PY_SSIZE_T_MAX, PY_SSIZE_T_MAX, iterable);
+}
+
+static inline int
+PyList_Clear(PyObject *list)
+{
+ return PyList_SetSlice(list, 0, PY_SSIZE_T_MAX, NULL);
+}
+#endif
+
+// gh-111262 added PyDict_Pop() and PyDict_PopString() to Python 3.13.0a2
+#if PY_VERSION_HEX < 0x030D00A2
+static inline int
+PyDict_Pop(PyObject *dict, PyObject *key, PyObject **result)
+{
+ PyObject *value;
+
+ if (!PyDict_Check(dict)) {
+ PyErr_BadInternalCall();
+ if (result) {
+ *result = NULL;
+ }
+ return -1;
+ }
+
+ // bpo-16991 added _PyDict_Pop() to Python 3.5.0b2.
+ // Python 3.6.0b3 changed _PyDict_Pop() first argument type to PyObject*.
+ // Python 3.13.0a1 removed _PyDict_Pop().
+#if defined(PYPY_VERSION) || PY_VERSION_HEX < 0x030500b2 || PY_VERSION_HEX >= 0x030D0000
+ value = PyObject_CallMethod(dict, "pop", "O", key);
+#elif PY_VERSION_HEX < 0x030600b3
+ value = _PyDict_Pop(_Py_CAST(PyDictObject*, dict), key, NULL);
+#else
+ value = _PyDict_Pop(dict, key, NULL);
+#endif
+ if (value == NULL) {
+ if (result) {
+ *result = NULL;
+ }
+ if (PyErr_Occurred() && !PyErr_ExceptionMatches(PyExc_KeyError)) {
+ return -1;
+ }
+ PyErr_Clear();
+ return 0;
+ }
+ if (result) {
+ *result = value;
+ }
+ else {
+ Py_DECREF(value);
+ }
+ return 1;
+}
+
+static inline int
+PyDict_PopString(PyObject *dict, const char *key, PyObject **result)
+{
+ PyObject *key_obj = PyUnicode_FromString(key);
+ if (key_obj == NULL) {
+ if (result != NULL) {
+ *result = NULL;
+ }
+ return -1;
+ }
+
+ int res = PyDict_Pop(dict, key_obj, result);
+ Py_DECREF(key_obj);
+ return res;
+}
+#endif
+
+
+#if PY_VERSION_HEX < 0x030200A4
+// Python 3.2.0a4 added Py_hash_t type
+typedef Py_ssize_t Py_hash_t;
+#endif
+
+
+// gh-111545 added Py_HashPointer() to Python 3.13.0a3
+#if PY_VERSION_HEX < 0x030D00A3
+static inline Py_hash_t Py_HashPointer(const void *ptr)
+{
+#if PY_VERSION_HEX >= 0x030900A4 && !defined(PYPY_VERSION)
+ return _Py_HashPointer(ptr);
+#else
+ return _Py_HashPointer(_Py_CAST(void*, ptr));
+#endif
+}
+#endif
+
+
+// Python 3.13a4 added a PyTime API.
+// Use the private API added to Python 3.5.
+#if PY_VERSION_HEX < 0x030D00A4 && PY_VERSION_HEX >= 0x03050000
+typedef _PyTime_t PyTime_t;
+#define PyTime_MIN _PyTime_MIN
+#define PyTime_MAX _PyTime_MAX
+
+static inline double PyTime_AsSecondsDouble(PyTime_t t)
+{ return _PyTime_AsSecondsDouble(t); }
+
+static inline int PyTime_Monotonic(PyTime_t *result)
+{ return _PyTime_GetMonotonicClockWithInfo(result, NULL); }
+
+static inline int PyTime_Time(PyTime_t *result)
+{ return _PyTime_GetSystemClockWithInfo(result, NULL); }
+
+static inline int PyTime_PerfCounter(PyTime_t *result)
+{
+#if PY_VERSION_HEX >= 0x03070000 && !defined(PYPY_VERSION)
+ return _PyTime_GetPerfCounterWithInfo(result, NULL);
+#elif PY_VERSION_HEX >= 0x03070000
+ // Call time.perf_counter_ns() and convert Python int object to PyTime_t.
+ // Cache time.perf_counter_ns() function for best performance.
+ static PyObject *func = NULL;
+ if (func == NULL) {
+ PyObject *mod = PyImport_ImportModule("time");
+ if (mod == NULL) {
+ return -1;
+ }
+
+ func = PyObject_GetAttrString(mod, "perf_counter_ns");
+ Py_DECREF(mod);
+ if (func == NULL) {
+ return -1;
+ }
+ }
+
+ PyObject *res = PyObject_CallNoArgs(func);
+ if (res == NULL) {
+ return -1;
+ }
+ long long value = PyLong_AsLongLong(res);
+ Py_DECREF(res);
+
+ if (value == -1 && PyErr_Occurred()) {
+ return -1;
+ }
+
+ Py_BUILD_ASSERT(sizeof(value) >= sizeof(PyTime_t));
+ *result = (PyTime_t)value;
+ return 0;
+#else
+ // Call time.perf_counter() and convert C double to PyTime_t.
+ // Cache time.perf_counter() function for best performance.
+ static PyObject *func = NULL;
+ if (func == NULL) {
+ PyObject *mod = PyImport_ImportModule("time");
+ if (mod == NULL) {
+ return -1;
+ }
+
+ func = PyObject_GetAttrString(mod, "perf_counter");
+ Py_DECREF(mod);
+ if (func == NULL) {
+ return -1;
+ }
+ }
+
+ PyObject *res = PyObject_CallNoArgs(func);
+ if (res == NULL) {
+ return -1;
+ }
+ double d = PyFloat_AsDouble(res);
+ Py_DECREF(res);
+
+ if (d == -1.0 && PyErr_Occurred()) {
+ return -1;
+ }
+
+ // Avoid floor() to avoid having to link to libm
+ *result = (PyTime_t)(d * 1e9);
+ return 0;
+#endif
+}
+
+#endif
+
+// gh-111389 added hash constants to Python 3.13.0a5. These constants were
+// added first as private macros to Python 3.4.0b1 and PyPy 7.3.9.
+#if (!defined(PyHASH_BITS) \
+ && ((!defined(PYPY_VERSION) && PY_VERSION_HEX >= 0x030400B1) \
+ || (defined(PYPY_VERSION) && PY_VERSION_HEX >= 0x03070000 \
+ && PYPY_VERSION_NUM >= 0x07090000)))
+# define PyHASH_BITS _PyHASH_BITS
+# define PyHASH_MODULUS _PyHASH_MODULUS
+# define PyHASH_INF _PyHASH_INF
+# define PyHASH_IMAG _PyHASH_IMAG
+#endif
+
+
+// gh-111545 added Py_GetConstant() and Py_GetConstantBorrowed()
+// to Python 3.13.0a6
+#if PY_VERSION_HEX < 0x030D00A6 && !defined(Py_CONSTANT_NONE)
+
+#define Py_CONSTANT_NONE 0
+#define Py_CONSTANT_FALSE 1
+#define Py_CONSTANT_TRUE 2
+#define Py_CONSTANT_ELLIPSIS 3
+#define Py_CONSTANT_NOT_IMPLEMENTED 4
+#define Py_CONSTANT_ZERO 5
+#define Py_CONSTANT_ONE 6
+#define Py_CONSTANT_EMPTY_STR 7
+#define Py_CONSTANT_EMPTY_BYTES 8
+#define Py_CONSTANT_EMPTY_TUPLE 9
+
+static inline PyObject* Py_GetConstant(unsigned int constant_id)
+{
+ static PyObject* constants[Py_CONSTANT_EMPTY_TUPLE + 1] = {NULL};
+
+ if (constants[Py_CONSTANT_NONE] == NULL) {
+ constants[Py_CONSTANT_NONE] = Py_None;
+ constants[Py_CONSTANT_FALSE] = Py_False;
+ constants[Py_CONSTANT_TRUE] = Py_True;
+ constants[Py_CONSTANT_ELLIPSIS] = Py_Ellipsis;
+ constants[Py_CONSTANT_NOT_IMPLEMENTED] = Py_NotImplemented;
+
+ constants[Py_CONSTANT_ZERO] = PyLong_FromLong(0);
+ if (constants[Py_CONSTANT_ZERO] == NULL) {
+ goto fatal_error;
+ }
+
+ constants[Py_CONSTANT_ONE] = PyLong_FromLong(1);
+ if (constants[Py_CONSTANT_ONE] == NULL) {
+ goto fatal_error;
+ }
+
+ constants[Py_CONSTANT_EMPTY_STR] = PyUnicode_FromStringAndSize("", 0);
+ if (constants[Py_CONSTANT_EMPTY_STR] == NULL) {
+ goto fatal_error;
+ }
+
+ constants[Py_CONSTANT_EMPTY_BYTES] = PyBytes_FromStringAndSize("", 0);
+ if (constants[Py_CONSTANT_EMPTY_BYTES] == NULL) {
+ goto fatal_error;
+ }
+
+ constants[Py_CONSTANT_EMPTY_TUPLE] = PyTuple_New(0);
+ if (constants[Py_CONSTANT_EMPTY_TUPLE] == NULL) {
+ goto fatal_error;
+ }
+ // goto dance to avoid compiler warnings about Py_FatalError()
+ goto init_done;
+
+fatal_error:
+ // This case should never happen
+ Py_FatalError("Py_GetConstant() failed to get constants");
+ }
+
+init_done:
+ if (constant_id <= Py_CONSTANT_EMPTY_TUPLE) {
+ return Py_NewRef(constants[constant_id]);
+ }
+ else {
+ PyErr_BadInternalCall();
+ return NULL;
+ }
+}
+
+static inline PyObject* Py_GetConstantBorrowed(unsigned int constant_id)
+{
+ PyObject *obj = Py_GetConstant(constant_id);
+ Py_XDECREF(obj);
+ return obj;
+}
+#endif
+
+
+// gh-114329 added PyList_GetItemRef() to Python 3.13.0a4
+#if PY_VERSION_HEX < 0x030D00A4
+static inline PyObject *
+PyList_GetItemRef(PyObject *op, Py_ssize_t index)
+{
+ PyObject *item = PyList_GetItem(op, index);
+ Py_XINCREF(item);
+ return item;
+}
+#endif
+
+
+// gh-114329 added PyList_GetItemRef() to Python 3.13.0a4
+#if PY_VERSION_HEX < 0x030D00A4
+static inline int
+PyDict_SetDefaultRef(PyObject *d, PyObject *key, PyObject *default_value,
+ PyObject **result)
+{
+ PyObject *value;
+ if (PyDict_GetItemRef(d, key, &value) < 0) {
+ // get error
+ if (result) {
+ *result = NULL;
+ }
+ return -1;
+ }
+ if (value != NULL) {
+ // present
+ if (result) {
+ *result = value;
+ }
+ else {
+ Py_DECREF(value);
+ }
+ return 1;
+ }
+
+ // missing: set the item
+ if (PyDict_SetItem(d, key, default_value) < 0) {
+ // set error
+ if (result) {
+ *result = NULL;
+ }
+ return -1;
+ }
+ if (result) {
+ *result = Py_NewRef(default_value);
+ }
+ return 0;
+}
+#endif
+
+#if PY_VERSION_HEX < 0x030D00B3
+# define Py_BEGIN_CRITICAL_SECTION(op) {
+# define Py_END_CRITICAL_SECTION() }
+# define Py_BEGIN_CRITICAL_SECTION2(a, b) {
+# define Py_END_CRITICAL_SECTION2() }
+#endif
+
+#if PY_VERSION_HEX < 0x030E0000 && PY_VERSION_HEX >= 0x03060000 && !defined(PYPY_VERSION)
+typedef struct PyUnicodeWriter PyUnicodeWriter;
+
+static inline void PyUnicodeWriter_Discard(PyUnicodeWriter *writer)
+{
+ _PyUnicodeWriter_Dealloc((_PyUnicodeWriter*)writer);
+ PyMem_Free(writer);
+}
+
+static inline PyUnicodeWriter* PyUnicodeWriter_Create(Py_ssize_t length)
+{
+ if (length < 0) {
+ PyErr_SetString(PyExc_ValueError,
+ "length must be positive");
+ return NULL;
+ }
+
+ const size_t size = sizeof(_PyUnicodeWriter);
+ PyUnicodeWriter *pub_writer = (PyUnicodeWriter *)PyMem_Malloc(size);
+ if (pub_writer == _Py_NULL) {
+ PyErr_NoMemory();
+ return _Py_NULL;
+ }
+ _PyUnicodeWriter *writer = (_PyUnicodeWriter *)pub_writer;
+
+ _PyUnicodeWriter_Init(writer);
+ if (_PyUnicodeWriter_Prepare(writer, length, 127) < 0) {
+ PyUnicodeWriter_Discard(pub_writer);
+ return NULL;
+ }
+ writer->overallocate = 1;
+ return pub_writer;
+}
+
+static inline PyObject* PyUnicodeWriter_Finish(PyUnicodeWriter *writer)
+{
+ PyObject *str = _PyUnicodeWriter_Finish((_PyUnicodeWriter*)writer);
+ assert(((_PyUnicodeWriter*)writer)->buffer == NULL);
+ PyMem_Free(writer);
+ return str;
+}
+
+static inline int
+PyUnicodeWriter_WriteChar(PyUnicodeWriter *writer, Py_UCS4 ch)
+{
+ if (ch > 0x10ffff) {
+ PyErr_SetString(PyExc_ValueError,
+ "character must be in range(0x110000)");
+ return -1;
+ }
+
+ return _PyUnicodeWriter_WriteChar((_PyUnicodeWriter*)writer, ch);
+}
+
+static inline int
+PyUnicodeWriter_WriteStr(PyUnicodeWriter *writer, PyObject *obj)
+{
+ PyObject *str = PyObject_Str(obj);
+ if (str == NULL) {
+ return -1;
+ }
+
+ int res = _PyUnicodeWriter_WriteStr((_PyUnicodeWriter*)writer, str);
+ Py_DECREF(str);
+ return res;
+}
+
+static inline int
+PyUnicodeWriter_WriteRepr(PyUnicodeWriter *writer, PyObject *obj)
+{
+ PyObject *str = PyObject_Repr(obj);
+ if (str == NULL) {
+ return -1;
+ }
+
+ int res = _PyUnicodeWriter_WriteStr((_PyUnicodeWriter*)writer, str);
+ Py_DECREF(str);
+ return res;
+}
+
+static inline int
+PyUnicodeWriter_WriteUTF8(PyUnicodeWriter *writer,
+ const char *str, Py_ssize_t size)
+{
+ if (size < 0) {
+ size = (Py_ssize_t)strlen(str);
+ }
+
+ PyObject *str_obj = PyUnicode_FromStringAndSize(str, size);
+ if (str_obj == _Py_NULL) {
+ return -1;
+ }
+
+ int res = _PyUnicodeWriter_WriteStr((_PyUnicodeWriter*)writer, str_obj);
+ Py_DECREF(str_obj);
+ return res;
+}
+
+static inline int
+PyUnicodeWriter_WriteWideChar(PyUnicodeWriter *writer,
+ const wchar_t *str, Py_ssize_t size)
+{
+ if (size < 0) {
+ size = (Py_ssize_t)wcslen(str);
+ }
+
+ PyObject *str_obj = PyUnicode_FromWideChar(str, size);
+ if (str_obj == _Py_NULL) {
+ return -1;
+ }
+
+ int res = _PyUnicodeWriter_WriteStr((_PyUnicodeWriter*)writer, str_obj);
+ Py_DECREF(str_obj);
+ return res;
+}
+
+static inline int
+PyUnicodeWriter_WriteSubstring(PyUnicodeWriter *writer, PyObject *str,
+ Py_ssize_t start, Py_ssize_t end)
+{
+ if (!PyUnicode_Check(str)) {
+ PyErr_Format(PyExc_TypeError, "expect str, not %T", str);
+ return -1;
+ }
+ if (start < 0 || start > end) {
+ PyErr_Format(PyExc_ValueError, "invalid start argument");
+ return -1;
+ }
+ if (end > PyUnicode_GET_LENGTH(str)) {
+ PyErr_Format(PyExc_ValueError, "invalid end argument");
+ return -1;
+ }
+
+ return _PyUnicodeWriter_WriteSubstring((_PyUnicodeWriter*)writer, str,
+ start, end);
+}
+
+static inline int
+PyUnicodeWriter_Format(PyUnicodeWriter *writer, const char *format, ...)
+{
+ va_list vargs;
+ va_start(vargs, format);
+ PyObject *str = PyUnicode_FromFormatV(format, vargs);
+ va_end(vargs);
+ if (str == _Py_NULL) {
+ return -1;
+ }
+
+ int res = _PyUnicodeWriter_WriteStr((_PyUnicodeWriter*)writer, str);
+ Py_DECREF(str);
+ return res;
+}
+#endif // PY_VERSION_HEX < 0x030E0000
+
+// gh-116560 added PyLong_GetSign() to Python 3.14.0a0
+#if PY_VERSION_HEX < 0x030E00A0
+static inline int PyLong_GetSign(PyObject *obj, int *sign)
+{
+ if (!PyLong_Check(obj)) {
+ PyErr_Format(PyExc_TypeError, "expect int, got %s", Py_TYPE(obj)->tp_name);
+ return -1;
+ }
+
+ *sign = _PyLong_Sign(obj);
+ return 0;
+}
+#endif
+
+
+// gh-124502 added PyUnicode_Equal() to Python 3.14.0a0
+#if PY_VERSION_HEX < 0x030E00A0
+static inline int PyUnicode_Equal(PyObject *str1, PyObject *str2)
+{
+ if (!PyUnicode_Check(str1)) {
+ PyErr_Format(PyExc_TypeError, "first argument must be str, not %s",
+ Py_TYPE(str1)->tp_name);
+ return -1;
+ }
+ if (!PyUnicode_Check(str2)) {
+ PyErr_Format(PyExc_TypeError, "second argument must be str, not %s",
+ Py_TYPE(str2)->tp_name);
+ return -1;
+ }
+
+#if PY_VERSION_HEX >= 0x030d0000 && !defined(PYPY_VERSION)
+ PyAPI_FUNC(int) _PyUnicode_Equal(PyObject *str1, PyObject *str2);
+
+ return _PyUnicode_Equal(str1, str2);
+#elif PY_VERSION_HEX >= 0x03060000 && !defined(PYPY_VERSION)
+ return _PyUnicode_EQ(str1, str2);
+#elif PY_VERSION_HEX >= 0x03090000 && defined(PYPY_VERSION)
+ return _PyUnicode_EQ(str1, str2);
+#else
+ return (PyUnicode_Compare(str1, str2) == 0);
+#endif
+}
+#endif
+
+
+// gh-121645 added PyBytes_Join() to Python 3.14.0a0
+#if PY_VERSION_HEX < 0x030E00A0
+static inline PyObject* PyBytes_Join(PyObject *sep, PyObject *iterable)
+{
+ return _PyBytes_Join(sep, iterable);
+}
+#endif
+
+
+#if PY_VERSION_HEX < 0x030E00A0
+static inline Py_hash_t Py_HashBuffer(const void *ptr, Py_ssize_t len)
+{
+#if PY_VERSION_HEX >= 0x03000000 && !defined(PYPY_VERSION)
+ PyAPI_FUNC(Py_hash_t) _Py_HashBytes(const void *src, Py_ssize_t len);
+
+ return _Py_HashBytes(ptr, len);
+#else
+ Py_hash_t hash;
+ PyObject *bytes = PyBytes_FromStringAndSize((const char*)ptr, len);
+ if (bytes == NULL) {
+ return -1;
+ }
+ hash = PyObject_Hash(bytes);
+ Py_DECREF(bytes);
+ return hash;
+#endif
+}
+#endif
+
+
+#if PY_VERSION_HEX < 0x030E00A0
+static inline int PyIter_NextItem(PyObject *iter, PyObject **item)
+{
+ iternextfunc tp_iternext;
+
+ assert(iter != NULL);
+ assert(item != NULL);
+
+ tp_iternext = Py_TYPE(iter)->tp_iternext;
+ if (tp_iternext == NULL) {
+ *item = NULL;
+ PyErr_Format(PyExc_TypeError, "expected an iterator, got '%s'",
+ Py_TYPE(iter)->tp_name);
+ return -1;
+ }
+
+ if ((*item = tp_iternext(iter))) {
+ return 1;
+ }
+ if (!PyErr_Occurred()) {
+ return 0;
+ }
+ if (PyErr_ExceptionMatches(PyExc_StopIteration)) {
+ PyErr_Clear();
+ return 0;
+ }
+ return -1;
+}
+#endif
+
+
+#if PY_VERSION_HEX < 0x030E00A0
+static inline PyObject* PyLong_FromInt32(int32_t value)
+{
+ Py_BUILD_ASSERT(sizeof(long) >= 4);
+ return PyLong_FromLong(value);
+}
+
+static inline PyObject* PyLong_FromInt64(int64_t value)
+{
+ Py_BUILD_ASSERT(sizeof(long long) >= 8);
+ return PyLong_FromLongLong(value);
+}
+
+static inline PyObject* PyLong_FromUInt32(uint32_t value)
+{
+ Py_BUILD_ASSERT(sizeof(unsigned long) >= 4);
+ return PyLong_FromUnsignedLong(value);
+}
+
+static inline PyObject* PyLong_FromUInt64(uint64_t value)
+{
+ Py_BUILD_ASSERT(sizeof(unsigned long long) >= 8);
+ return PyLong_FromUnsignedLongLong(value);
+}
+
+static inline int PyLong_AsInt32(PyObject *obj, int32_t *pvalue)
+{
+ Py_BUILD_ASSERT(sizeof(int) == 4);
+ int value = PyLong_AsInt(obj);
+ if (value == -1 && PyErr_Occurred()) {
+ return -1;
+ }
+ *pvalue = (int32_t)value;
+ return 0;
+}
+
+static inline int PyLong_AsInt64(PyObject *obj, int64_t *pvalue)
+{
+ Py_BUILD_ASSERT(sizeof(long long) == 8);
+ long long value = PyLong_AsLongLong(obj);
+ if (value == -1 && PyErr_Occurred()) {
+ return -1;
+ }
+ *pvalue = (int64_t)value;
+ return 0;
+}
+
+static inline int PyLong_AsUInt32(PyObject *obj, uint32_t *pvalue)
+{
+ Py_BUILD_ASSERT(sizeof(long) >= 4);
+ unsigned long value = PyLong_AsUnsignedLong(obj);
+ if (value == (unsigned long)-1 && PyErr_Occurred()) {
+ return -1;
+ }
+#if SIZEOF_LONG > 4
+ if ((unsigned long)UINT32_MAX < value) {
+ PyErr_SetString(PyExc_OverflowError,
+ "Python int too large to convert to C uint32_t");
+ return -1;
+ }
+#endif
+ *pvalue = (uint32_t)value;
+ return 0;
+}
+
+static inline int PyLong_AsUInt64(PyObject *obj, uint64_t *pvalue)
+{
+ Py_BUILD_ASSERT(sizeof(long long) == 8);
+ unsigned long long value = PyLong_AsUnsignedLongLong(obj);
+ if (value == (unsigned long long)-1 && PyErr_Occurred()) {
+ return -1;
+ }
+ *pvalue = (uint64_t)value;
+ return 0;
+}
+#endif
+
+
+#ifdef __cplusplus
+}
+#endif
+#endif // PYTHONCAPI_COMPAT
diff --git a/tox.ini b/tox.ini
index 85a2020d6..e79d88500 100644
--- a/tox.ini
+++ b/tox.ini
@@ -3,11 +3,10 @@ requires =
tox>=4.2
env_list =
lint
- py{py3, 312, 311, 310, 39, 38}
+ py{py3, 313, 312, 311, 310, 39}
[testenv]
deps =
- cffi
numpy
extras =
tests
@@ -34,15 +33,7 @@ commands =
skip_install = true
deps =
-r .ci/requirements-mypy.txt
- IceSpringPySideStubs-PyQt6
- IceSpringPySideStubs-PySide6
- ipython
- numpy
- packaging
- types-cffi
- types-defusedxml
- types-olefile
extras =
typing
commands =
- mypy src {posargs}
+ mypy conftest.py selftest.py setup.py docs src winbuild Tests {posargs}
diff --git a/wheels/README.md b/wheels/README.md
index 8b412b7fe..1175616e1 100644
--- a/wheels/README.md
+++ b/wheels/README.md
@@ -1,11 +1,11 @@
README
------
-[cibuildwheel](https://github.com/pypa/cibuildwheel) is used to build macOS and Linux
-wheels for tagged versions of Pillow.
+[cibuildwheel](https://github.com/pypa/cibuildwheel) is used to build wheels for tagged
+versions of Pillow.
This directory contains [multibuild](https://github.com/multi-build/multibuild) to
-build dependencies for the wheels, and dependency licenses to be included.
+build dependencies for macOS and Linux wheels, and dependency licenses to be included.
Archives
--------
@@ -30,6 +30,3 @@ Wheels
Wheels are
[GitHub Actions artifacts created for tags, relevant changes or manual builds](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml).
-
-Windows wheels are created separately. They are
-[GitHub Actions artifacts created on each run of the Windows workflow](https://github.com/python-pillow/Pillow/actions/workflows/test-windows.yml?query=branch%3Amain).
diff --git a/wheels/multibuild b/wheels/multibuild
index 452dd2d17..42d761728 160000
--- a/wheels/multibuild
+++ b/wheels/multibuild
@@ -1 +1 @@
-Subproject commit 452dd2d1705f6b2375369a6570c415beb3163f70
+Subproject commit 42d761728d141d8462cd9943f4329f12fe62b155
diff --git a/winbuild/README.md b/winbuild/README.md
index 7e81abcb0..f6111c79b 100644
--- a/winbuild/README.md
+++ b/winbuild/README.md
@@ -11,12 +11,12 @@ For more extensive info, see the [Windows build instructions](build.rst).
* Requires Microsoft Visual Studio 2017 or newer with C++ component.
* Requires NASM for libjpeg-turbo, a required dependency when using this script.
* Requires CMake 3.15 or newer (available as Visual Studio component).
-* Tested on Windows Server 2016 with Visual Studio 2017 Community, and Windows Server 2019 with Visual Studio 2022 Community (AppVeyor).
+* Tested on Windows Server 2019 with Visual Studio 2019 Community and Visual Studio 2022 Community (AppVeyor).
* Tested on Windows Server 2022 with Visual Studio 2022 Enterprise (GitHub Actions).
The following is a simplified version of the script used on AppVeyor:
```
-set PYTHON=C:\Python38\bin
+set PYTHON=C:\Python39\bin
cd /D C:\Pillow\winbuild
%PYTHON%\python.exe build_prepare.py -v --depends=C:\pillow-depends
build\build_dep_all.cmd
diff --git a/winbuild/build.rst b/winbuild/build.rst
index d0be2943e..96b8803b4 100644
--- a/winbuild/build.rst
+++ b/winbuild/build.rst
@@ -114,7 +114,7 @@ Example
The following is a simplified version of the script used on AppVeyor::
- set PYTHON=C:\Python38\bin
+ set PYTHON=C:\Python39\bin
cd /D C:\Pillow\winbuild
%PYTHON%\python.exe build_prepare.py -v --depends C:\pillow-depends
build\build_dep_all.cmd
diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py
index 9837589b2..0674a9a15 100644
--- a/winbuild/build_prepare.py
+++ b/winbuild/build_prepare.py
@@ -7,6 +7,8 @@ import re
import shutil
import struct
import subprocess
+import sys
+from typing import Any
def cmd_cd(path: str) -> str:
@@ -43,21 +45,19 @@ def cmd_nmake(
target: str = "",
params: list[str] | None = None,
) -> str:
- params = "" if params is None else " ".join(params)
-
return " ".join(
[
"{nmake}",
"-nologo",
f'-f "{makefile}"' if makefile is not None else "",
- f"{params}",
+ f'{" ".join(params)}' if params is not None else "",
f'"{target}"',
]
)
def cmds_cmake(
- target: str | tuple[str, ...] | list[str], *params, build_dir: str = "."
+ target: str | tuple[str, ...] | list[str], *params: str, build_dir: str = "."
) -> list[str]:
if not isinstance(target, str):
target = " ".join(target)
@@ -111,30 +111,27 @@ ARCHITECTURES = {
V = {
"BROTLI": "1.1.0",
- "FREETYPE": "2.13.2",
- "FRIBIDI": "1.0.15",
- "HARFBUZZ": "8.5.0",
- "JPEGTURBO": "3.0.3",
+ "FREETYPE": "2.13.3",
+ "FRIBIDI": "1.0.16",
+ "HARFBUZZ": "10.1.0",
+ "JPEGTURBO": "3.1.0",
"LCMS2": "2.16",
- "LIBPNG": "1.6.43",
- "LIBWEBP": "1.4.0",
- "OPENJPEG": "2.5.2",
+ "LIBPNG": "1.6.44",
+ "LIBWEBP": "1.5.0",
+ "OPENJPEG": "2.5.3",
"TIFF": "4.6.0",
- "XZ": "5.4.5",
- "ZLIB": "1.3.1",
+ "XZ": "5.6.3",
+ "ZLIBNG": "2.2.2",
}
V["LIBPNG_DOTLESS"] = V["LIBPNG"].replace(".", "")
V["LIBPNG_XY"] = "".join(V["LIBPNG"].split(".")[:2])
-V["ZLIB_DOTLESS"] = V["ZLIB"].replace(".", "")
# dependencies, listed in order of compilation
-DEPS = {
+DEPS: dict[str, dict[str, Any]] = {
"libjpeg": {
- "url": f"{SF_PROJECTS}/libjpeg-turbo/files/{V['JPEGTURBO']}/"
- f"libjpeg-turbo-{V['JPEGTURBO']}.tar.gz/download",
+ "url": f"https://github.com/libjpeg-turbo/libjpeg-turbo/releases/download/{V['JPEGTURBO']}/libjpeg-turbo-{V['JPEGTURBO']}.tar.gz",
"filename": f"libjpeg-turbo-{V['JPEGTURBO']}.tar.gz",
- "dir": f"libjpeg-turbo-{V['JPEGTURBO']}",
"license": ["README.ijg", "LICENSE.md"],
"license_pattern": (
"(LEGAL ISSUES\n============\n\n.+?)\n\nREFERENCES\n=========="
@@ -157,28 +154,30 @@ DEPS = {
cmd_copy("cjpeg-static.exe", "cjpeg.exe"),
cmd_copy("djpeg-static.exe", "djpeg.exe"),
],
- "headers": ["j*.h"],
+ "headers": ["jconfig.h", r"src\j*.h"],
"libs": ["libjpeg.lib"],
"bins": ["cjpeg.exe", "djpeg.exe"],
},
"zlib": {
- "url": f"https://zlib.net/zlib{V['ZLIB_DOTLESS']}.zip",
- "filename": f"zlib{V['ZLIB_DOTLESS']}.zip",
- "dir": f"zlib-{V['ZLIB']}",
- "license": "README",
- "license_pattern": "Copyright notice:\n\n(.+)$",
+ "url": f"https://github.com/zlib-ng/zlib-ng/archive/refs/tags/{V['ZLIBNG']}.tar.gz",
+ "filename": f"zlib-ng-{V['ZLIBNG']}.tar.gz",
+ "license": "LICENSE.md",
+ "patch": {
+ r"CMakeLists.txt": {
+ "set_target_properties(zlib PROPERTIES OUTPUT_NAME zlibstatic${{SUFFIX}})": "set_target_properties(zlib PROPERTIES OUTPUT_NAME zlib)", # noqa: E501
+ },
+ },
"build": [
- cmd_nmake(r"win32\Makefile.msc", "clean"),
- cmd_nmake(r"win32\Makefile.msc", "zlib.lib"),
- cmd_copy("zlib.lib", "z.lib"),
+ *cmds_cmake(
+ "zlib", "-DBUILD_SHARED_LIBS:BOOL=OFF", "-DZLIB_COMPAT:BOOL=ON"
+ ),
],
"headers": [r"z*.h"],
- "libs": [r"*.lib"],
+ "libs": [r"zlib.lib"],
},
"xz": {
- "url": f"{SF_PROJECTS}/lzmautils/files/xz-{V['XZ']}.tar.gz/download",
+ "url": f"https://github.com/tukaani-project/xz/releases/download/v{V['XZ']}/FILENAME",
"filename": f"xz-{V['XZ']}.tar.gz",
- "dir": f"xz-{V['XZ']}",
"license": "COPYING",
"build": [
*cmds_cmake("liblzma", "-DBUILD_SHARED_LIBS:BOOL=OFF"),
@@ -186,12 +185,11 @@ DEPS = {
cmd_copy(r"src\liblzma\api\lzma\*.h", r"{inc_dir}\lzma"),
],
"headers": [r"src\liblzma\api\lzma.h"],
- "libs": [r"liblzma.lib"],
+ "libs": [r"lzma.lib"],
},
"libwebp": {
- "url": f"http://downloads.webmproject.org/releases/webp/libwebp-{V['LIBWEBP']}.tar.gz",
+ "url": "http://downloads.webmproject.org/releases/webp/FILENAME",
"filename": f"libwebp-{V['LIBWEBP']}.tar.gz",
- "dir": f"libwebp-{V['LIBWEBP']}",
"license": "COPYING",
"patch": {
r"src\enc\picture_csp_enc.c": {
@@ -201,7 +199,7 @@ DEPS = {
},
"build": [
*cmds_cmake(
- "webp webpdemux webpmux",
+ "webp webpmux webpdemux",
"-DBUILD_SHARED_LIBS:BOOL=OFF",
"-DWEBP_LINK_STATIC:BOOL=OFF",
),
@@ -211,14 +209,13 @@ DEPS = {
"libs": [r"libsharpyuv.lib", r"libwebp*.lib"],
},
"libtiff": {
- "url": f"https://download.osgeo.org/libtiff/tiff-{V['TIFF']}.tar.gz",
+ "url": "https://download.osgeo.org/libtiff/FILENAME",
"filename": f"tiff-{V['TIFF']}.tar.gz",
- "dir": f"tiff-{V['TIFF']}",
"license": "LICENSE.md",
"patch": {
r"libtiff\tif_lzma.c": {
- # link against liblzma.lib
- "#ifdef LZMA_SUPPORT": '#ifdef LZMA_SUPPORT\n#pragma comment(lib, "liblzma.lib")', # noqa: E501
+ # link against lzma.lib
+ "#ifdef LZMA_SUPPORT": '#ifdef LZMA_SUPPORT\n#pragma comment(lib, "lzma.lib")', # noqa: E501
},
r"libtiff\tif_webp.c": {
# link against libwebp.lib
@@ -246,7 +243,6 @@ DEPS = {
"url": f"{SF_PROJECTS}/libpng/files/libpng{V['LIBPNG_XY']}/{V['LIBPNG']}/"
f"lpng{V['LIBPNG_DOTLESS']}.zip/download",
"filename": f"lpng{V['LIBPNG_DOTLESS']}.zip",
- "dir": f"lpng{V['LIBPNG_DOTLESS']}",
"license": "LICENSE",
"build": [
*cmds_cmake("png_static", "-DPNG_SHARED:BOOL=OFF", "-DPNG_TESTS:BOOL=OFF"),
@@ -260,7 +256,6 @@ DEPS = {
"brotli": {
"url": f"https://github.com/google/brotli/archive/refs/tags/v{V['BROTLI']}.tar.gz",
"filename": f"brotli-{V['BROTLI']}.tar.gz",
- "dir": f"brotli-{V['BROTLI']}",
"license": "LICENSE",
"build": [
*cmds_cmake(("brotlicommon", "brotlidec"), "-DBUILD_SHARED_LIBS:BOOL=OFF"),
@@ -269,9 +264,8 @@ DEPS = {
"libs": ["*.lib"],
},
"freetype": {
- "url": f"https://download.savannah.gnu.org/releases/freetype/freetype-{V['FREETYPE']}.tar.gz",
+ "url": "https://download.savannah.gnu.org/releases/freetype/FILENAME",
"filename": f"freetype-{V['FREETYPE']}.tar.gz",
- "dir": f"freetype-{V['FREETYPE']}",
"license": ["LICENSE.TXT", r"docs\FTL.TXT", r"docs\GPLv2.TXT"],
"patch": {
r"builds\windows\vc2010\freetype.vcxproj": {
@@ -294,19 +288,18 @@ DEPS = {
"build": [
cmd_rmdir("objs"),
cmd_msbuild(
- r"builds\windows\vc2010\freetype.sln", "Release Static", "Clean"
+ r"builds\windows\vc2010\freetype.vcxproj", "Release Static", "Clean"
),
cmd_msbuild(
- r"builds\windows\vc2010\freetype.sln", "Release Static", "Build"
+ r"builds\windows\vc2010\freetype.vcxproj", "Release Static", "Build"
),
cmd_xcopy("include", "{inc_dir}"),
],
"libs": [r"objs\{msbuild_arch}\Release Static\freetype.lib"],
},
"lcms2": {
- "url": f"{SF_PROJECTS}/lcms/files/lcms/{V['LCMS2']}/lcms2-{V['LCMS2']}.tar.gz/download", # noqa: E501
+ "url": f"{SF_PROJECTS}/lcms/files/lcms/{V['LCMS2']}/FILENAME/download",
"filename": f"lcms2-{V['LCMS2']}.tar.gz",
- "dir": f"lcms2-{V['LCMS2']}",
"license": "LICENSE",
"patch": {
r"Projects\VC2022\lcms2_static\lcms2_static.vcxproj": {
@@ -332,7 +325,6 @@ DEPS = {
"openjpeg": {
"url": f"https://github.com/uclouvain/openjpeg/archive/v{V['OPENJPEG']}.tar.gz",
"filename": f"openjpeg-{V['OPENJPEG']}.tar.gz",
- "dir": f"openjpeg-{V['OPENJPEG']}",
"license": "LICENSE",
"build": [
*cmds_cmake(
@@ -347,7 +339,6 @@ DEPS = {
# commit: Merge branch 'master' into msvc (matches 2.17.0 tag)
"url": "https://github.com/ImageOptim/libimagequant/archive/e4c1334be0eff290af5e2b4155057c2953a313ab.zip",
"filename": "libimagequant-e4c1334be0eff290af5e2b4155057c2953a313ab.zip",
- "dir": "libimagequant-e4c1334be0eff290af5e2b4155057c2953a313ab",
"license": "COPYRIGHT",
"patch": {
"CMakeLists.txt": {
@@ -367,7 +358,6 @@ DEPS = {
"harfbuzz": {
"url": f"https://github.com/harfbuzz/harfbuzz/archive/{V['HARFBUZZ']}.zip",
"filename": f"harfbuzz-{V['HARFBUZZ']}.zip",
- "dir": f"harfbuzz-{V['HARFBUZZ']}",
"license": "COPYING",
"build": [
*cmds_cmake(
@@ -382,7 +372,6 @@ DEPS = {
"fribidi": {
"url": f"https://github.com/fribidi/fribidi/archive/v{V['FRIBIDI']}.zip",
"filename": f"fribidi-{V['FRIBIDI']}.zip",
- "dir": f"fribidi-{V['FRIBIDI']}",
"license": "COPYING",
"build": [
cmd_copy(r"COPYING", rf"{{bin_dir}}\fribidi-{V['FRIBIDI']}-COPYING"),
@@ -498,7 +487,7 @@ def extract_dep(url: str, filename: str, prefs: dict[str, str]) -> None:
except RuntimeError as exc:
# Otherwise try upstream
print(exc)
- download_dep(url, file)
+ download_dep(url.replace("FILENAME", filename), file)
print("Extracting " + filename)
sources_dir_abs = os.path.abspath(sources_dir)
@@ -519,7 +508,10 @@ def extract_dep(url: str, filename: str, prefs: dict[str, str]) -> None:
if sources_dir_abs != member_prefix:
msg = "Attempted Path Traversal in Tar File"
raise RuntimeError(msg)
- tgz.extractall(sources_dir)
+ if sys.version_info >= (3, 12):
+ tgz.extractall(sources_dir, filter="data")
+ else:
+ tgz.extractall(sources_dir)
else:
msg = "Unknown archive type: " + filename
raise RuntimeError(msg)
@@ -538,7 +530,7 @@ def write_script(
print(" " + line)
-def get_footer(dep: dict) -> list[str]:
+def get_footer(dep: dict[str, Any]) -> list[str]:
lines = []
for out in dep.get("headers", []):
lines.append(cmd_copy(out, "{inc_dir}"))
@@ -583,6 +575,7 @@ def build_dep(name: str, prefs: dict[str, str], verbose: bool) -> str:
license_text += f.read()
if "license_pattern" in dep:
match = re.search(dep["license_pattern"], license_text, re.DOTALL)
+ assert match is not None
license_text = "\n".join(match.groups())
assert len(license_text) > 50
with open(os.path.join(license_dir, f"{directory}.txt"), "w") as f:
@@ -761,6 +754,8 @@ def main() -> None:
}
for k, v in DEPS.items():
+ if "dir" not in v:
+ v["dir"] = re.sub(r"\.(tar\.gz|zip)", "", v["filename"])
prefs[f"dir_{k}"] = os.path.join(sources_dir, v["dir"])
print()