bool:
return prefix[:4] in (b"BLP1", b"BLP2")
@@ -253,7 +257,7 @@ class BlpImageFile(ImageFile.ImageFile):
format = "BLP"
format_description = "Blizzard Mipmap Format"
- def _open(self):
+ def _open(self) -> None:
self.magic = self.fp.read(4)
self.fp.seek(5, os.SEEK_CUR)
@@ -275,7 +279,7 @@ def _open(self):
class _BLPBaseDecoder(ImageFile.PyDecoder):
_pulls_fd = True
- def decode(self, buffer):
+ def decode(self, buffer: bytes) -> tuple[int, int]:
try:
self._read_blp_header()
self._load()
@@ -284,7 +288,12 @@ def decode(self, buffer):
raise OSError(msg) from e
return -1, 0
- def _read_blp_header(self):
+ @abc.abstractmethod
+ def _load(self) -> None:
+ pass
+
+ def _read_blp_header(self) -> None:
+ assert self.fd is not None
self.fd.seek(4)
(self._blp_compression,) = struct.unpack(" bytes:
return ImageFile._safe_read(self.fd, length)
- def _read_palette(self):
+ def _read_palette(self) -> list[tuple[int, int, int, int]]:
ret = []
for i in range(256):
try:
@@ -316,7 +325,7 @@ def _read_palette(self):
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:
@@ -325,7 +334,7 @@ def _read_bgra(self, palette):
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)
@@ -333,7 +342,7 @@ def _read_bgra(self, palette):
class BLP1Decoder(_BLPBaseDecoder):
- def _load(self):
+ def _load(self) -> None:
if self._blp_compression == Format.JPEG:
self._decode_jpeg_stream()
@@ -349,29 +358,30 @@ def _load(self):
msg = f"Unsupported BLP compression {repr(self._blp_encoding)}"
raise BLPFormatError(msg)
- def _decode_jpeg_stream(self):
+ def _decode_jpeg_stream(self) -> None:
from .JpegImagePlugin import JpegImageFile
(jpeg_header_size,) = struct.unpack(" None:
palette = self._read_palette()
+ assert self.fd is not None
self.fd.seek(self._blp_offsets[0])
if self._blp_compression == 1:
@@ -418,8 +428,9 @@ def _load(self):
class BLPEncoder(ImageFile.PyEncoder):
_pushes_fd = True
- def _write_palette(self):
+ 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]
@@ -428,12 +439,13 @@ def _write_palette(self):
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))
@@ -446,7 +458,7 @@ def encode(self, bufsize):
return len(data), 0, data
-def _save(im, fp, filename):
+def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
if im.mode != "P":
msg = "Unsupported BLP image mode"
raise ValueError(msg)
diff --git a/src/PIL/BmpImagePlugin.py b/src/PIL/BmpImagePlugin.py
index 9947f439b07..ef204533712 100644
--- a/src/PIL/BmpImagePlugin.py
+++ b/src/PIL/BmpImagePlugin.py
@@ -25,6 +25,7 @@
from __future__ import annotations
import os
+from typing import IO
from . import Image, ImageFile, ImagePalette
from ._binary import i16le as i16
@@ -48,12 +49,12 @@
}
-def _accept(prefix):
+def _accept(prefix: bytes) -> bool:
return prefix[:2] == b"BM"
-def _dib_accept(prefix):
- return i32(prefix) in [12, 40, 64, 108, 124]
+def _dib_accept(prefix: bytes) -> bool:
+ return i32(prefix) in [12, 40, 52, 56, 64, 108, 124]
# =============================================================================
@@ -83,8 +84,9 @@ def _bitmap(self, header=0, offset=0):
# read the rest of the bmp header, without its size
header_data = ImageFile._safe_read(self.fp, file_info["header_size"] - 4)
- # -------------------------------------------------- IBM OS/2 Bitmap v1
+ # ------------------------------- Windows Bitmap v2, IBM OS/2 Bitmap v1
# ----- This format has different offsets because of width/height types
+ # 12: BITMAPCOREHEADER/OS21XBITMAPHEADER
if file_info["header_size"] == 12:
file_info["width"] = i16(header_data, 0)
file_info["height"] = i16(header_data, 2)
@@ -93,9 +95,14 @@ def _bitmap(self, header=0, offset=0):
file_info["compression"] = self.RAW
file_info["palette_padding"] = 3
- # --------------------------------------------- Windows Bitmap v2 to v5
- # v3, OS/2 v2, v4, v5
- elif file_info["header_size"] in (40, 64, 108, 124):
+ # --------------------------------------------- Windows Bitmap v3 to v5
+ # 40: BITMAPINFOHEADER
+ # 52: BITMAPV2HEADER
+ # 56: BITMAPV3HEADER
+ # 64: BITMAPCOREHEADER2/OS22XBITMAPHEADER
+ # 108: BITMAPV4HEADER
+ # 124: BITMAPV5HEADER
+ elif file_info["header_size"] in (40, 52, 56, 64, 108, 124):
file_info["y_flip"] = header_data[7] == 0xFF
file_info["direction"] = 1 if file_info["y_flip"] else -1
file_info["width"] = i32(header_data, 0)
@@ -117,10 +124,13 @@ def _bitmap(self, header=0, offset=0):
file_info["palette_padding"] = 4
self.info["dpi"] = tuple(x / 39.3701 for x in file_info["pixels_per_meter"])
if file_info["compression"] == self.BITFIELDS:
- if len(header_data) >= 52:
- for idx, mask in enumerate(
- ["r_mask", "g_mask", "b_mask", "a_mask"]
- ):
+ masks = ["r_mask", "g_mask", "b_mask"]
+ if len(header_data) >= 48:
+ if len(header_data) >= 52:
+ masks.append("a_mask")
+ else:
+ file_info["a_mask"] = 0x0
+ for idx, mask in enumerate(masks):
file_info[mask] = i32(header_data, 36 + idx * 4)
else:
# 40 byte headers only have the three components in the
@@ -132,7 +142,7 @@ def _bitmap(self, header=0, offset=0):
# location, but it is listed as a reserved component,
# and it is not generally an alpha channel
file_info["a_mask"] = 0x0
- for mask in ["r_mask", "g_mask", "b_mask"]:
+ for mask in masks:
file_info[mask] = i32(read(4))
file_info["rgb_mask"] = (
file_info["r_mask"],
@@ -175,9 +185,11 @@ def _bitmap(self, header=0, offset=0):
32: [
(0xFF0000, 0xFF00, 0xFF, 0x0),
(0xFF000000, 0xFF0000, 0xFF00, 0x0),
+ (0xFF000000, 0xFF00, 0xFF, 0x0),
(0xFF000000, 0xFF0000, 0xFF00, 0xFF),
(0xFF, 0xFF00, 0xFF0000, 0xFF000000),
(0xFF0000, 0xFF00, 0xFF, 0xFF000000),
+ (0xFF000000, 0xFF00, 0xFF, 0xFF0000),
(0x0, 0x0, 0x0, 0x0),
],
24: [(0xFF0000, 0xFF00, 0xFF)],
@@ -186,9 +198,11 @@ def _bitmap(self, header=0, offset=0):
MASK_MODES = {
(32, (0xFF0000, 0xFF00, 0xFF, 0x0)): "BGRX",
(32, (0xFF000000, 0xFF0000, 0xFF00, 0x0)): "XBGR",
+ (32, (0xFF000000, 0xFF00, 0xFF, 0x0)): "BGXR",
(32, (0xFF000000, 0xFF0000, 0xFF00, 0xFF)): "ABGR",
(32, (0xFF, 0xFF00, 0xFF0000, 0xFF000000)): "RGBA",
(32, (0xFF0000, 0xFF00, 0xFF, 0xFF000000)): "BGRA",
+ (32, (0xFF000000, 0xFF00, 0xFF, 0xFF0000)): "BGAR",
(32, (0x0, 0x0, 0x0, 0x0)): "BGRA",
(24, (0xFF0000, 0xFF00, 0xFF)): "BGR",
(16, (0xF800, 0x7E0, 0x1F)): "BGR;16",
@@ -270,7 +284,7 @@ def _bitmap(self, header=0, offset=0):
)
]
- def _open(self):
+ def _open(self) -> None:
"""Open file, check magic number and read header"""
# read 14 bytes: magic number, filesize, reserved, header final offset
head_data = self.fp.read(14)
@@ -287,7 +301,8 @@ def _open(self):
class BmpRleDecoder(ImageFile.PyDecoder):
_pulls_fd = True
- def decode(self, buffer):
+ def decode(self, buffer: bytes) -> tuple[int, int]:
+ assert self.fd is not None
rle4 = self.args[1]
data = bytearray()
x = 0
@@ -363,7 +378,7 @@ class DibImageFile(BmpImageFile):
format = "DIB"
format_description = "Windows Bitmap"
- def _open(self):
+ def _open(self) -> None:
self._bitmap()
@@ -381,11 +396,13 @@ def _open(self):
}
-def _dib_save(im, fp, filename):
+def _dib_save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
_save(im, fp, filename, False)
-def _save(im, fp, filename, bitmap_header=True):
+def _save(
+ im: Image.Image, fp: IO[bytes], filename: str | bytes, bitmap_header: bool = True
+) -> None:
try:
rawmode, bits, colors = SAVE[im.mode]
except KeyError as e:
diff --git a/src/PIL/BufrStubImagePlugin.py b/src/PIL/BufrStubImagePlugin.py
index 60f3ec25b38..0ee2f653b2c 100644
--- a/src/PIL/BufrStubImagePlugin.py
+++ b/src/PIL/BufrStubImagePlugin.py
@@ -10,12 +10,14 @@
#
from __future__ import annotations
+from typing import IO
+
from . import Image, ImageFile
_handler = None
-def register_handler(handler):
+def register_handler(handler: ImageFile.StubHandler | None) -> None:
"""
Install application-specific BUFR image handler.
@@ -29,7 +31,7 @@ def register_handler(handler):
# Image adapter
-def _accept(prefix):
+def _accept(prefix: bytes) -> bool:
return prefix[:4] == b"BUFR" or prefix[:4] == b"ZCZC"
@@ -37,7 +39,7 @@ class BufrStubImageFile(ImageFile.StubImageFile):
format = "BUFR"
format_description = "BUFR"
- def _open(self):
+ def _open(self) -> None:
offset = self.fp.tell()
if not _accept(self.fp.read(4)):
@@ -54,11 +56,11 @@ def _open(self):
if loader:
loader.open(self)
- def _load(self):
+ def _load(self) -> ImageFile.StubHandler | None:
return _handler
-def _save(im, fp, filename):
+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/CurImagePlugin.py b/src/PIL/CurImagePlugin.py
index 5fb2b0193ca..85e2145e766 100644
--- a/src/PIL/CurImagePlugin.py
+++ b/src/PIL/CurImagePlugin.py
@@ -25,7 +25,7 @@
# --------------------------------------------------------------------
-def _accept(prefix):
+def _accept(prefix: bytes) -> bool:
return prefix[:4] == b"\0\0\2\0"
@@ -37,7 +37,7 @@ class CurImageFile(BmpImagePlugin.BmpImageFile):
format = "CUR"
format_description = "Windows Cursor"
- def _open(self):
+ def _open(self) -> None:
offset = self.fp.tell()
# check magic
diff --git a/src/PIL/DcxImagePlugin.py b/src/PIL/DcxImagePlugin.py
index f7344df44a7..f67f27d73bb 100644
--- a/src/PIL/DcxImagePlugin.py
+++ b/src/PIL/DcxImagePlugin.py
@@ -29,7 +29,7 @@
MAGIC = 0x3ADE68B1 # QUIZ: what's this value, then?
-def _accept(prefix):
+def _accept(prefix: bytes) -> bool:
return len(prefix) >= 4 and i32(prefix) == MAGIC
@@ -42,7 +42,7 @@ class DcxImageFile(PcxImageFile):
format_description = "Intel DCX"
_close_exclusive_fp_after_loading = False
- def _open(self):
+ def _open(self) -> None:
# Header
s = self.fp.read(4)
if not _accept(s):
@@ -58,12 +58,12 @@ def _open(self):
self._offset.append(offset)
self._fp = self.fp
- self.frame = None
+ self.frame = -1
self.n_frames = len(self._offset)
self.is_animated = self.n_frames > 1
self.seek(0)
- def seek(self, frame):
+ def seek(self, frame: int) -> None:
if not self._seek_check(frame):
return
self.frame = frame
@@ -71,7 +71,7 @@ def seek(self, frame):
self.fp.seek(self._offset[frame])
PcxImageFile._open(self)
- def tell(self):
+ def tell(self) -> int:
return self.frame
diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py
index 93c8e341d44..a57e4aea2d5 100644
--- a/src/PIL/DdsImagePlugin.py
+++ b/src/PIL/DdsImagePlugin.py
@@ -16,6 +16,7 @@
import struct
import sys
from enum import IntEnum, IntFlag
+from typing import IO
from . import Image, ImageFile, ImagePalette
from ._binary import i32le as i32
@@ -271,16 +272,16 @@ class D3DFMT(IntEnum):
module = sys.modules[__name__]
for item in DDSD:
assert item.name is not None
- setattr(module, "DDSD_" + item.name, item.value)
+ setattr(module, f"DDSD_{item.name}", item.value)
for item1 in DDSCAPS:
assert item1.name is not None
- setattr(module, "DDSCAPS_" + item1.name, item1.value)
+ setattr(module, f"DDSCAPS_{item1.name}", item1.value)
for item2 in DDSCAPS2:
assert item2.name is not None
- setattr(module, "DDSCAPS2_" + item2.name, item2.value)
+ setattr(module, f"DDSCAPS2_{item2.name}", item2.value)
for item3 in DDPF:
assert item3.name is not None
- setattr(module, "DDPF_" + item3.name, item3.value)
+ setattr(module, f"DDPF_{item3.name}", item3.value)
DDS_FOURCC = DDPF.FOURCC
DDS_RGB = DDPF.RGB
@@ -331,7 +332,7 @@ class DdsImageFile(ImageFile.ImageFile):
format = "DDS"
format_description = "DirectDraw Surface"
- def _open(self):
+ def _open(self) -> None:
if not _accept(self.fp.read(4)):
msg = "not a DDS file"
raise SyntaxError(msg)
@@ -379,6 +380,7 @@ def _open(self):
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:
@@ -472,14 +474,15 @@ def _open(self):
else:
self.tile = [ImageFile._Tile("raw", extents, 0, rawmode or self.mode)]
- def load_seek(self, pos):
+ def load_seek(self, pos: int) -> None:
pass
class DdsRgbDecoder(ImageFile.PyDecoder):
_pulls_fd = True
- def decode(self, buffer):
+ def decode(self, buffer: bytes) -> 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
@@ -510,7 +513,7 @@ def decode(self, buffer):
return -1, 0
-def _save(im, fp, filename):
+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)
@@ -562,7 +565,7 @@ def _save(im, fp, filename):
)
-def _accept(prefix):
+def _accept(prefix: bytes) -> bool:
return prefix[:4] == b"DDS "
diff --git a/src/PIL/EpsImagePlugin.py b/src/PIL/EpsImagePlugin.py
index 523ffcbf7ee..f31b1c1a2d0 100644
--- a/src/PIL/EpsImagePlugin.py
+++ b/src/PIL/EpsImagePlugin.py
@@ -27,6 +27,7 @@
import subprocess
import sys
import tempfile
+from typing import IO
from . import Image, ImageFile
from ._binary import i32le as i32
@@ -42,7 +43,7 @@
gs_windows_binary = None
-def has_ghostscript():
+def has_ghostscript() -> bool:
global gs_binary, gs_windows_binary
if gs_binary is None:
if sys.platform.startswith("win"):
@@ -178,7 +179,7 @@ def seek(self, offset, whence=io.SEEK_SET):
self.char = None
self.fp.seek(offset, whence)
- def readline(self):
+ def readline(self) -> str:
s = [self.char or b""]
self.char = None
@@ -195,7 +196,7 @@ def readline(self):
return b"".join(s).decode("latin-1")
-def _accept(prefix):
+def _accept(prefix: bytes) -> bool:
return prefix[:4] == b"%!PS" or (len(prefix) >= 4 and i32(prefix) == 0xC6D3D0C5)
@@ -212,7 +213,7 @@ class EpsImageFile(ImageFile.ImageFile):
mode_map = {1: "L", 2: "LAB", 3: "RGB", 4: "CMYK"}
- def _open(self):
+ def _open(self) -> None:
(length, offset) = self._find_offset(self.fp)
# go to offset - start of "%!PS"
@@ -228,7 +229,12 @@ def _open(self):
reading_trailer_comments = False
trailer_reached = False
- def check_required_header_comments():
+ 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,7 +242,7 @@ def check_required_header_comments():
msg = 'EPS header missing "%%BoundingBox" comment'
raise SyntaxError(msg)
- def _read_comment(s):
+ def _read_comment(s: str) -> bool:
nonlocal reading_trailer_comments
try:
m = split.match(s)
@@ -244,33 +250,33 @@ def _read_comment(s):
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 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
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
@@ -366,8 +372,6 @@ def _read_comment(s):
trailer_reached = True
bytes_read = 0
- check_required_header_comments()
-
if not self._size:
msg = "cannot determine EPS bounding box"
raise OSError(msg)
@@ -404,7 +408,7 @@ def load(self, scale=1, transparency=False):
self.tile = []
return Image.Image.load(self)
- def load_seek(self, pos):
+ def load_seek(self, pos: int) -> None:
# we can't incrementally load, so force ImageFile.parser to
# use our custom load method by defining this method.
pass
@@ -413,7 +417,7 @@ def load_seek(self, pos):
# --------------------------------------------------------------------
-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
diff --git a/src/PIL/ExifTags.py b/src/PIL/ExifTags.py
index 60a4d9774ae..39b4aa55262 100644
--- a/src/PIL/ExifTags.py
+++ b/src/PIL/ExifTags.py
@@ -346,7 +346,7 @@ class Interop(IntEnum):
InteropVersion = 2
RelatedImageFileFormat = 4096
RelatedImageWidth = 4097
- RleatedImageHeight = 4098
+ RelatedImageHeight = 4098
class IFD(IntEnum):
diff --git a/src/PIL/FitsImagePlugin.py b/src/PIL/FitsImagePlugin.py
index 07191892506..4846054b1e4 100644
--- a/src/PIL/FitsImagePlugin.py
+++ b/src/PIL/FitsImagePlugin.py
@@ -115,14 +115,18 @@ def _parse_headers(
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) -> 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 f9e4c731c88..dceb839279a 100644
--- a/src/PIL/FliImagePlugin.py
+++ b/src/PIL/FliImagePlugin.py
@@ -27,7 +27,7 @@
# decoder
-def _accept(prefix):
+def _accept(prefix: bytes) -> bool:
return (
len(prefix) >= 6
and i16(prefix, 4) in [0xAF11, 0xAF12]
@@ -123,7 +123,7 @@ def _palette(self, palette, shift):
palette[i] = (r, g, b)
i += 1
- def seek(self, frame):
+ def seek(self, frame: int) -> None:
if not self._seek_check(frame):
return
if frame < self.__frame:
@@ -132,7 +132,7 @@ def seek(self, frame):
for f in range(self.__frame + 1, frame + 1):
self._seek(f)
- def _seek(self, frame):
+ def _seek(self, frame: int) -> None:
if frame == 0:
self.__frame = -1
self._fp.seek(self.__rewind)
@@ -162,7 +162,7 @@ def _seek(self, frame):
self.__offset += framesize
- def tell(self):
+ def tell(self) -> int:
return self.__frame
diff --git a/src/PIL/FpxImagePlugin.py b/src/PIL/FpxImagePlugin.py
index 75680a94e02..c1927bd26aa 100644
--- a/src/PIL/FpxImagePlugin.py
+++ b/src/PIL/FpxImagePlugin.py
@@ -41,7 +41,7 @@
# --------------------------------------------------------------------
-def _accept(prefix):
+def _accept(prefix: bytes) -> bool:
return prefix[:8] == olefile.MAGIC
@@ -70,7 +70,7 @@ def _open(self):
self._open_index(1)
- def _open_index(self, index=1):
+ def _open_index(self, index: int = 1) -> None:
#
# get the Image Contents Property Set
@@ -85,7 +85,7 @@ def _open_index(self, index=1):
size = max(self.size)
i = 1
while size > 64:
- size = size / 2
+ size = size // 2
i += 1
self.maxid = i - 1
@@ -118,7 +118,7 @@ def _open_index(self, index=1):
self._open_subimage(1, self.maxid)
- def _open_subimage(self, index=1, subimage=0):
+ def _open_subimage(self, index: int = 1, subimage: int = 0) -> None:
#
# setup tile descriptors for a given subimage
@@ -237,11 +237,11 @@ def load(self):
return ImageFile.ImageFile.load(self)
- def close(self):
+ def close(self) -> None:
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 b4488e6ee9c..5acbb4912f7 100644
--- a/src/PIL/FtexImagePlugin.py
+++ b/src/PIL/FtexImagePlugin.py
@@ -71,7 +71,7 @@ class FtexImageFile(ImageFile.ImageFile):
format = "FTEX"
format_description = "Texture File Format (IW2:EOC)"
- def _open(self):
+ def _open(self) -> None:
if not _accept(self.fp.read(4)):
msg = "not an FTEX file"
raise SyntaxError(msg)
@@ -103,11 +103,11 @@ def _open(self):
self.fp.close()
self.fp = BytesIO(data)
- def load_seek(self, pos):
+ def load_seek(self, pos: int) -> None:
pass
-def _accept(prefix):
+def _accept(prefix: bytes) -> bool:
return prefix[:4] == MAGIC
diff --git a/src/PIL/GbrImagePlugin.py b/src/PIL/GbrImagePlugin.py
index 6722fa2b144..93e89b1e655 100644
--- a/src/PIL/GbrImagePlugin.py
+++ b/src/PIL/GbrImagePlugin.py
@@ -29,7 +29,7 @@
from ._binary import i32be as i32
-def _accept(prefix):
+def _accept(prefix: bytes) -> bool:
return len(prefix) >= 8 and i32(prefix, 0) >= 20 and i32(prefix, 4) in (1, 2)
@@ -41,7 +41,7 @@ class GbrImageFile(ImageFile.ImageFile):
format = "GBR"
format_description = "GIMP brush file"
- def _open(self):
+ def _open(self) -> None:
header_size = i32(self.fp.read(4))
if header_size < 20:
msg = "not a GIMP brush"
diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py
index 6b415d2384a..284128c773f 100644
--- a/src/PIL/GifImagePlugin.py
+++ b/src/PIL/GifImagePlugin.py
@@ -29,7 +29,10 @@
import math
import os
import subprocess
+import sys
from enum import IntEnum
+from functools import cached_property
+from typing import IO, TYPE_CHECKING, Any, List, Literal, NamedTuple, Union
from . import (
Image,
@@ -44,6 +47,9 @@
from ._binary import o8
from ._binary import o16le as o16
+if TYPE_CHECKING:
+ from . import _imaging
+
class LoadingStrategy(IntEnum):
""".. versionadded:: 9.1.0"""
@@ -60,7 +66,7 @@ class LoadingStrategy(IntEnum):
# Identify/read GIF files
-def _accept(prefix):
+def _accept(prefix: bytes) -> bool:
return prefix[:6] in [b"GIF87a", b"GIF89a"]
@@ -76,19 +82,19 @@ class GifImageFile(ImageFile.ImageFile):
global_palette = None
- def data(self):
+ def data(self) -> bytes | None:
s = self.fp.read(1)
if s and s[0]:
return self.fp.read(s[0])
return None
- def _is_palette_needed(self, p):
+ def _is_palette_needed(self, p: bytes) -> bool:
for i in range(0, len(p), 3):
if not (i // 3 == p[i] == p[i + 1] == p[i + 2]):
return True
return False
- def _open(self):
+ def _open(self) -> None:
# Screen
s = self.fp.read(13)
if not _accept(s):
@@ -112,12 +118,11 @@ def _open(self):
self._fp = self.fp # FIXME: hack
self.__rewind = self.fp.tell()
- self._n_frames = None
- self._is_animated = None
+ self._n_frames: int | None = None
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:
@@ -128,26 +133,25 @@ def n_frames(self):
self.seek(current)
return self._n_frames
- @property
- def is_animated(self):
- if self._is_animated is None:
- if self._n_frames is not None:
- self._is_animated = self._n_frames != 1
- else:
- current = self.tell()
- if current:
- self._is_animated = True
- else:
- try:
- self._seek(1, False)
- self._is_animated = True
- except EOFError:
- self._is_animated = False
+ @cached_property
+ def is_animated(self) -> bool:
+ if self._n_frames is not None:
+ return self._n_frames != 1
- self.seek(current)
- return self._is_animated
+ current = self.tell()
+ if current:
+ return True
- def seek(self, frame):
+ try:
+ self._seek(1, False)
+ is_animated = True
+ except EOFError:
+ is_animated = False
+
+ self.seek(current)
+ return is_animated
+
+ def seek(self, frame: int) -> None:
if not self._seek_check(frame):
return
if frame < self.__frame:
@@ -163,11 +167,11 @@ def seek(self, frame):
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 +199,9 @@ def _seek(self, frame, update_image=True):
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 +217,7 @@ def _seek(self, frame, update_image=True):
#
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 +253,14 @@ def _seek(self, frame, update_image=True):
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
@@ -337,60 +341,60 @@ def _seek(self, frame, update_image=True):
self._mode = "RGB"
self.im = self.im.convert("RGB", Image.Dither.FLOYDSTEINBERG)
- def _rgb(color):
+ def _rgb(color: int) -> tuple[int, int, int]:
if self._frame_palette:
if color * 3 + 3 > len(self._frame_palette.palette):
color = 0
- color = tuple(self._frame_palette.palette[color * 3 : color * 3 + 3])
+ return tuple(self._frame_palette.palette[color * 3 : color * 3 + 3])
else:
- color = (color, color, color)
- return color
+ 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
-
- # 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:
+ 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
- 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
@@ -417,7 +421,7 @@ def _rgb(color):
elif k in self.info:
del self.info[k]
- def load_prepare(self):
+ def load_prepare(self) -> None:
temp_mode = "P" if self._frame_palette else "L"
self._prev_im = None
if self.__frame == 0:
@@ -429,7 +433,7 @@ def load_prepare(self):
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._mode = temp_mode
@@ -437,7 +441,7 @@ def load_prepare(self):
super().load_prepare()
- def load_end(self):
+ def load_end(self) -> None:
if self.__frame == 0:
if self.mode == "P" and LOADING_STRATEGY == LoadingStrategy.RGB_ALWAYS:
if self._frame_transparency is not None:
@@ -454,6 +458,8 @@ def load_end(self):
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
@@ -463,7 +469,7 @@ def load_end(self):
else:
self.im.paste(frame_im, self.dispose_extent)
- def tell(self):
+ def tell(self) -> int:
return self.__frame
@@ -474,7 +480,7 @@ def tell(self):
RAWMODE = {"1": "L", "L": "L", "P": "P"}
-def _normalize_mode(im):
+def _normalize_mode(im: Image.Image) -> Image.Image:
"""
Takes an image (or frame), returns an image in a mode that is appropriate
for saving in a Gif.
@@ -499,7 +505,12 @@ def _normalize_mode(im):
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.
@@ -527,8 +538,10 @@ def _normalize_palette(im, palette, info):
source_palette = bytearray(i // 3 for i in range(768))
im.palette = ImagePalette.ImagePalette("RGB", palette=source_palette)
+ used_palette_colors: list[int] | None
if palette:
used_palette_colors = []
+ assert source_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)
@@ -559,7 +572,11 @@ def _normalize_palette(im, palette, info):
return im
-def _write_single_frame(im, fp, palette):
+def _write_single_frame(
+ im: Image.Image,
+ fp: IO[bytes],
+ palette: _Palette | None,
+) -> None:
im_out = _normalize_mode(im)
for k, v in im_out.info.items():
im.encoderinfo.setdefault(k, v)
@@ -580,7 +597,9 @@ def _write_single_frame(im, fp, palette):
fp.write(b"\0") # end of image data
-def _getbbox(base_im, im_frame):
+def _getbbox(
+ base_im: Image.Image, im_frame: Image.Image
+) -> tuple[Image.Image, tuple[int, int, int, int] | None]:
if _get_palette_bytes(im_frame) != _get_palette_bytes(base_im):
im_frame = im_frame.convert("RGBA")
base_im = base_im.convert("RGBA")
@@ -588,12 +607,20 @@ def _getbbox(base_im, im_frame):
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", [])):
@@ -619,24 +646,22 @@ 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)
+ background_im.putpalette(im_frames[0].im.palette)
bbox = _getbbox(background_im, im_frame)[1]
elif encoderinfo.get("optimize") and im_frame.mode != "1":
if "transparency" not in encoderinfo:
@@ -682,39 +707,39 @@ 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, fp, filename):
+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, save_all=False):
+def _save(
+ 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:
palette = im.encoderinfo.get("palette", im.info.get("palette"))
@@ -731,7 +756,7 @@ def _save(im, fp, filename, save_all=False):
fp.flush()
-def get_interlace(im):
+def get_interlace(im: Image.Image) -> int:
interlace = im.encoderinfo.get("interlace", 1)
# workaround for @PIL153
@@ -741,7 +766,9 @@ def get_interlace(im):
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:
@@ -789,7 +816,7 @@ def _write_local_header(fp, im, offset, flags):
fp.write(o8(8)) # bits
-def _save_netpbm(im, fp, filename):
+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.
#
@@ -820,6 +847,7 @@ def _save_netpbm(im, fp, filename):
)
# Allow ppmquant to receive SIGPIPE if ppmtogif exits
+ assert quant_proc.stdout is not None
quant_proc.stdout.close()
retcode = quant_proc.wait()
@@ -841,7 +869,7 @@ def _save_netpbm(im, fp, filename):
_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.
@@ -885,9 +913,10 @@ def _get_optimize(im, info):
and current_palette_size > 2
):
return used_palette_colors
+ return None
-def _get_color_table_size(palette_bytes):
+def _get_color_table_size(palette_bytes: bytes) -> int:
# calculate the palette size for the header
if not palette_bytes:
return 0
@@ -897,7 +926,7 @@ def _get_color_table_size(palette_bytes):
return math.ceil(math.log(len(palette_bytes) // 3, 2)) - 1
-def _get_header_palette(palette_bytes):
+def _get_header_palette(palette_bytes: bytes) -> bytes:
"""
Returns the palette, null padded to the next power of 2 (*3) bytes
suitable for direct inclusion in the GIF header
@@ -915,7 +944,7 @@ def _get_header_palette(palette_bytes):
return palette_bytes
-def _get_palette_bytes(im):
+def _get_palette_bytes(im: Image.Image) -> bytes:
"""
Gets the palette for inclusion in the gif header
@@ -925,7 +954,10 @@ def _get_palette_bytes(im):
return im.palette.palette if im.palette else b""
-def _get_background(im, info_background):
+def _get_background(
+ im: Image.Image,
+ info_background: int | tuple[int, int, int] | tuple[int, int, int, int] | None,
+) -> int:
background = 0
if info_background:
if isinstance(info_background, tuple):
@@ -948,7 +980,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
@@ -1010,7 +1042,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
@@ -1030,7 +1067,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.
@@ -1042,11 +1081,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"]
@@ -1058,7 +1097,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
@@ -1075,12 +1116,23 @@ 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):
- self.data.append(data)
+ if sys.version_info >= (3, 12):
+ from collections.abc import Buffer
+
+ def write(self, data: Buffer) -> int:
+ self.data.append(data)
+ return len(data)
+
+ else:
+
+ def write(self, data: Any) -> int:
+ self.data.append(data)
+ return len(data)
im.load() # make sure raster data is available
diff --git a/src/PIL/GimpGradientFile.py b/src/PIL/GimpGradientFile.py
index 2d8c78ea91a..220eac57e38 100644
--- a/src/PIL/GimpGradientFile.py
+++ b/src/PIL/GimpGradientFile.py
@@ -21,6 +21,7 @@
from __future__ import annotations
from math import log, pi, sin, sqrt
+from typing import IO, Callable
from ._binary import o8
@@ -28,7 +29,7 @@
"""""" # Enable auto-doc for data member
-def linear(middle, pos):
+def linear(middle: float, pos: float) -> float:
if pos <= middle:
if middle < EPSILON:
return 0.0
@@ -43,19 +44,19 @@ def linear(middle, pos):
return 0.5 + 0.5 * pos / middle
-def curved(middle, pos):
+def curved(middle: float, pos: float) -> float:
return pos ** (log(0.5) / log(max(middle, EPSILON)))
-def sine(middle, pos):
+def sine(middle: float, pos: float) -> float:
return (sin((-pi / 2.0) + pi * linear(middle, pos)) + 1.0) / 2.0
-def sphere_increasing(middle, pos):
+def sphere_increasing(middle: float, pos: float) -> float:
return sqrt(1.0 - (linear(middle, pos) - 1.0) ** 2)
-def sphere_decreasing(middle, pos):
+def sphere_decreasing(middle: float, pos: float) -> float:
return 1.0 - sqrt(1.0 - linear(middle, pos) ** 2)
@@ -64,9 +65,22 @@ def sphere_decreasing(middle, pos):
class GradientFile:
- gradient = None
-
- def getpalette(self, entries=256):
+ gradient: (
+ list[
+ tuple[
+ float,
+ float,
+ float,
+ list[float],
+ list[float],
+ Callable[[float, float], float],
+ ]
+ ]
+ | None
+ ) = None
+
+ def getpalette(self, entries: int = 256) -> tuple[bytes, str]:
+ assert self.gradient is not None
palette = []
ix = 0
@@ -101,7 +115,7 @@ def getpalette(self, entries=256):
class GimpGradientFile(GradientFile):
"""File handler for GIMP's gradient format."""
- def __init__(self, fp):
+ def __init__(self, fp: IO[bytes]) -> None:
if fp.readline()[:13] != b"GIMP Gradient":
msg = "not a GIMP gradient file"
raise SyntaxError(msg)
@@ -114,7 +128,7 @@ def __init__(self, fp):
count = int(line)
- gradient = []
+ self.gradient = []
for i in range(count):
s = fp.readline().split()
@@ -132,6 +146,4 @@ def __init__(self, fp):
msg = "cannot handle HSV colour space"
raise OSError(msg)
- gradient.append((x0, x1, xm, rgb0, rgb1, segment))
-
- self.gradient = gradient
+ self.gradient.append((x0, x1, xm, rgb0, rgb1, segment))
diff --git a/src/PIL/GimpPaletteFile.py b/src/PIL/GimpPaletteFile.py
index a3109ebaa1b..4cad0ebeea1 100644
--- a/src/PIL/GimpPaletteFile.py
+++ b/src/PIL/GimpPaletteFile.py
@@ -16,6 +16,7 @@
from __future__ import annotations
import re
+from typing import IO
from ._binary import o8
@@ -25,8 +26,8 @@ class GimpPaletteFile:
rawmode = "RGB"
- def __init__(self, fp):
- self.palette = [o8(i) * 3 for i in range(256)]
+ def __init__(self, fp: IO[bytes]) -> None:
+ palette = [o8(i) * 3 for i in range(256)]
if fp.readline()[:12] != b"GIMP Palette":
msg = "not a GIMP palette file"
@@ -49,9 +50,9 @@ def __init__(self, fp):
msg = "bad palette entry"
raise ValueError(msg)
- self.palette[i] = o8(v[0]) + o8(v[1]) + o8(v[2])
+ palette[i] = o8(v[0]) + o8(v[1]) + o8(v[2])
- self.palette = b"".join(self.palette)
+ self.palette = b"".join(palette)
- def getpalette(self):
+ def getpalette(self) -> tuple[bytes, str]:
return self.palette, self.rawmode
diff --git a/src/PIL/GribStubImagePlugin.py b/src/PIL/GribStubImagePlugin.py
index f8106800c42..e9aa084b281 100644
--- a/src/PIL/GribStubImagePlugin.py
+++ b/src/PIL/GribStubImagePlugin.py
@@ -10,12 +10,14 @@
#
from __future__ import annotations
+from typing import IO
+
from . import Image, ImageFile
_handler = None
-def register_handler(handler):
+def register_handler(handler: ImageFile.StubHandler | None) -> None:
"""
Install application-specific GRIB image handler.
@@ -29,7 +31,7 @@ def register_handler(handler):
# Image adapter
-def _accept(prefix):
+def _accept(prefix: bytes) -> bool:
return prefix[:4] == b"GRIB" and prefix[7] == 1
@@ -37,7 +39,7 @@ class GribStubImageFile(ImageFile.StubImageFile):
format = "GRIB"
format_description = "GRIB"
- def _open(self):
+ def _open(self) -> None:
offset = self.fp.tell()
if not _accept(self.fp.read(8)):
@@ -54,11 +56,11 @@ def _open(self):
if loader:
loader.open(self)
- def _load(self):
+ def _load(self) -> ImageFile.StubHandler | None:
return _handler
-def _save(im, fp, filename):
+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 65409e269fc..cc9e73deb80 100644
--- a/src/PIL/Hdf5StubImagePlugin.py
+++ b/src/PIL/Hdf5StubImagePlugin.py
@@ -10,12 +10,14 @@
#
from __future__ import annotations
+from typing import IO
+
from . import Image, ImageFile
_handler = None
-def register_handler(handler):
+def register_handler(handler: ImageFile.StubHandler | None) -> None:
"""
Install application-specific HDF5 image handler.
@@ -29,7 +31,7 @@ def register_handler(handler):
# Image adapter
-def _accept(prefix):
+def _accept(prefix: bytes) -> bool:
return prefix[:8] == b"\x89HDF\r\n\x1a\n"
@@ -37,7 +39,7 @@ class HDF5StubImageFile(ImageFile.StubImageFile):
format = "HDF5"
format_description = "HDF5"
- def _open(self):
+ def _open(self) -> None:
offset = self.fp.tell()
if not _accept(self.fp.read(8)):
@@ -54,11 +56,11 @@ def _open(self):
if loader:
loader.open(self)
- def _load(self):
+ def _load(self) -> ImageFile.StubHandler | None:
return _handler
-def _save(im, fp, filename):
+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 d877b4ecba6..2a89d498cbf 100644
--- a/src/PIL/IcnsImagePlugin.py
+++ b/src/PIL/IcnsImagePlugin.py
@@ -22,6 +22,7 @@
import os
import struct
import sys
+from typing import IO
from . import Image, ImageFile, PngImagePlugin, features
@@ -252,7 +253,7 @@ class IcnsImageFile(ImageFile.ImageFile):
format = "ICNS"
format_description = "Mac OS icns resource"
- def _open(self):
+ def _open(self) -> None:
self.icns = IcnsFile(self.fp)
self._mode = "RGBA"
self.info["sizes"] = self.icns.itersizes()
@@ -312,7 +313,7 @@ def load(self):
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,35 +347,33 @@ 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()
-def _accept(prefix):
+def _accept(prefix: bytes) -> bool:
return prefix[:4] == MAGIC
diff --git a/src/PIL/IcoImagePlugin.py b/src/PIL/IcoImagePlugin.py
index d66fbc28797..227fcf35cbb 100644
--- a/src/PIL/IcoImagePlugin.py
+++ b/src/PIL/IcoImagePlugin.py
@@ -25,6 +25,7 @@
import warnings
from io import BytesIO
from math import ceil, log
+from typing import IO
from . import BmpImagePlugin, Image, ImageFile, PngImagePlugin
from ._binary import i16le as i16
@@ -39,7 +40,7 @@
_MAGIC = b"\0\0\1\0"
-def _save(im, fp, filename):
+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(
@@ -114,7 +115,7 @@ def _save(im, fp, filename):
fp.seek(current)
-def _accept(prefix):
+def _accept(prefix: bytes) -> bool:
return prefix[:4] == _MAGIC
@@ -194,7 +195,7 @@ def getimage(self, size, bpp=False):
"""
return self.frame(self.getentryindex(size, bpp))
- def frame(self, idx):
+ def frame(self, idx: int) -> Image.Image:
"""
Get an image from frame idx
"""
@@ -205,6 +206,7 @@ def frame(self, idx):
data = self.buf.read(8)
self.buf.seek(header["offset"])
+ im: Image.Image
if data[:8] == PngImagePlugin._MAGIC:
# png frame
im = PngImagePlugin.PngImageFile(self.buf)
@@ -302,7 +304,7 @@ class IcoImageFile(ImageFile.ImageFile):
format = "ICO"
format_description = "Windows Icon"
- def _open(self):
+ def _open(self) -> None:
self.ico = IcoFile(self.fp)
self.info["sizes"] = self.ico.sizes()
self.size = self.ico.entry[0]["dim"]
@@ -341,7 +343,7 @@ def load(self):
self.size = im.size
- def load_seek(self, pos):
+ def load_seek(self, pos: int) -> None:
# Flag the ImageFile.Parser so that it
# just does all the decode at the end.
pass
diff --git a/src/PIL/ImImagePlugin.py b/src/PIL/ImImagePlugin.py
index 4613e40b60f..2fb7ecd52a0 100644
--- a/src/PIL/ImImagePlugin.py
+++ b/src/PIL/ImImagePlugin.py
@@ -28,6 +28,7 @@
import os
import re
+from typing import IO, Any
from . import Image, ImageFile, ImagePalette
@@ -78,7 +79,7 @@
"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"),
}
@@ -103,7 +104,7 @@
split = re.compile(rb"^([A-Za-z][^:]*):[ \t]*(.*)[ \t]*$")
-def number(s):
+def number(s: Any) -> float:
try:
return int(s)
except ValueError:
@@ -119,7 +120,7 @@ class ImImageFile(ImageFile.ImageFile):
format_description = "IFUNC Image Memory"
_close_exclusive_fp_after_loading = False
- def _open(self):
+ def _open(self) -> None:
# Quick rejection: if there's not an LF among the first
# 100 bytes, this is (probably) not a text header.
@@ -196,7 +197,7 @@ def _open(self):
n += 1
else:
- msg = "Syntax error in IM header: " + s.decode("ascii", "replace")
+ msg = f"Syntax error in IM header: {s.decode('ascii', 'replace')}"
raise SyntaxError(msg)
if not n:
@@ -271,14 +272,14 @@ def _open(self):
self.tile = [("raw", (0, 0) + self.size, offs, (self.rawmode, 0, -1))]
@property
- def n_frames(self):
+ def n_frames(self) -> int:
return self.info[FRAMES]
@property
- def is_animated(self):
+ def is_animated(self) -> bool:
return self.info[FRAMES] > 1
- def seek(self, frame):
+ def seek(self, frame: int) -> None:
if not self._seek_check(frame):
return
@@ -296,7 +297,7 @@ def seek(self, frame):
self.tile = [("raw", (0, 0) + self.size, offs, (self.rawmode, 0, -1))]
- def tell(self):
+ def tell(self) -> int:
return self.frame
@@ -325,7 +326,7 @@ def tell(self):
}
-def _save(im, fp, filename):
+def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
try:
image_type, rawmode = SAVE[im.mode]
except KeyError as e:
@@ -340,6 +341,8 @@ def _save(im, fp, filename):
# 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])
diff --git a/src/PIL/Image.py b/src/PIL/Image.py
index baef0aa112e..d41c06523f4 100644
--- a/src/PIL/Image.py
+++ b/src/PIL/Image.py
@@ -41,7 +41,16 @@
from collections.abc import Callable, MutableMapping
from enum import IntEnum
from types import ModuleType
-from typing import IO, TYPE_CHECKING, Any
+from typing import (
+ IO,
+ TYPE_CHECKING,
+ Any,
+ Literal,
+ Protocol,
+ Sequence,
+ Tuple,
+ cast,
+)
# VERSION was removed in Pillow 6.0.0.
# PILLOW_VERSION was removed in Pillow 9.0.0.
@@ -55,7 +64,8 @@
_plugins,
)
from ._binary import i32le, o32be, o32le
-from ._typing import TypeGuard
+from ._deprecate import deprecate
+from ._typing import StrOrBytesPath, TypeGuard
from ._util import DeferredError, is_path
ElementTree: ModuleType | None
@@ -75,6 +85,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)
@@ -217,13 +229,13 @@ class Quantize(IntEnum):
# Registries
if TYPE_CHECKING:
- from . import ImageFile
+ from . import ImageFile, PyAccess
ID: list[str] = []
OPEN: dict[
str,
tuple[
Callable[[IO[bytes], str | bytes], ImageFile.ImageFile],
- Callable[[bytes], bool] | None,
+ Callable[[bytes], bool | str] | None,
],
] = {}
MIME: dict[str, str] = {}
@@ -248,7 +260,28 @@ def _conv_type_shape(im):
return shape, m.typestr
-MODES = ["1", "CMYK", "F", "HSV", "I", "L", "LAB", "P", "RGB", "RGBA", "RGBX", "YCbCr"]
+MODES = [
+ "1",
+ "CMYK",
+ "F",
+ "HSV",
+ "I",
+ "I;16",
+ "I;16B",
+ "I;16L",
+ "I;16N",
+ "L",
+ "LA",
+ "La",
+ "LAB",
+ "P",
+ "PA",
+ "RGB",
+ "RGBA",
+ "RGBa",
+ "RGBX",
+ "YCbCr",
+]
# raw modes that may be memory mapped. NOTE: if you change this, you
# may have to modify the stride calculation in map.c too!
@@ -357,7 +390,7 @@ def preinit() -> None:
_initialized = 1
-def init():
+def init() -> bool:
"""
Explicitly initializes the Python Imaging Library. This function
loads all available file format drivers.
@@ -368,7 +401,7 @@ def init():
global _initialized
if _initialized >= 2:
- return 0
+ return False
parent_name = __name__.rpartition(".")[0]
for plugin in _plugins:
@@ -380,14 +413,17 @@ def init():
if OPEN or SAVE:
_initialized = 2
- return 1
+ return True
+ return False
# --------------------------------------------------------------------
# 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 = ()
@@ -403,14 +439,16 @@ def _getdecoder(mode, decoder_name, args, extra=()):
try:
# get decoder
- decoder = getattr(core, decoder_name + "_decoder")
+ decoder = getattr(core, f"{decoder_name}_decoder")
except AttributeError as e:
msg = f"decoder {decoder_name} not available"
raise OSError(msg) from e
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 = ()
@@ -426,7 +464,7 @@ def _getencoder(mode, encoder_name, args, extra=()):
try:
# get encoder
- encoder = getattr(core, encoder_name + "_encoder")
+ encoder = getattr(core, f"{encoder_name}_encoder")
except AttributeError as e:
msg = f"encoder {encoder_name} not available"
raise OSError(msg) from e
@@ -480,6 +518,12 @@ def _getscaleoffset(expr):
# Implementation wrapper
+class SupportsGetData(Protocol):
+ def getdata(
+ self,
+ ) -> tuple[Transform, Sequence[int]]: ...
+
+
class Image:
"""
This class represents an image object. To create
@@ -521,10 +565,10 @@ def size(self) -> tuple[int, int]:
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
@@ -597,11 +641,11 @@ def _ensure_mutable(self) -> None:
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:
- suffix = "." + format
+ suffix = f".{format}"
if not file:
f, filename = tempfile.mkstemp(suffix)
@@ -620,10 +664,12 @@ def _dump(
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()
@@ -656,7 +702,7 @@ def _repr_pretty_(self, p, cycle) -> None:
)
)
- 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.
@@ -669,14 +715,14 @@ def _repr_image(self, image_format, **kwargs):
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
@@ -723,7 +769,7 @@ def __setstate__(self, state) -> None:
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.
@@ -745,12 +791,13 @@ def tobytes(self, encoder_name: str = "raw", *args) -> bytes:
: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()
@@ -758,7 +805,7 @@ def tobytes(self, encoder_name: str = "raw", *args) -> bytes:
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
@@ -801,7 +848,9 @@ def tobitmap(self, name: str = "image") -> bytes:
]
)
- def frombytes(self, data: bytes, decoder_name: str = "raw", *args) -> None:
+ def frombytes(
+ self, data: bytes | bytearray, decoder_name: str = "raw", *args: Any
+ ) -> None:
"""
Loads this image with pixel data from a bytes object.
@@ -812,16 +861,17 @@ def frombytes(self, data: bytes, decoder_name: str = "raw", *args) -> None:
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)
@@ -832,7 +882,7 @@ def frombytes(self, data: bytes, decoder_name: str = "raw", *args) -> None:
msg = "cannot decode image data"
raise ValueError(msg)
- def load(self):
+ def load(self) -> core.PixelAccess | PyAccess.PyAccess | None:
"""
Allocates storage for the image and loads the pixel data. In
normal cases, you don't need to call this method, since the
@@ -845,12 +895,12 @@ def load(self):
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` or :py:class:`.PyAccess`
"""
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"):
@@ -860,9 +910,9 @@ def load(self):
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:
@@ -874,8 +924,9 @@ def load(self):
if self.pyaccess:
return self.pyaccess
return self.im.pixel_access(self.readonly)
+ return None
- def verify(self):
+ def verify(self) -> None:
"""
Verifies the contents of a file. For data read from a file, this
method attempts to determine if the file is broken, without
@@ -938,6 +989,9 @@ def convert(
:returns: An :py:class:`~PIL.Image.Image` object.
"""
+ if mode in ("BGR;15", "BGR;16", "BGR;24"):
+ deprecate(mode, 12)
+
self.load()
has_transparency = "transparency" in self.info
@@ -962,9 +1016,11 @@ def convert(
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)
@@ -1058,7 +1114,10 @@ def convert_transparency(m, v):
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.
@@ -1106,7 +1165,7 @@ def convert_transparency(m, v):
# 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)
except ValueError as e:
@@ -1124,7 +1183,7 @@ def convert_transparency(m, v):
def quantize(
self,
colors: int = 256,
- method: Quantize | None = None,
+ method: int | None = None,
kmeans: int = 0,
palette=None,
dither: Dither = Dither.FLOYDSTEINBERG,
@@ -1216,7 +1275,7 @@ def copy(self) -> 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
@@ -1242,7 +1301,9 @@ def crop(self, box: tuple[int, int, int, int] | None = None) -> 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.
@@ -1262,7 +1323,9 @@ def _crop(self, im, box):
return im.crop((x0, y0, x1, y1))
- def draft(self, mode, size):
+ def draft(
+ 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
image that as closely as possible matches the given mode and
@@ -1285,13 +1348,16 @@ def draft(self, mode, size):
"""
pass
- def _expand(self, xmargin, ymargin=None):
+ def _expand(self, xmargin: int, ymargin: int | None = None) -> Image:
if ymargin is None:
ymargin = xmargin
self.load()
return self._new(self.im.expand(xmargin, ymargin))
- def filter(self, filter):
+ 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
available filters, see the :py:mod:`~PIL.ImageFilter` module.
@@ -1303,7 +1369,7 @@ def filter(self, filter):
self.load()
- if isinstance(filter, Callable):
+ if callable(filter):
filter = filter()
if not hasattr(filter, "filter"):
msg = "filter argument should be ImageFilter.Filter instance or class"
@@ -1328,7 +1394,7 @@ def getbands(self) -> tuple[str, ...]:
"""
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.
@@ -1408,8 +1474,15 @@ def getextrema(self) -> tuple[float, float] | tuple[tuple[int, int], ...]:
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):
+ """
+ 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):
@@ -1435,9 +1508,10 @@ def get_value(element):
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:
"""
@@ -1480,7 +1554,7 @@ def _reload_exif(self) -> None:
self._exif._loaded = False
self.getexif()
- def get_child_images(self):
+ def get_child_images(self) -> list[ImageFile.ImageFile]:
child_images = []
exif = self.getexif()
ifds = []
@@ -1504,16 +1578,17 @@ def get_child_images(self):
fp = self.fp
thumbnail_offset = ifd.get(513)
if thumbnail_offset is not None:
- try:
- thumbnail_offset += self._exif_offset
- except AttributeError:
- pass
+ thumbnail_offset += getattr(self, "_exif_offset", 0)
self.fp.seek(thumbnail_offset)
data = self.fp.read(ifd.get(514))
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()
@@ -1573,7 +1648,7 @@ def has_transparency_data(self) -> bool:
or "transparency" in self.info
)
- def apply_transparency(self):
+ def apply_transparency(self) -> None:
"""
If a P mode image has a "transparency" key in the info dictionary,
remove the key and instead apply the transparency to the palette.
@@ -1585,6 +1660,7 @@ def apply_transparency(self):
from . import ImagePalette
palette = self.getpalette("RGBA")
+ assert palette is not None
transparency = self.info["transparency"]
if isinstance(transparency, bytes):
for i, alpha in enumerate(transparency):
@@ -1596,7 +1672,9 @@ def apply_transparency(self):
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.
@@ -1680,7 +1758,12 @@ def entropy(self, mask=None, extrema=None):
return self.im.entropy(extrema)
return self.im.entropy()
- def paste(self, im, box=None, mask=None) -> None:
+ def paste(
+ self,
+ im: Image | str | float | tuple[float, ...],
+ box: Image | tuple[int, int, int, int] | tuple[int, int] | None = None,
+ mask: Image | None = None,
+ ) -> None:
"""
Pastes another image into this image. The box argument is either
a 2-tuple giving the upper left corner, a 4-tuple defining the
@@ -1708,7 +1791,7 @@ def paste(self, im, box=None, mask=None) -> None:
See :py:meth:`~PIL.Image.Image.alpha_composite` if you want to
combine images with respect to their alpha channels.
- :param im: Source image or pixel value (integer or tuple).
+ :param im: Source image or pixel value (integer, float or tuple).
:param box: An optional 4-tuple giving the region to paste into.
If a 2-tuple is used instead, it's treated as the upper left
corner. If omitted or None, the source is pasted into the
@@ -1720,10 +1803,14 @@ def paste(self, im, box=None, mask=None) -> None:
:param mask: An optional mask image.
"""
- if isImageType(box) and mask is None:
+ if isImageType(box):
+ 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
+ assert not isinstance(box, Image)
if box is None:
box = (0, 0)
@@ -1761,7 +1848,9 @@ def paste(self, im, box=None, mask=None) -> None:
else:
self.im.paste(im, 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.
@@ -1776,32 +1865,35 @@ def alpha_composite(self, im, dest=(0, 0), source=(0, 0)):
"""
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:
@@ -1812,7 +1904,11 @@ def alpha_composite(self, im, dest=(0, 0), source=(0, 0)):
result = alpha_composite(background, overlay)
self.paste(result, box)
- def point(self, lut, mode: str | None = None) -> Image:
+ def point(
+ self,
+ lut: Sequence[float] | Callable[[int], float] | ImagePointHandler,
+ mode: str | None = None,
+ ) -> Image:
"""
Maps this image through a lookup table or function.
@@ -1849,7 +1945,9 @@ def point(self, data):
scale, offset = _getscaleoffset(lut)
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
+ else:
+ flatLut = lut
if self.mode == "F":
# FIXME: _imaging returns a confusing error message for this case
@@ -1857,18 +1955,17 @@ def point(self, data):
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()
@@ -1907,6 +2004,7 @@ def putalpha(self, alpha):
alpha = alpha.convert("L")
else:
# constant alpha
+ alpha = cast(int, alpha) # see python/typing#1013
try:
self.im.fillband(band, alpha)
except (AttributeError, ValueError):
@@ -1917,7 +2015,12 @@ def putalpha(self, alpha):
self.im.putband(alpha.im, band)
- def putdata(self, data, scale=1.0, offset=0.0):
+ def putdata(
+ self,
+ data: Sequence[float] | Sequence[Sequence[int]],
+ scale: float = 1.0,
+ offset: float = 0.0,
+ ) -> None:
"""
Copies pixel data from a flattened sequence object into the image. The
values should start at the upper left corner (0, 0), continue to the
@@ -1967,10 +2070,12 @@ def putpalette(self, data, rawmode="RGB") -> None:
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
@@ -2008,9 +2113,8 @@ def putpixel(self, xy, value):
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)
+ palette_index = self.palette.getcolor(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):
@@ -2082,7 +2186,7 @@ def remap_palette(self, dest_map, source_palette=None):
# 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")
@@ -2115,7 +2219,13 @@ def _get_safe_box(self, size, resample, box):
min(self.size[1], math.ceil(box[3] + support_y)),
)
- def resize(self, size, resample=None, box=None, reducing_gap=None) -> Image:
+ def resize(
+ self,
+ size: tuple[int, int],
+ resample: int | None = None,
+ box: tuple[float, float, float, float] | None = None,
+ reducing_gap: float | None = None,
+ ) -> Image:
"""
Returns a resized copy of this image.
@@ -2173,20 +2283,16 @@ def resize(self, size, resample=None, box=None, reducing_gap=None) -> Image:
(Resampling.HAMMING, "Image.Resampling.HAMMING"),
)
]
- msg += " Use " + ", ".join(filters[:-1]) + " or " + filters[-1]
+ msg += f" Use {', '.join(filters[:-1])} or {filters[-1]}"
raise ValueError(msg)
if reducing_gap is not None and reducing_gap < 1.0:
msg = "reducing_gap must be 1.0 or greater"
raise ValueError(msg)
- size = tuple(size)
-
self.load()
if box is None:
box = (0, 0) + self.size
- else:
- box = tuple(box)
if self.size == size and box == (0, 0) + self.size:
return self.copy()
@@ -2221,7 +2327,11 @@ def resize(self, size, resample=None, box=None, reducing_gap=None) -> Image:
return self._new(self.im.resize(size, resample, box))
- def reduce(self, factor, box=None):
+ def reduce(
+ self,
+ factor: int | tuple[int, int],
+ box: tuple[int, int, int, int] | None = None,
+ ) -> Image:
"""
Returns a copy of the image reduced ``factor`` times.
If the size of the image is not dividable by ``factor``,
@@ -2239,8 +2349,6 @@ def reduce(self, factor, box=None):
if box is None:
box = (0, 0) + self.size
- else:
- box = tuple(box)
if factor == (1, 1) and box == (0, 0) + self.size:
return self.copy()
@@ -2256,13 +2364,13 @@ def reduce(self, factor, box=None):
def rotate(
self,
- angle,
- resample=Resampling.NEAREST,
- expand=0,
- center=None,
- translate=None,
- fillcolor=None,
- ):
+ angle: float,
+ resample: Resampling = Resampling.NEAREST,
+ expand: int | bool = False,
+ center: tuple[float, float] | None = None,
+ translate: tuple[int, int] | None = None,
+ fillcolor: float | tuple[float, ...] | str | None = None,
+ ) -> Image:
"""
Returns a rotated copy of this image. This method returns a
copy of this image, rotated the given number of degrees counter
@@ -2327,10 +2435,7 @@ def rotate(
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 = [
@@ -2347,10 +2452,10 @@ def transform(x, y, 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
@@ -2373,7 +2478,9 @@ def transform(x, y, matrix):
(w, h), Transform.AFFINE, matrix, resample, fillcolor=fillcolor
)
- def save(self, fp, format=None, **params) -> None:
+ def save(
+ self, fp: StrOrBytesPath | IO[bytes], format: str | None = None, **params: Any
+ ) -> None:
"""
Saves this image under the given filename. If no format is
specified, the format to use is determined from the filename
@@ -2422,7 +2529,7 @@ def save(self, fp, format=None, **params) -> None:
save_all = params.pop("save_all", False)
self.encoderinfo = params
- self.encoderconfig = ()
+ self.encoderconfig: tuple[Any, ...] = ()
preinit()
@@ -2454,6 +2561,8 @@ def save(self, fp, format=None, **params) -> None:
fp = builtins.open(filename, "r+b")
else:
fp = builtins.open(filename, "w+b")
+ else:
+ fp = cast(IO[bytes], fp)
try:
save_handler(self, fp, filename)
@@ -2565,7 +2674,12 @@ def tell(self) -> int:
"""
return 0
- def thumbnail(self, size, resample=Resampling.BICUBIC, reducing_gap=2.0):
+ def thumbnail(
+ self,
+ size: tuple[float, float],
+ resample: Resampling = Resampling.BICUBIC,
+ reducing_gap: float | None = 2.0,
+ ) -> None:
"""
Make this image into a thumbnail. This method modifies the
image to contain a thumbnail version of itself, no larger than
@@ -2625,27 +2739,32 @@ def round_aspect(number, key):
return x, y
box = None
+ final_size: tuple[int, int]
if reducing_gap is not None:
- size = preserve_aspect_ratio()
- if size is None:
+ preserved_size = preserve_aspect_ratio()
+ if preserved_size is None:
return
+ final_size = preserved_size
- res = self.draft(None, (size[0] * reducing_gap, size[1] * reducing_gap))
+ 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
- size = preserve_aspect_ratio()
- if size is None:
+ preserved_size = preserve_aspect_ratio()
+ if preserved_size is None:
return
+ final_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
@@ -2655,12 +2774,12 @@ def round_aspect(number, key):
# instead of bloating the method docs, add a separate chapter.
def transform(
self,
- size,
- method,
- data=None,
- resample=Resampling.NEAREST,
- fill=1,
- fillcolor=None,
+ size: tuple[int, int],
+ method: Transform | ImageTransformHandler | SupportsGetData,
+ data: Sequence[Any] | None = None,
+ resample: int = Resampling.NEAREST,
+ fill: int = 1,
+ fillcolor: float | tuple[float, ...] | str | None = None,
) -> Image:
"""
Transforms this image. This method creates a new image with the
@@ -2814,7 +2933,7 @@ def __transformer(
(Resampling.BICUBIC, "Image.Resampling.BICUBIC"),
)
]
- msg += " Use " + ", ".join(filters[:-1]) + " or " + filters[-1]
+ msg += f" Use {', '.join(filters[:-1])} or {filters[-1]}"
raise ValueError(msg)
image.load()
@@ -2824,7 +2943,7 @@ def __transformer(
if image.mode in ("1", "P"):
resample = Resampling.NEAREST
- self.im.transform2(box, image.im, method, data, resample, fill)
+ self.im.transform(box, image.im, method, data, resample, fill)
def transpose(self, method: Transpose) -> Image:
"""
@@ -2840,7 +2959,7 @@ def transpose(self, method: Transpose) -> Image:
self.load()
return self._new(self.im.transpose(method))
- def effect_spread(self, distance):
+ def effect_spread(self, distance: int) -> Image:
"""
Randomly spread pixels in an image.
@@ -2894,7 +3013,7 @@ def transform(
self,
size: tuple[int, int],
image: Image,
- **options: dict[str, str | int | tuple[int, ...] | list[int]],
+ **options: Any,
) -> Image:
pass
@@ -2906,35 +3025,35 @@ def transform(
# 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.
@@ -2951,6 +3070,9 @@ def new(
:returns: An :py:class:`~PIL.Image.Image` object.
"""
+ if mode in ("BGR;15", "BGR;16", "BGR;24"):
+ deprecate(mode, 12)
+
_check_size(size)
if color is None:
@@ -2965,16 +3087,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="raw", *args) -> Image:
+def frombytes(
+ mode: str,
+ size: tuple[int, int],
+ data: bytes | bytearray,
+ decoder_name: str = "raw",
+ *args: Any,
+) -> Image:
"""
Creates a copy of an image memory from pixel data in a buffer.
@@ -3002,18 +3136,21 @@ def frombytes(mode, size, data, decoder_name="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, size, data, decoder_name="raw", *args):
+def frombuffer(
+ mode: str, size: tuple[int, int], data, decoder_name: str = "raw", *args: Any
+) -> Image:
"""
Creates an image memory referencing pixel data in a byte buffer.
@@ -3069,7 +3206,17 @@ def frombuffer(mode, size, data, decoder_name="raw", *args):
return frombytes(mode, size, data, decoder_name, args)
-def fromarray(obj, mode=None):
+class SupportsArrayInterface(Protocol):
+ """
+ An object that has an ``__array_interface__`` dictionary.
+ """
+
+ @property
+ def __array_interface__(self) -> dict[str, Any]:
+ raise NotImplementedError()
+
+
+def fromarray(obj: SupportsArrayInterface, mode: str | None = None) -> Image:
"""
Creates an image memory from an object exporting the array interface
(using the buffer protocol)::
@@ -3148,8 +3295,11 @@ def fromarray(obj, mode=None):
if strides is not None:
if hasattr(obj, "tobytes"):
obj = obj.tobytes()
- else:
+ elif hasattr(obj, "tostring"):
obj = obj.tostring()
+ else:
+ msg = "'strides' requires either tobytes() or tostring()"
+ raise ValueError(msg)
return frombuffer(mode, size, obj, "raw", rawmode, 0, 1)
@@ -3196,8 +3346,8 @@ def fromqpixmap(im):
((1, 1, 3), "|u1"): ("RGB", "RGB"),
((1, 1, 4), "|u1"): ("RGBA", "RGBA"),
# shortcuts:
- ((1, 1), _ENDIAN + "i4"): ("I", "I"),
- ((1, 1), _ENDIAN + "f4"): ("F", "F"),
+ ((1, 1), f"{_ENDIAN}i4"): ("I", "I"),
+ ((1, 1), f"{_ENDIAN}f4"): ("F", "F"),
}
@@ -3222,7 +3372,11 @@ def _decompression_bomb_check(size: tuple[int, int]) -> None:
)
-def open(fp, mode="r", formats=None) -> Image:
+def open(
+ fp: StrOrBytesPath | IO[bytes],
+ mode: Literal["r"] = "r",
+ formats: list[str] | tuple[str, ...] | None = None,
+) -> ImageFile.ImageFile:
"""
Opens and identifies the given image file.
@@ -3253,10 +3407,10 @@ def open(fp, mode="r", formats=None) -> Image:
"""
if mode != "r":
- msg = f"bad mode {repr(mode)}"
+ msg = f"bad mode {repr(mode)}" # type: ignore[unreachable]
raise ValueError(msg)
elif isinstance(fp, io.StringIO):
- msg = (
+ msg = ( # type: ignore[unreachable]
"StringIO cannot be used to open an image. "
"Binary data must be used instead."
)
@@ -3265,7 +3419,7 @@ def open(fp, mode="r", formats=None) -> Image:
if formats is None:
formats = ID
elif not isinstance(formats, (list, tuple)):
- msg = "formats must be a list or tuple"
+ msg = "formats must be a list or tuple" # type: ignore[unreachable]
raise TypeError(msg)
exclusive_fp = False
@@ -3276,6 +3430,8 @@ def open(fp, mode="r", formats=None) -> Image:
if filename:
fp = builtins.open(filename, "rb")
exclusive_fp = True
+ else:
+ fp = cast(IO[bytes], fp)
try:
fp.seek(0)
@@ -3287,9 +3443,14 @@ def open(fp, mode="r", formats=None) -> Image:
preinit()
- accept_warnings = []
+ warning_messages: list[str] = []
- def _open_core(fp, filename, prefix, formats):
+ def _open_core(
+ fp: IO[bytes],
+ filename: str | bytes,
+ prefix: bytes,
+ formats: list[str] | tuple[str, ...],
+ ) -> ImageFile.ImageFile | None:
for i in formats:
i = i.upper()
if i not in OPEN:
@@ -3297,18 +3458,16 @@ def _open_core(fp, filename, prefix, formats):
try:
factory, accept = OPEN[i]
result = not accept or accept(prefix)
- if type(result) in [str, bytes]:
- accept_warnings.append(result)
+ if isinstance(result, str):
+ 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()
@@ -3318,7 +3477,7 @@ def _open_core(fp, filename, prefix, formats):
im = _open_core(fp, filename, prefix, formats)
if im is None and formats is ID:
- checked_formats = formats.copy()
+ checked_formats = ID.copy()
if init():
im = _open_core(
fp,
@@ -3333,7 +3492,7 @@ def _open_core(fp, filename, prefix, formats):
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)
@@ -3414,7 +3573,7 @@ def eval(image, *args):
return image.point(args[0])
-def merge(mode, bands):
+def merge(mode: str, bands: Sequence[Image]) -> Image:
"""
Merge a set of single band images into a new multiband image.
@@ -3446,9 +3605,9 @@ def merge(mode, bands):
def register_open(
- id,
+ id: str,
factory: Callable[[IO[bytes], str | bytes], ImageFile.ImageFile],
- accept: Callable[[bytes], bool] | None = None,
+ accept: Callable[[bytes], bool | str] | None = None,
) -> None:
"""
Register an image file plugin. This function should not be used
@@ -3480,7 +3639,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.
@@ -3491,7 +3652,9 @@ def register_save(id: str, driver) -> None:
SAVE[id.upper()] = driver
-def register_save_all(id, 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
@@ -3503,7 +3666,7 @@ def register_save_all(id, driver) -> None:
SAVE_ALL[id.upper()] = driver
-def register_extension(id, extension) -> None:
+def register_extension(id: str, extension: str) -> None:
"""
Registers an image extension. This function should not be
used in application code.
@@ -3514,7 +3677,7 @@ def register_extension(id, extension) -> None:
EXTENSION[extension.lower()] = id.upper()
-def register_extensions(id, extensions) -> None:
+def register_extensions(id: str, extensions: list[str]) -> None:
"""
Registers image extensions. This function should not be
used in application code.
@@ -3526,7 +3689,7 @@ def register_extensions(id, extensions) -> None:
register_extension(id, extension)
-def registered_extensions():
+def registered_extensions() -> dict[str, str]:
"""
Returns a dictionary containing all file extensions belonging
to registered plugins
@@ -3565,7 +3728,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)
@@ -3575,7 +3738,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.
@@ -3588,7 +3753,7 @@ def effect_mandelbrot(size, extent, quality):
return Image()._new(core.effect_mandelbrot(size, extent, quality))
-def effect_noise(size, sigma):
+def effect_noise(size: tuple[int, int], sigma: float) -> Image:
"""
Generate Gaussian noise centered around 128.
@@ -3599,7 +3764,7 @@ def effect_noise(size, sigma):
return Image()._new(core.effect_noise(size, sigma))
-def linear_gradient(mode):
+def linear_gradient(mode: str) -> Image:
"""
Generate 256x256 linear gradient from black to white, top to bottom.
@@ -3608,7 +3773,7 @@ def linear_gradient(mode):
return Image()._new(core.linear_gradient(mode))
-def radial_gradient(mode):
+def radial_gradient(mode: str) -> Image:
"""
Generate 256x256 radial gradient from black to white, centre to edge.
@@ -3621,19 +3786,18 @@ def radial_gradient(mode):
# 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)]:
@@ -3642,13 +3806,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}")
diff --git a/src/PIL/ImageCms.py b/src/PIL/ImageCms.py
index 3a45572a128..ec10230f12b 100644
--- a/src/PIL/ImageCms.py
+++ b/src/PIL/ImageCms.py
@@ -299,6 +299,31 @@ def __init__(
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
@@ -704,12 +729,12 @@ def applyTransform(
"""
(pyCMS) Applies a transform to a given image.
- If ``im.mode != transform.inMode``, a :exc:`PyCMSError` is raised.
+ If ``im.mode != transform.input_mode``, a :exc:`PyCMSError` is raised.
- If ``inPlace`` is ``True`` and ``transform.inMode != transform.outMode``, a
+ If ``inPlace`` is ``True`` and ``transform.input_mode != transform.output_mode``, a
:exc:`PyCMSError` is raised.
- If ``im.mode``, ``transform.inMode`` or ``transform.outMode`` is not
+ If ``im.mode``, ``transform.input_mode`` or ``transform.output_mode`` is not
supported by pyCMSdll or the profiles you used for the transform, a
:exc:`PyCMSError` is raised.
@@ -723,13 +748,13 @@ def applyTransform(
If you want to modify im in-place instead of receiving a new image as
the return value, set ``inPlace`` to ``True``. This can only be done if
- ``transform.inMode`` and ``transform.outMode`` are the same, because we can't
- change the mode in-place (the buffer sizes for some modes are
+ ``transform.input_mode`` and ``transform.output_mode`` are the same, because we
+ can't change the mode in-place (the buffer sizes for some modes are
different). The default behavior is to return a new :py:class:`~PIL.Image.Image`
- object of the same dimensions in mode ``transform.outMode``.
+ object of the same dimensions in mode ``transform.output_mode``.
- :param im: An :py:class:`~PIL.Image.Image` object, and im.mode must be the same
- as the ``inMode`` supported by the transform.
+ :param im: An :py:class:`~PIL.Image.Image` object, and ``im.mode`` must be the same
+ as the ``input_mode`` supported by the transform.
:param transform: A valid CmsTransform class object
:param inPlace: Bool. If ``True``, ``im`` is modified in place and ``None`` is
returned, if ``False``, a new :py:class:`~PIL.Image.Image` object with the
@@ -754,7 +779,7 @@ def applyTransform(
def createProfile(
- colorSpace: Literal["LAB", "XYZ", "sRGB"], colorTemp: SupportsFloat = -1
+ colorSpace: Literal["LAB", "XYZ", "sRGB"], colorTemp: SupportsFloat = 0
) -> core.CmsProfile:
"""
(pyCMS) Creates a profile.
@@ -777,7 +802,7 @@ def createProfile(
:param colorSpace: String, the color space of the profile you wish to
create.
Currently only "LAB", "XYZ", and "sRGB" are supported.
- :param colorTemp: Positive integer for the white point for the profile, in
+ :param colorTemp: Positive number for the white point for the profile, in
degrees Kelvin (i.e. 5000, 6500, 9600, etc.). The default is for D50
illuminant if omitted (5000k). colorTemp is ONLY applied to LAB
profiles, and is ignored for XYZ and sRGB.
@@ -838,8 +863,8 @@ def getProfileName(profile: _CmsProfileCompatible) -> str:
if not (model or manufacturer):
return (profile.profile.profile_description or "") + "\n"
- if not manufacturer or len(model) > 30: # type: ignore[arg-type]
- return model + "\n" # type: ignore[operator]
+ if not manufacturer or (model and len(model) > 30):
+ return f"{model}\n"
return f"{model} - {manufacturer}\n"
except (AttributeError, OSError, TypeError, ValueError) as v:
@@ -1089,7 +1114,7 @@ def isIntentSupported(
raise PyCMSError(v) from v
-def versions() -> tuple[str, str, str, str]:
+def versions() -> tuple[str, str | None, str, str]:
"""
(pyCMS) Fetches versions.
"""
diff --git a/src/PIL/ImageColor.py b/src/PIL/ImageColor.py
index 5fb80b75310..9a15a8eb759 100644
--- a/src/PIL/ImageColor.py
+++ b/src/PIL/ImageColor.py
@@ -25,7 +25,7 @@
@lru_cache
-def getrgb(color):
+def getrgb(color: str) -> tuple[int, int, int] | tuple[int, int, int, int]:
"""
Convert a color string to an RGB or RGBA tuple. If the string cannot be
parsed, this function raises a :py:exc:`ValueError` exception.
@@ -44,8 +44,10 @@ def getrgb(color):
if rgb:
if isinstance(rgb, tuple):
return rgb
- colormap[color] = rgb = getrgb(rgb)
- return rgb
+ rgb_tuple = getrgb(rgb)
+ assert len(rgb_tuple) == 3
+ colormap[color] = rgb_tuple
+ return rgb_tuple
# check for known string formats
if re.match("#[a-f0-9]{3}$", color):
@@ -88,15 +90,15 @@ def getrgb(color):
if m:
from colorsys import hls_to_rgb
- rgb = hls_to_rgb(
+ rgb_floats = hls_to_rgb(
float(m.group(1)) / 360.0,
float(m.group(3)) / 100.0,
float(m.group(2)) / 100.0,
)
return (
- int(rgb[0] * 255 + 0.5),
- int(rgb[1] * 255 + 0.5),
- int(rgb[2] * 255 + 0.5),
+ int(rgb_floats[0] * 255 + 0.5),
+ int(rgb_floats[1] * 255 + 0.5),
+ int(rgb_floats[2] * 255 + 0.5),
)
m = re.match(
@@ -105,15 +107,15 @@ def getrgb(color):
if m:
from colorsys import hsv_to_rgb
- rgb = hsv_to_rgb(
+ rgb_floats = hsv_to_rgb(
float(m.group(1)) / 360.0,
float(m.group(2)) / 100.0,
float(m.group(3)) / 100.0,
)
return (
- int(rgb[0] * 255 + 0.5),
- int(rgb[1] * 255 + 0.5),
- int(rgb[2] * 255 + 0.5),
+ int(rgb_floats[0] * 255 + 0.5),
+ int(rgb_floats[1] * 255 + 0.5),
+ int(rgb_floats[2] * 255 + 0.5),
)
m = re.match(r"rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$", color)
@@ -124,7 +126,7 @@ def getrgb(color):
@lru_cache
-def getcolor(color, mode: str) -> tuple[int, ...]:
+def getcolor(color: str, mode: str) -> int | tuple[int, ...]:
"""
Same as :py:func:`~PIL.ImageColor.getrgb` for most modes. However, if
``mode`` is HSV, converts the RGB value to a HSV value, or if ``mode`` is
@@ -136,33 +138,34 @@ def getcolor(color, mode: str) -> tuple[int, ...]:
:param color: A color string
:param mode: Convert result to this mode
- :return: ``(graylevel[, alpha]) or (red, green, blue[, alpha])``
+ :return: ``graylevel, (graylevel, alpha) or (red, green, blue[, alpha])``
"""
# same as getrgb, but converts the result to the given mode
- color, alpha = getrgb(color), 255
- if len(color) == 4:
- color, alpha = color[:3], color[3]
+ rgb, alpha = getrgb(color), 255
+ if len(rgb) == 4:
+ alpha = rgb[3]
+ rgb = rgb[:3]
if mode == "HSV":
from colorsys import rgb_to_hsv
- r, g, b = color
+ r, g, b = rgb
h, s, v = rgb_to_hsv(r / 255, g / 255, b / 255)
return int(h * 255), int(s * 255), int(v * 255)
elif Image.getmodebase(mode) == "L":
- r, g, b = color
+ r, g, b = rgb
# ITU-R Recommendation 601-2 for nonlinear RGB
# scaled to 24 bits to match the convert's implementation.
- color = (r * 19595 + g * 38470 + b * 7471 + 0x8000) >> 16
+ graylevel = (r * 19595 + g * 38470 + b * 7471 + 0x8000) >> 16
if mode[-1] == "A":
- return color, alpha
- else:
- if mode[-1] == "A":
- return color + (alpha,)
- return color
+ return graylevel, alpha
+ return graylevel
+ elif mode[-1] == "A":
+ return rgb + (alpha,)
+ return rgb
-colormap = {
+colormap: dict[str, str | tuple[int, int, int]] = {
# X11 colour table from https://drafts.csswg.org/css-color-4/, with
# gray/grey spelling issues fixed. This is a superset of HTML 4.0
# colour names used in CSS 1.
diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py
index d3efe64865e..244d3d5f022 100644
--- a/src/PIL/ImageDraw.py
+++ b/src/PIL/ImageDraw.py
@@ -34,11 +34,25 @@
import math
import numbers
import struct
-from typing import Sequence, cast
+from types import ModuleType
+from typing import TYPE_CHECKING, AnyStr, Callable, List, Sequence, Tuple, 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 @@
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,7 +108,9 @@ def __init__(self, im: Image.Image, mode: str | None = None) -> None:
self.fontmode = "L" # aliasing is okay for other modes
self.fill = False
- def getfont(self):
+ def getfont(
+ self,
+ ) -> ImageFont.ImageFont | ImageFont.FreeTypeFont | ImageFont.TransposedFont:
"""
Get the current default font.
@@ -117,43 +135,57 @@ def getfont(self):
self.font = ImageFont.load_default()
return self.font
- def _getfont(self, font_size: float | None):
+ def _getfont(
+ self, font_size: float | None
+ ) -> ImageFont.ImageFont | ImageFont.FreeTypeFont | ImageFont.TransposedFont:
if font_size is not None:
from . import ImageFont
- font = ImageFont.load_default(font_size)
+ return ImageFont.load_default(font_size)
else:
- font = self.getfont()
- return font
+ 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):
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):
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)
@@ -162,23 +194,55 @@ def bitmap(self, xy: Sequence[int], bitmap, fill=None) -> None:
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 line(self, xy: Coords, fill=None, width=0, joint=None) -> None:
+ def circle(
+ 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: _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:
@@ -206,7 +270,9 @@ def line(self, xy: Coords, fill=None, width=0, joint=None) -> None:
# 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
@@ -247,37 +313,54 @@ def coord_at_angle(coord, angle):
]
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:
@@ -303,22 +386,41 @@ def polygon(self, xy: Coords, fill=None, outline=None, width=1) -> None:
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)):
@@ -360,10 +462,10 @@ def rounded_rectangle(
# 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
@@ -393,32 +495,32 @@ def draw_corners(pieslice) -> None:
)
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)
+ self.draw.draw_rectangle((x0, y0 + r + 1, x1, y1 - r - 1), fill_ink, 1)
else:
- self.draw.draw_rectangle((x0 + r + 1, y0, x1 - r - 1, y1), fill, 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:
@@ -450,15 +552,13 @@ def draw_corners(pieslice) -> None:
right[3] -= r + 1
self.draw.draw_rectangle(right, ink, 1)
- def _multiline_check(self, text) -> bool:
+ def _multiline_check(self, text: AnyStr) -> bool:
split_character = "\n" if isinstance(text, str) else b"\n"
return split_character in text
- def _multiline_split(self, text) -> list[str | bytes]:
- split_character = "\n" if isinstance(text, str) else b"\n"
-
- return text.split(split_character)
+ 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):
return (
@@ -469,10 +569,15 @@ def _multiline_spacing(self, font, spacing, stroke_width):
def text(
self,
- xy,
- text,
+ xy: tuple[float, float],
+ text: str,
fill=None,
- font=None,
+ font: (
+ ImageFont.ImageFont
+ | ImageFont.FreeTypeFont
+ | ImageFont.TransposedFont
+ | None
+ ) = None,
anchor=None,
spacing=4,
align="left",
@@ -510,10 +615,11 @@ def text(
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:
@@ -526,7 +632,7 @@ def draw_text(ink, stroke_width=0, stroke_offset=None) -> None:
coord.append(int(xy[i]))
start.append(math.modf(xy[i])[0])
try:
- mask, offset = font.getmask2(
+ mask, offset = font.getmask2( # type: ignore[union-attr,misc]
text,
mode,
direction=direction,
@@ -542,7 +648,7 @@ def draw_text(ink, stroke_width=0, stroke_offset=None) -> None:
coord = [coord[0] + offset[0], coord[1] + offset[1]]
except AttributeError:
try:
- mask = font.getmask(
+ mask = font.getmask( # type: ignore[misc]
text,
mode,
direction,
@@ -591,10 +697,15 @@ def draw_text(ink, stroke_width=0, stroke_offset=None) -> None:
def multiline_text(
self,
- xy,
- text,
+ xy: tuple[float, float],
+ text: str,
fill=None,
- font=None,
+ font: (
+ ImageFont.ImageFont
+ | ImageFont.FreeTypeFont
+ | ImageFont.TransposedFont
+ | None
+ ) = None,
anchor=None,
spacing=4,
align="left",
@@ -624,7 +735,7 @@ def multiline_text(
font = self._getfont(font_size)
widths = []
- max_width = 0
+ max_width: float = 0
lines = self._multiline_split(text)
line_spacing = self._multiline_spacing(font, spacing, stroke_width)
for line in lines:
@@ -678,15 +789,20 @@ def multiline_text(
def textlength(
self,
- text,
- font=None,
+ text: str,
+ font: (
+ ImageFont.ImageFont
+ | ImageFont.FreeTypeFont
+ | ImageFont.TransposedFont
+ | None
+ ) = None,
direction=None,
features=None,
language=None,
embedded_color=False,
*,
font_size=None,
- ):
+ ) -> float:
"""Get the length of a given string, in pixels with 1/64 precision."""
if self._multiline_check(text):
msg = "can't measure length of multiline text"
@@ -778,7 +894,7 @@ def multiline_textbbox(
font = self._getfont(font_size)
widths = []
- max_width = 0
+ max_width: float = 0
lines = self._multiline_split(text)
line_spacing = self._multiline_spacing(font, spacing, stroke_width)
for line in lines:
@@ -850,7 +966,7 @@ def multiline_textbbox(
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.
@@ -862,45 +978,38 @@ 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
-
-
-def floodfill(image: Image.Image, xy, value, border=None, thresh=0) -> None:
+ 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(
+ image: Image.Image,
+ xy: tuple[int, int],
+ value: float | tuple[int, ...],
+ border: float | tuple[int, ...] | None = None,
+ 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
@@ -918,6 +1027,7 @@ def floodfill(image: Image.Image, xy, value, border=None, thresh=0) -> None:
# 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]
@@ -955,12 +1065,12 @@ def floodfill(image: Image.Image, xy, value, border=None, thresh=0) -> None:
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
@@ -998,7 +1108,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"
@@ -1010,9 +1120,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 "
@@ -1020,25 +1145,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))
@@ -1054,7 +1171,7 @@ def _apply_rotation(point: list[float], degrees: float) -> tuple[int, int]:
),
)
- 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)
@@ -1077,11 +1194,13 @@ def _get_angles(n_sides: int, rotation: float) -> list[float]:
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 35ee5834e34..e89a78be4b5 100644
--- a/src/PIL/ImageDraw2.py
+++ b/src/PIL/ImageDraw2.py
@@ -24,13 +24,16 @@
"""
from __future__ import annotations
+from typing import BinaryIO
+
from . import Image, ImageColor, ImageDraw, ImageFont, ImagePath
+from ._typing import StrOrBytesPath
class Pen:
"""Stores an outline color and width."""
- def __init__(self, color, width=1, opacity=255):
+ def __init__(self, color: str, width: int = 1, opacity: int = 255) -> None:
self.color = ImageColor.getrgb(color)
self.width = width
@@ -38,14 +41,16 @@ def __init__(self, color, width=1, opacity=255):
class Brush:
"""Stores a fill color"""
- def __init__(self, color, opacity=255):
+ def __init__(self, color: str, opacity: int = 255) -> None:
self.color = ImageColor.getrgb(color)
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,14 +61,22 @@ 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
- def flush(self):
+ def flush(self) -> Image.Image:
return self.image
def render(self, op, xy, pen, brush=None):
diff --git a/src/PIL/ImageEnhance.py b/src/PIL/ImageEnhance.py
index 93a50d2a2b9..d7e99a96815 100644
--- a/src/PIL/ImageEnhance.py
+++ b/src/PIL/ImageEnhance.py
@@ -23,7 +23,10 @@
class _Enhance:
- def enhance(self, factor):
+ image: Image.Image
+ degenerate: Image.Image
+
+ def enhance(self, factor: float) -> Image.Image:
"""
Returns an enhanced image.
@@ -46,7 +49,7 @@ class Color(_Enhance):
the original image.
"""
- def __init__(self, image):
+ def __init__(self, image: Image.Image) -> None:
self.image = image
self.intermediate_mode = "L"
if "A" in image.getbands():
@@ -63,7 +66,7 @@ class Contrast(_Enhance):
gives a solid gray image. A factor of 1.0 gives the original image.
"""
- def __init__(self, image):
+ 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)
@@ -80,7 +83,7 @@ class Brightness(_Enhance):
original image.
"""
- def __init__(self, image):
+ def __init__(self, image: Image.Image) -> None:
self.image = image
self.degenerate = Image.new(image.mode, image.size, 0)
@@ -96,7 +99,7 @@ class Sharpness(_Enhance):
original image, and a factor of 2.0 gives a sharpened image.
"""
- def __init__(self, image):
+ def __init__(self, image: Image.Image) -> None:
self.image = image
self.degenerate = image.filter(ImageFilter.SMOOTH)
diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py
index 0283fa2fd42..69e7ee54811 100644
--- a/src/PIL/ImageFile.py
+++ b/src/PIL/ImageFile.py
@@ -28,6 +28,7 @@
#
from __future__ import annotations
+import abc
import io
import itertools
import struct
@@ -64,7 +65,7 @@
# Helpers
-def _get_oserror(error, *, encoder):
+def _get_oserror(error: int, *, encoder: bool) -> OSError:
try:
msg = Image.core.getcodecstatus(error)
except AttributeError:
@@ -75,7 +76,7 @@ def _get_oserror(error, *, encoder):
return OSError(msg)
-def raise_oserror(error):
+def raise_oserror(error: int) -> OSError:
deprecate(
"raise_oserror",
12,
@@ -153,17 +154,18 @@ def __init__(self, fp=None, filename=None):
self.fp.close()
raise
- def get_format_mimetype(self):
+ 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):
self.tile = []
super().__setstate__(state)
- def verify(self):
+ def verify(self) -> None:
"""Check file integrity"""
# raise exception if something's wrong. must be called
@@ -311,7 +313,7 @@ def load(self):
return Image.Image.load(self)
- def load_prepare(self):
+ 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:
self.im = Image.core.new(self.mode, self.size)
@@ -319,16 +321,16 @@ def load_prepare(self):
if self.mode == "P":
Image.Image.load(self)
- def load_end(self):
+ def load_end(self) -> None:
# may be overridden
pass
# may be defined for contained formats
- # def load_seek(self, pos):
+ # def load_seek(self, pos: int) -> None:
# pass
# may be defined for blocked formats (e.g. PNG)
- # def load_read(self, read_bytes):
+ # def load_read(self, read_bytes: int) -> bytes:
# pass
def _seek_check(self, frame):
@@ -347,6 +349,15 @@ def _seek_check(self, frame):
return self.tell() != frame
+class StubHandler:
+ def open(self, im: StubImageFile) -> None:
+ pass
+
+ @abc.abstractmethod
+ def load(self, im: StubImageFile) -> Image.Image:
+ pass
+
+
class StubImageFile(ImageFile):
"""
Base class for stub image loaders.
@@ -355,7 +366,7 @@ 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)
@@ -371,7 +382,7 @@ def load(self):
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)
@@ -390,7 +401,7 @@ class Parser:
offset = 0
finished = 0
- def reset(self):
+ def reset(self) -> None:
"""
(Consumer) Reset the parser. Note that you can only call this
method immediately after you've created a parser; parser
@@ -477,7 +488,7 @@ def feed(self, data):
def __enter__(self):
return self
- def __exit__(self, *args):
+ def __exit__(self, *args: object) -> None:
self.close()
def close(self):
@@ -605,13 +616,13 @@ def _safe_read(fp, size):
class PyCodecState:
- def __init__(self):
+ def __init__(self) -> None:
self.xsize = 0
self.ysize = 0
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
@@ -634,7 +645,7 @@ def init(self, args):
"""
self.args = args
- def cleanup(self):
+ def cleanup(self) -> None:
"""
Override to perform codec specific cleanup
@@ -651,7 +662,7 @@ def setfd(self, fd):
"""
self.fd = fd
- def setimage(self, im, extents=None):
+ def setimage(self, im, extents: tuple[int, int, int, int] | None = None) -> None:
"""
Called from ImageFile to set the core output image for the codec
@@ -700,10 +711,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) -> tuple[int, int]:
"""
Override to perform the decoding process.
@@ -728,6 +739,7 @@ def set_as_raw(self, data: bytes, rawmode=None) -> None:
if not rawmode:
rawmode = self.mode
d = Image._getdecoder(self.mode, "raw", rawmode)
+ assert self.im is not None
d.setimage(self.im, self.state.extents())
s = d.decode(data)
@@ -750,10 +762,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.
@@ -765,7 +777,7 @@ def encode(self, bufsize):
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.
@@ -777,6 +789,7 @@ def encode_to_pyfd(self):
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
diff --git a/src/PIL/ImageFilter.py b/src/PIL/ImageFilter.py
index b2c4950d6a9..e18b4a4466a 100644
--- a/src/PIL/ImageFilter.py
+++ b/src/PIL/ImageFilter.py
@@ -16,11 +16,20 @@
#
from __future__ import annotations
+import abc
import functools
+from types import ModuleType
+from typing import TYPE_CHECKING, Any, Callable, Sequence, cast
+
+if TYPE_CHECKING:
+ from . import _imaging
+ from ._typing import NumpyArray
class Filter:
- pass
+ @abc.abstractmethod
+ def filter(self, image: _imaging.ImagingCore) -> _imaging.ImagingCore:
+ pass
class MultibandFilter(Filter):
@@ -28,7 +37,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)
@@ -53,7 +64,13 @@ class Kernel(BuiltinFilter):
name = "Kernel"
- def __init__(self, size, kernel, scale=None, offset=0):
+ def __init__(
+ self,
+ size: tuple[int, int],
+ kernel: Sequence[float],
+ scale: float | None = None,
+ offset: float = 0,
+ ) -> None:
if scale is None:
# default scale is sum of kernel
scale = functools.reduce(lambda a, b: a + b, kernel)
@@ -76,11 +93,11 @@ class RankFilter(Filter):
name = "Rank"
- def __init__(self, size, rank):
+ def __init__(self, size: int, rank: int) -> None:
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)
@@ -98,7 +115,7 @@ class MedianFilter(RankFilter):
name = "Median"
- def __init__(self, size=3):
+ def __init__(self, size: int = 3) -> None:
self.size = size
self.rank = size * size // 2
@@ -113,7 +130,7 @@ class MinFilter(RankFilter):
name = "Min"
- def __init__(self, size=3):
+ def __init__(self, size: int = 3) -> None:
self.size = size
self.rank = 0
@@ -128,7 +145,7 @@ class MaxFilter(RankFilter):
name = "Max"
- def __init__(self, size=3):
+ def __init__(self, size: int = 3) -> None:
self.size = size
self.rank = size * size - 1
@@ -144,10 +161,10 @@ class ModeFilter(Filter):
name = "Mode"
- def __init__(self, size=3):
+ 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)
@@ -162,12 +179,12 @@ class GaussianBlur(MultibandFilter):
name = "GaussianBlur"
- def __init__(self, radius=2):
+ 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()
@@ -190,18 +207,16 @@ class BoxBlur(MultibandFilter):
name = "BoxBlur"
- def __init__(self, radius):
- xy = radius
- if not isinstance(xy, (tuple, list)):
- xy = (xy, xy)
+ def __init__(self, radius: float | Sequence[float]) -> None:
+ xy = radius if isinstance(radius, (tuple, list)) else (radius, radius)
if xy[0] < 0 or xy[1] < 0:
msg = "radius must be >= 0"
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()
@@ -225,12 +240,14 @@ class UnsharpMask(MultibandFilter):
name = "UnsharpMask"
- def __init__(self, radius=2, percent=150, threshold=3):
+ def __init__(
+ self, radius: float = 2, percent: int = 150, threshold: int = 3
+ ) -> None:
self.radius = radius
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)
@@ -375,7 +392,14 @@ class Color3DLUT(MultibandFilter):
name = "Color 3D LUT"
- def __init__(self, size, table, channels=3, target_mode=None, **kwargs):
+ def __init__(
+ 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)
@@ -389,7 +413,7 @@ def __init__(self, size, table, channels=3, target_mode=None, **kwargs):
items = size[0] * size[1] * size[2]
wrong_size = False
- numpy = None
+ numpy: ModuleType | None = None
if hasattr(table, "shape"):
try:
import numpy
@@ -397,15 +421,16 @@ def __init__(self, size, table, channels=3, target_mode=None, **kwargs):
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
@@ -415,7 +440,8 @@ def __init__(self, size, table, channels=3, target_mode=None, **kwargs):
# 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 = (
@@ -423,7 +449,8 @@ def __init__(self, size, table, channels=3, target_mode=None, **kwargs):
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 = (
@@ -436,7 +463,7 @@ def __init__(self, size, table, channels=3, target_mode=None, **kwargs):
self.table = table
@staticmethod
- def _check_size(size):
+ def _check_size(size: Any) -> tuple[int, int, int]:
try:
_, _, _ = size
except ValueError as e:
@@ -444,7 +471,7 @@ def _check_size(size):
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."
@@ -452,7 +479,13 @@ def _check_size(size):
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.
@@ -469,7 +502,7 @@ def generate(cls, size, callback, channels=3, target_mode=None):
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):
@@ -487,7 +520,13 @@ def generate(cls, size, callback, channels=3, target_mode=None):
_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.
@@ -541,7 +580,7 @@ def transform(self, callback, with_normals=False, channels=None, target_mode=Non
_copy_table=False,
)
- def __repr__(self):
+ def __repr__(self) -> str:
r = [
f"{self.__class__.__name__} from {self.table.__class__.__name__}",
"size={:d}x{:d}x{:d}".format(*self.size),
@@ -551,7 +590,7 @@ def __repr__(self):
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 256c581df0c..d260eef6946 100644
--- a/src/PIL/ImageFont.py
+++ b/src/PIL/ImageFont.py
@@ -33,11 +33,17 @@
import warnings
from enum import IntEnum
from io import BytesIO
-from typing import BinaryIO
+from types import ModuleType
+from typing import IO, TYPE_CHECKING, Any, BinaryIO
from . import Image
from ._typing import StrOrBytesPath
-from ._util import is_directory, is_path
+from ._util import DeferredError, is_path
+
+if TYPE_CHECKING:
+ from . import ImageFile
+ from ._imaging import ImagingFont
+ from ._imagingft import Font
class Layout(IntEnum):
@@ -48,15 +54,14 @@ 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)
-def _string_length_check(text):
+def _string_length_check(text: str | bytes | bytearray) -> None:
if MAX_STRING_LENGTH is not None and len(text) > MAX_STRING_LENGTH:
msg = "too many characters in string"
raise ValueError(msg)
@@ -81,9 +86,11 @@ def _string_length_check(text):
class ImageFont:
"""PIL font wrapper"""
- def _load_pilfont(self, filename):
+ font: ImagingFont
+
+ def _load_pilfont(self, filename: str) -> None:
with open(filename, "rb") as fp:
- image = None
+ image: ImageFile.ImageFile | None = None
for ext in (".png", ".gif", ".pbm"):
if image:
image.close()
@@ -106,7 +113,7 @@ def _load_pilfont(self, filename):
self._load_pilfont_data(fp, image)
image.close()
- def _load_pilfont_data(self, file, image):
+ def _load_pilfont_data(self, file: IO[bytes], image: Image.Image) -> None:
# read PILfont header
if file.readline() != b"PILfont\n":
msg = "Not a PILfont file"
@@ -153,17 +160,15 @@ def getmask(self, text, mode="", *args, **kwargs):
Image._decompression_bomb_check(self.font.getsize(text))
return self.font.getmask(text, mode)
- def getbbox(self, text, *args, **kwargs):
+ def getbbox(
+ self, text: str | bytes | bytearray, *args: Any, **kwargs: Any
+ ) -> tuple[int, int, int, int]:
"""
Returns bounding box (in pixels) of given text.
.. versionadded:: 9.2.0
:param text: Text to render.
- :param mode: Used by some graphics drivers to indicate what mode the
- driver prefers; if empty, the renderer may return either
- mode. Note that the mode is always a string, to simplify
- C-level implementations.
:return: ``(left, top, right, bottom)`` bounding box
"""
@@ -171,7 +176,9 @@ def getbbox(self, text, *args, **kwargs):
width, height = self.font.getsize(text)
return 0, 0, width, height
- def getlength(self, text, *args, **kwargs):
+ def getlength(
+ self, text: str | bytes | bytearray, *args: Any, **kwargs: Any
+ ) -> int:
"""
Returns length (in pixels) of given text.
This is the amount by which following text should be offset.
@@ -191,6 +198,9 @@ def getlength(self, text, *args, **kwargs):
class FreeTypeFont:
"""FreeType font wrapper (requires _imagingft service)"""
+ font: Font
+ font_bytes: bytes
+
def __init__(
self,
font: StrOrBytesPath | BinaryIO | None = None,
@@ -201,6 +211,9 @@ def __init__(
) -> None:
# FIXME: use service provider instead
+ if isinstance(core, DeferredError):
+ raise core.ex
+
if size <= 0:
msg = "font size must be greater than 0"
raise ValueError(msg)
@@ -254,14 +267,14 @@ def __setstate__(self, state):
path, size, index, encoding, layout_engine = state
self.__init__(path, size, index, encoding, layout_engine)
- def getname(self):
+ def getname(self) -> tuple[str | None, str | None]:
"""
:return: A tuple of the font family (e.g. Helvetica) and the font style
(e.g. Bold)
"""
return self.font.family, self.font.style
- def getmetrics(self):
+ def getmetrics(self) -> tuple[int, int]:
"""
:return: A tuple of the font ascent (the distance from the baseline to
the highest outline point) and descent (the distance from the
@@ -269,7 +282,9 @@ def getmetrics(self):
"""
return self.font.ascent, self.font.descent
- def getlength(self, text, mode="", direction=None, features=None, language=None):
+ def getlength(
+ self, text: str | bytes, mode="", direction=None, features=None, language=None
+ ) -> float:
"""
Returns length (in pixels with 1/64 precision) of given text when rendered
in font with provided direction, features, and language.
@@ -343,14 +358,14 @@ def getlength(self, text, mode="", direction=None, features=None, language=None)
def getbbox(
self,
- text,
- mode="",
- direction=None,
- features=None,
- language=None,
- stroke_width=0,
- anchor=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,
+ ) -> tuple[float, float, float, float]:
"""
Returns bounding box (in pixels) of given text relative to given anchor
when rendered in font with provided direction, features, and language.
@@ -500,7 +515,7 @@ def getmask(
def getmask2(
self,
- text,
+ text: str | bytes,
mode="",
direction=None,
features=None,
@@ -628,7 +643,7 @@ def font_variant(
layout_engine=layout_engine or self.layout_engine,
)
- def get_variation_names(self):
+ def get_variation_names(self) -> list[bytes]:
"""
:returns: A list of the named styles in a variation font.
:exception OSError: If the font is not a variation font.
@@ -670,10 +685,11 @@ def get_variation_axes(self):
msg = "FreeType 2.9.1 or greater is required"
raise NotImplementedError(msg) from e
for axis in axes:
- axis["name"] = axis["name"].replace(b"\x00", b"")
+ if axis["name"]:
+ axis["name"] = axis["name"].replace(b"\x00", b"")
return axes
- def set_variation_by_axes(self, axes):
+ def set_variation_by_axes(self, axes: list[float]) -> None:
"""
:param axes: A list of values for each axis.
:exception OSError: If the font is not a variation font.
@@ -718,14 +734,14 @@ def getbbox(self, text, *args, **kwargs):
return 0, 0, height, width
return 0, 0, width, height
- def getlength(self, text, *args, **kwargs):
+ def getlength(self, text: str | bytes, *args, **kwargs) -> 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)
return self.font.getlength(text, *args, **kwargs)
-def load(filename):
+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.
@@ -739,7 +755,13 @@ def load(filename):
return f
-def truetype(font=None, size=10, index=0, encoding="", layout_engine=None):
+def truetype(
+ font: StrOrBytesPath | BinaryIO | None = None,
+ size: float = 10,
+ index: int = 0,
+ encoding: str = "",
+ layout_engine: Layout | None = None,
+) -> FreeTypeFont:
"""
Load a TrueType or OpenType font from a file or file-like object,
and create a font object.
@@ -757,10 +779,15 @@ def truetype(font=None, size=10, index=0, encoding="", layout_engine=None):
: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).
@@ -800,7 +827,7 @@ def truetype(font=None, size=10, index=0, encoding="", layout_engine=None):
:exception ValueError: If the font size is not greater than zero.
"""
- def freetype(font):
+ def freetype(font: StrOrBytesPath | BinaryIO | None) -> FreeTypeFont:
return FreeTypeFont(font, size, index, encoding, layout_engine)
try:
@@ -819,12 +846,21 @@ def freetype(font):
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",
@@ -850,7 +886,7 @@ def freetype(font):
raise
-def load_path(filename):
+def load_path(filename: str | bytes) -> ImageFont:
"""
Load font file. Same as :py:func:`~PIL.ImageFont.load`, but searches for a
bitmap font along the Python path.
@@ -859,18 +895,153 @@ def load_path(filename):
:return: A font object.
:exception OSError: If the file could not be read.
"""
+ if not isinstance(filename, str):
+ filename = filename.decode("utf-8")
for directory in sys.path:
- if is_directory(directory):
- if not isinstance(filename, str):
- filename = filename.decode("utf-8")
- try:
- return load(os.path.join(directory, filename))
- except OSError:
- pass
+ try:
+ return load(os.path.join(directory, filename))
+ except OSError:
+ pass
msg = "cannot find font file"
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.
@@ -885,8 +1056,8 @@ def load_default(size: float | None = None) -> FreeTypeFont | ImageFont:
:return: A font object.
"""
- 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"""
@@ -1116,137 +1287,4 @@ def load_default(size: float | None = None) -> FreeTypeFont | ImageFont:
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 3f3be706d96..e27ca7e5033 100644
--- a/src/PIL/ImageGrab.py
+++ b/src/PIL/ImageGrab.py
@@ -26,7 +26,13 @@
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 77472a24c68..6664434ea45 100644
--- a/src/PIL/ImageMath.py
+++ b/src/PIL/ImageMath.py
@@ -61,7 +61,7 @@ def apply(
out = Image.new(mode or im_1.mode, im_1.size, None)
im_1.load()
try:
- op = getattr(_imagingmath, op + "_" + im_1.mode)
+ op = getattr(_imagingmath, f"{op}_{im_1.mode}")
except AttributeError as e:
msg = f"bad operand type for '{op}'"
raise TypeError(msg) from e
@@ -89,7 +89,7 @@ def apply(
im_1.load()
im_2.load()
try:
- op = getattr(_imagingmath, op + "_" + im_1.mode)
+ op = getattr(_imagingmath, f"{op}_{im_1.mode}")
except AttributeError as e:
msg = f"bad operand type for '{op}'"
raise TypeError(msg) from e
diff --git a/src/PIL/ImageMode.py b/src/PIL/ImageMode.py
index 0b31f608174..92a08d2cbcb 100644
--- a/src/PIL/ImageMode.py
+++ b/src/PIL/ImageMode.py
@@ -16,24 +16,19 @@
import sys
from functools import lru_cache
+from typing import NamedTuple
+from ._deprecate import deprecate
-class ModeDescriptor:
+
+class ModeDescriptor(NamedTuple):
"""Wrapper for mode strings."""
- def __init__(
- self,
- mode: str,
- bands: tuple[str, ...],
- basemode: str,
- basetype: str,
- typestr: str,
- ) -> None:
- self.mode = mode
- self.bands = bands
- self.basemode = basemode
- self.basetype = basetype
- self.typestr = typestr
+ mode: str
+ bands: tuple[str, ...]
+ basemode: str
+ basetype: str
+ typestr: str
def __str__(self) -> str:
return self.mode
@@ -42,7 +37,6 @@ def __str__(self) -> str:
@lru_cache
def getmode(mode: str) -> ModeDescriptor:
"""Gets a mode descriptor for the given mode."""
- # initialize mode cache
endian = "<" if sys.byteorder == "little" else ">"
modes = {
@@ -50,8 +44,8 @@ def getmode(mode: str) -> ModeDescriptor:
# Bits need to be extended to bytes
"1": ("L", "L", ("1",), "|b1"),
"L": ("L", "L", ("L",), "|u1"),
- "I": ("L", "I", ("I",), endian + "i4"),
- "F": ("L", "F", ("F",), endian + "f4"),
+ "I": ("L", "I", ("I",), f"{endian}i4"),
+ "F": ("L", "F", ("F",), f"{endian}f4"),
"P": ("P", "L", ("P",), "|u1"),
"RGB": ("RGB", "L", ("R", "G", "B"), "|u1"),
"RGBX": ("RGB", "L", ("R", "G", "B", "X"), "|u1"),
@@ -71,6 +65,8 @@ def getmode(mode: str) -> ModeDescriptor:
"PA": ("RGB", "L", ("P", "A"), "|u1"),
}
if mode in modes:
+ if mode in ("BGR;15", "BGR;16", "BGR;24"):
+ deprecate(mode, 12)
base_mode, base_type, bands, type_str = modes[mode]
return ModeDescriptor(mode, bands, base_mode, base_type, type_str)
@@ -82,8 +78,8 @@ def getmode(mode: str) -> ModeDescriptor:
"I;16LS": "u2",
"I;16BS": ">i2",
- "I;16N": endian + "u2",
- "I;16NS": endian + "i2",
+ "I;16N": f"{endian}u2",
+ "I;16NS": f"{endian}i2",
"I;32": "u4",
"I;32L": " tuple[int, Image.Image]:
"""Run a single morphological operation on an image
Returns a tuple of the number of changed pixels and the
@@ -216,7 +216,7 @@ def apply(self, image: Image.Image):
count = _imagingmorph.apply(bytes(self.lut), image.im.id, outimage.im.id)
return count, outimage
- def match(self, image: Image.Image):
+ def match(self, image: Image.Image) -> list[tuple[int, int]]:
"""Get a list of coordinates matching the morphological operation on
an image.
@@ -231,7 +231,7 @@ def match(self, image: Image.Image):
raise ValueError(msg)
return _imagingmorph.match(bytes(self.lut), image.im.id)
- def get_on_pixels(self, image: Image.Image):
+ def get_on_pixels(self, image: Image.Image) -> list[tuple[int, int]]:
"""Get a list of all turned on pixels in a binary image
Returns a list of tuples of (x,y) coordinates
diff --git a/src/PIL/ImageOps.py b/src/PIL/ImageOps.py
index 33db8fa50c7..a84c0834561 100644
--- a/src/PIL/ImageOps.py
+++ b/src/PIL/ImageOps.py
@@ -497,7 +497,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
@@ -709,14 +709,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 770d10025c8..ed38285dc28 100644
--- a/src/PIL/ImagePalette.py
+++ b/src/PIL/ImagePalette.py
@@ -18,10 +18,13 @@
from __future__ import annotations
import array
-from typing import Sequence
+from typing import IO, TYPE_CHECKING, Sequence
from . import GimpGradientFile, GimpPaletteFile, ImageColor, PaletteFile
+if TYPE_CHECKING:
+ from . import Image
+
class ImagePalette:
"""
@@ -35,23 +38,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,10 +70,10 @@ def colors(self):
return self._colors
@colors.setter
- def colors(self, colors):
+ def colors(self, colors: dict[tuple[int, ...], int]) -> None:
self._colors = colors
- def copy(self):
+ def copy(self) -> ImagePalette:
new = ImagePalette()
new.mode = self.mode
@@ -77,7 +84,7 @@ def copy(self):
return new
- def getdata(self):
+ def getdata(self) -> tuple[str, Sequence[int] | bytes | bytearray]:
"""
Get palette contents in format suitable for the low-level
``im.putpalette`` primitive.
@@ -88,7 +95,7 @@ def getdata(self):
return self.rawmode, self.palette
return self.mode, self.tobytes()
- def tobytes(self):
+ def tobytes(self) -> bytes:
"""Convert palette to bytes.
.. warning:: This method is experimental.
@@ -104,11 +111,13 @@ def tobytes(self):
# 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 +137,11 @@ def _new_color_index(self, image=None, e=None):
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,22 +164,23 @@ def getcolor(self, color, image=None) -> int:
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):
+ def save(self, fp: str | IO[str]) -> None:
"""Save palette to text file.
.. warning:: This method is experimental.
@@ -193,7 +207,7 @@ def save(self, fp):
# Internal
-def raw(rawmode, data) -> ImagePalette:
+def raw(rawmode, data: Sequence[int] | bytes | bytearray) -> ImagePalette:
palette = ImagePalette()
palette.rawmode = rawmode
palette.palette = data
@@ -205,50 +219,57 @@ 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
-def make_gamma_lut(exp):
+def make_gamma_lut(exp: float) -> list[int]:
return [int(((i / 255.0) ** exp) * 255.0 + 0.5) for i in range(256)]
-def negative(mode="RGB"):
+def negative(mode: str = "RGB") -> ImagePalette:
palette = list(range(256 * len(mode)))
palette.reverse()
return ImagePalette(mode, [i // len(mode) for i in palette])
-def random(mode="RGB"):
+def random(mode: str = "RGB") -> ImagePalette:
from random import randint
palette = [randint(0, 255) for _ in range(256 * len(mode))]
return ImagePalette(mode, palette)
-def sepia(white="#fff0c0"):
+def sepia(white: str = "#fff0c0") -> ImagePalette:
bands = [make_linear_lut(0, band) for band in ImageColor.getrgb(white)]
return ImagePalette("RGB", [bands[i % 3][i // 3] for i in range(256 * 3)])
-def wedge(mode="RGB"):
+def wedge(mode: str = "RGB") -> ImagePalette:
palette = list(range(256 * len(mode)))
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 293ba4941e3..35a37760c76 100644
--- a/src/PIL/ImageQt.py
+++ b/src/PIL/ImageQt.py
@@ -152,7 +152,7 @@ def _toqclass_helper(im):
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+
+ elif im.mode == "I;16":
im = im.point(lambda i: i * 256)
format = qt_format.Format_Grayscale16
@@ -196,7 +196,7 @@ def __init__(self, im):
self.setColorTable(im_data["colortable"])
-def toqimage(im):
+def toqimage(im) -> ImageQt:
return ImageQt(im)
diff --git a/src/PIL/ImageShow.py b/src/PIL/ImageShow.py
index 4e505f2eea5..037d6f492ce 100644
--- a/src/PIL/ImageShow.py
+++ b/src/PIL/ImageShow.py
@@ -118,6 +118,8 @@ def show_file(self, path: str, **options: Any) -> int:
"""
Display given file.
"""
+ if not os.path.exists(path):
+ raise FileNotFoundError
os.system(self.get_command(path, **options)) # nosec
return 1
@@ -142,6 +144,8 @@ def show_file(self, path: str, **options: Any) -> int:
"""
Display given file.
"""
+ if not os.path.exists(path):
+ raise FileNotFoundError
subprocess.Popen(
self.get_command(path, **options),
shell=True,
@@ -171,6 +175,8 @@ def show_file(self, path: str, **options: Any) -> int:
"""
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:
@@ -199,7 +205,7 @@ def get_command_ex(self, file: str, **options: Any) -> tuple[str, str]:
def get_command(self, file: str, **options: Any) -> str:
command = self.get_command_ex(file, **options)[0]
- return f"({command} {quote(file)}"
+ return f"{command} {quote(file)}"
class XDGViewer(UnixViewer):
@@ -215,6 +221,8 @@ def show_file(self, path: str, **options: Any) -> int:
"""
Display given file.
"""
+ if not os.path.exists(path):
+ raise FileNotFoundError
subprocess.Popen(["xdg-open", path])
return 1
@@ -237,6 +245,8 @@ def show_file(self, path: str, **options: Any) -> int:
"""
Display given file.
"""
+ if not os.path.exists(path):
+ raise FileNotFoundError
args = ["display"]
title = options.get("title")
if title:
@@ -259,6 +269,8 @@ def show_file(self, path: str, **options: Any) -> int:
"""
Display given file.
"""
+ if not os.path.exists(path):
+ raise FileNotFoundError
subprocess.Popen(["gm", "display", path])
return 1
@@ -275,6 +287,8 @@ def show_file(self, path: str, **options: Any) -> int:
"""
Display given file.
"""
+ if not os.path.exists(path):
+ raise FileNotFoundError
subprocess.Popen(["eog", "-n", path])
return 1
@@ -299,6 +313,8 @@ def show_file(self, path: str, **options: Any) -> int:
"""
Display given file.
"""
+ if not os.path.exists(path):
+ raise FileNotFoundError
args = ["xv"]
title = options.get("title")
if title:
diff --git a/src/PIL/ImageStat.py b/src/PIL/ImageStat.py
index 13864e59cfc..8bc504526f0 100644
--- a/src/PIL/ImageStat.py
+++ b/src/PIL/ImageStat.py
@@ -23,35 +23,58 @@
from __future__ import annotations
import math
+from functools import cached_property
+
+from . import Image
class Stat:
- def __init__(self, image_or_list, mask=None):
- try:
- if mask:
- self.h = image_or_list.histogram(mask)
- else:
- self.h = image_or_list.histogram()
- except AttributeError:
- self.h = image_or_list # assume it to be a histogram list
- if not isinstance(self.h, list):
- msg = "first argument must be image or list"
+ def __init__(
+ self, image_or_list: Image.Image | list[int], mask: Image.Image | None = None
+ ) -> None:
+ """
+ Calculate statistics for the given image. If a mask is included,
+ only the regions covered by that mask are included in the
+ statistics. You can also pass in a previously calculated histogram.
+
+ :param image: A PIL image, or a precalculated histogram.
+
+ .. note::
+
+ For a PIL image, calculations rely on the
+ :py:meth:`~PIL.Image.Image.histogram` method. The pixel counts are
+ grouped into 256 bins, even if the image has more than 8 bits per
+ channel. So ``I`` and ``F`` mode images have a maximum ``mean``,
+ ``median`` and ``rms`` of 255, and cannot have an ``extrema`` maximum
+ of more than 255.
+
+ :param mask: An optional mask.
+ """
+ if isinstance(image_or_list, Image.Image):
+ self.h = image_or_list.histogram(mask)
+ elif isinstance(image_or_list, list):
+ self.h = image_or_list
+ else:
+ msg = "first argument must be image or list" # type: ignore[unreachable]
raise TypeError(msg)
self.bands = list(range(len(self.h) // 256))
- def __getattr__(self, id):
- """Calculate missing attribute"""
- if id[:4] == "_get":
- raise AttributeError(id)
- # calculate missing attribute
- v = getattr(self, "_get" + id)()
- setattr(self, id, v)
- return v
-
- def _getextrema(self):
- """Get min/max values for each band in the image"""
-
- def minmax(histogram):
+ @cached_property
+ def extrema(self) -> list[tuple[int, int]]:
+ """
+ Min/max values for each band in the image.
+
+ .. note::
+ This relies on the :py:meth:`~PIL.Image.Image.histogram` method, and
+ simply returns the low and high bins used. This is correct for
+ images with 8 bits per channel, but fails for other modes such as
+ ``I`` or ``F``. Instead, use :py:meth:`~PIL.Image.Image.getextrema` to
+ return per-band extrema for the image. This is more correct and
+ efficient because, for non-8-bit modes, the histogram method uses
+ :py:meth:`~PIL.Image.Image.getextrema` to determine the bins used.
+ """
+
+ def minmax(histogram: list[int]) -> tuple[int, int]:
res_min, res_max = 255, 0
for i in range(256):
if histogram[i]:
@@ -65,12 +88,14 @@ def minmax(histogram):
return [minmax(self.h[i:]) for i in range(0, len(self.h), 256)]
- def _getcount(self):
- """Get total number of pixels in each layer"""
+ @cached_property
+ def count(self) -> list[int]:
+ """Total number of pixels for each band in the image."""
return [sum(self.h[i : i + 256]) for i in range(0, len(self.h), 256)]
- def _getsum(self):
- """Get sum of all pixels in each layer"""
+ @cached_property
+ def sum(self) -> list[float]:
+ """Sum of all pixels for each band in the image."""
v = []
for i in range(0, len(self.h), 256):
@@ -80,8 +105,9 @@ def _getsum(self):
v.append(layer_sum)
return v
- def _getsum2(self):
- """Get squared sum of all pixels in each layer"""
+ @cached_property
+ def sum2(self) -> list[float]:
+ """Squared sum of all pixels for each band in the image."""
v = []
for i in range(0, len(self.h), 256):
@@ -91,12 +117,14 @@ def _getsum2(self):
v.append(sum2)
return v
- def _getmean(self):
- """Get average pixel level for each layer"""
+ @cached_property
+ def mean(self) -> list[float]:
+ """Average (arithmetic mean) pixel level for each band in the image."""
return [self.sum[i] / self.count[i] for i in self.bands]
- def _getmedian(self):
- """Get median pixel level for each layer"""
+ @cached_property
+ def median(self) -> list[int]:
+ """Median pixel level for each band in the image."""
v = []
for i in self.bands:
@@ -110,19 +138,22 @@ def _getmedian(self):
v.append(j)
return v
- def _getrms(self):
- """Get RMS for each layer"""
+ @cached_property
+ def rms(self) -> list[float]:
+ """RMS (root-mean-square) for each band in the image."""
return [math.sqrt(self.sum2[i] / self.count[i]) for i in self.bands]
- def _getvar(self):
- """Get variance for each layer"""
+ @cached_property
+ def var(self) -> list[float]:
+ """Variance for each band in the image."""
return [
(self.sum2[i] - (self.sum[i] ** 2.0) / self.count[i]) / self.count[i]
for i in self.bands
]
- def _getstddev(self):
- """Get standard deviation for each layer"""
+ @cached_property
+ def stddev(self) -> list[float]:
+ """Standard deviation for each band in the image."""
return [math.sqrt(self.var[i]) for i in self.bands]
diff --git a/src/PIL/ImageTk.py b/src/PIL/ImageTk.py
index 10b2cc69a1e..90defdbbc23 100644
--- a/src/PIL/ImageTk.py
+++ b/src/PIL/ImageTk.py
@@ -37,7 +37,7 @@
_pilbitmap_ok = None
-def _pilbitmap_check():
+def _pilbitmap_check() -> int:
global _pilbitmap_ok
if _pilbitmap_ok is None:
try:
@@ -128,7 +128,7 @@ def __init__(self, image=None, size=None, **kw):
if image:
self.paste(image)
- def __del__(self):
+ def __del__(self) -> None:
name = self.__photo.name
self.__photo.name = None
try:
@@ -136,7 +136,7 @@ def __del__(self):
except Exception:
pass # ignore internal errors
- def __str__(self):
+ def __str__(self) -> str:
"""
Get the Tkinter photo image identifier. This method is automatically
called by Tkinter whenever a PhotoImage object is passed to a Tkinter
@@ -146,7 +146,7 @@ def __str__(self):
"""
return str(self.__photo)
- def width(self):
+ def width(self) -> int:
"""
Get the width of the image.
@@ -154,7 +154,7 @@ def width(self):
"""
return self.__size[0]
- def height(self):
+ def height(self) -> int:
"""
Get the height of the image.
@@ -162,7 +162,7 @@ def height(self):
"""
return self.__size[1]
- def paste(self, im):
+ def paste(self, im: Image.Image) -> None:
"""
Paste a PIL image into the photo image. Note that this can
be very slow if the photo image is displayed.
@@ -219,7 +219,7 @@ def __init__(self, image=None, **kw):
kw["data"] = image.tobitmap()
self.__photo = tkinter.BitmapImage(**kw)
- def __del__(self):
+ def __del__(self) -> None:
name = self.__photo.name
self.__photo.name = None
try:
@@ -227,7 +227,7 @@ def __del__(self):
except Exception:
pass # ignore internal errors
- def width(self):
+ def width(self) -> int:
"""
Get the width of the image.
@@ -235,7 +235,7 @@ def width(self):
"""
return self.__size[0]
- def height(self):
+ def height(self) -> int:
"""
Get the height of the image.
@@ -243,7 +243,7 @@ def height(self):
"""
return self.__size[1]
- def __str__(self):
+ def __str__(self) -> str:
"""
Get the Tkinter bitmap image identifier. This method is automatically
called by Tkinter whenever a BitmapImage object is passed to a Tkinter
@@ -254,7 +254,7 @@ def __str__(self):
return str(self.__photo)
-def getimage(photo):
+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
diff --git a/src/PIL/ImageTransform.py b/src/PIL/ImageTransform.py
index 6aa82dadd9c..ffd7916745c 100644
--- a/src/PIL/ImageTransform.py
+++ b/src/PIL/ImageTransform.py
@@ -14,7 +14,7 @@
#
from __future__ import annotations
-from typing import Sequence
+from typing import Any, Sequence
from . import Image
@@ -24,7 +24,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]]:
@@ -34,7 +34,7 @@ def transform(
self,
size: tuple[int, int],
image: Image.Image,
- **options: dict[str, str | int | tuple[int, ...] | list[int]],
+ **options: Any,
) -> Image.Image:
"""Perform the transform. Called from :py:meth:`.Image.transform`."""
# can be overridden
diff --git a/src/PIL/ImageWin.py b/src/PIL/ImageWin.py
index 75910d2d9ab..978c5a9d176 100644
--- a/src/PIL/ImageWin.py
+++ b/src/PIL/ImageWin.py
@@ -28,10 +28,10 @@ class HDC:
methods.
"""
- def __init__(self, dc):
+ def __init__(self, dc: int) -> None:
self.dc = dc
- def __int__(self):
+ def __int__(self) -> int:
return self.dc
@@ -42,10 +42,10 @@ class HWND:
methods, instead of a DC.
"""
- def __init__(self, wnd):
+ def __init__(self, wnd: int) -> None:
self.wnd = wnd
- def __int__(self):
+ def __int__(self) -> int:
return self.wnd
@@ -69,19 +69,22 @@ 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] | list[int] | None = None
+ ) -> None:
+ if isinstance(image, str):
+ mode = image
+ image = ""
+ 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):
@@ -149,7 +152,9 @@ def query_palette(self, handle):
result = self.image.query_palette(handle)
return result
- def paste(self, im, box=None):
+ def paste(
+ self, im: Image.Image, box: tuple[int, int, int, int] | None = None
+ ) -> None:
"""
Paste a PIL image into the bitmap image.
@@ -169,16 +174,16 @@ def paste(self, im, box=None):
else:
self.image.paste(im.im)
- def frombytes(self, buffer):
+ def frombytes(self, buffer: bytes) -> None:
"""
Load display memory contents from byte data.
:param buffer: A buffer containing display data (usually
data returned from :py:func:`~PIL.ImageWin.Dib.tobytes`)
"""
- return self.image.frombytes(buffer)
+ self.image.frombytes(buffer)
- def tobytes(self):
+ def tobytes(self) -> bytes:
"""
Copy display memory contents to bytes object.
@@ -190,13 +195,15 @@ def tobytes(self):
class Window:
"""Create a Window with the given title size."""
- def __init__(self, title="PIL", width=None, height=None):
+ def __init__(
+ self, title: str = "PIL", width: int | None = None, height: int | None = None
+ ) -> None:
self.hwnd = Image.core.createwindow(
title, self.__dispatcher, width or 0, height or 0
)
def __dispatcher(self, action, *args):
- return getattr(self, "ui_handle_" + action)(*args)
+ return getattr(self, f"ui_handle_{action}")(*args)
def ui_handle_clear(self, dc, x0, y0, x1, y1):
pass
@@ -204,7 +211,7 @@ def ui_handle_clear(self, dc, x0, y0, x1, y1):
def ui_handle_damage(self, x0, y0, x1, y1):
pass
- def ui_handle_destroy(self):
+ def ui_handle_destroy(self) -> None:
pass
def ui_handle_repair(self, dc, x0, y0, x1, y1):
@@ -213,7 +220,7 @@ def ui_handle_repair(self, dc, x0, y0, x1, y1):
def ui_handle_resize(self, width, height):
pass
- def mainloop(self):
+ def mainloop(self) -> None:
Image.core.eventloop()
diff --git a/src/PIL/IptcImagePlugin.py b/src/PIL/IptcImagePlugin.py
index 4096094348a..73df83bfb31 100644
--- a/src/PIL/IptcImagePlugin.py
+++ b/src/PIL/IptcImagePlugin.py
@@ -57,7 +57,7 @@ def dump(c: Sequence[int | bytes]) -> None:
""".. deprecated:: 10.2.0"""
deprecate("IptcImagePlugin.dump", 12)
for i in c:
- print("%02x" % _i8(i), end=" ")
+ print(f"{_i8(i):02x}", end=" ")
print()
diff --git a/src/PIL/Jpeg2KImagePlugin.py b/src/PIL/Jpeg2KImagePlugin.py
index be000c351b6..e50cd7799a3 100644
--- a/src/PIL/Jpeg2KImagePlugin.py
+++ b/src/PIL/Jpeg2KImagePlugin.py
@@ -18,6 +18,7 @@
import io
import os
import struct
+from typing import IO, Tuple, cast
from . import Image, ImageFile, ImagePalette, _binary
@@ -34,7 +35,7 @@ def __init__(self, fp, length=-1):
self.length = length
self.remaining_in_box = -1
- def _can_read(self, num_bytes):
+ def _can_read(self, num_bytes: int) -> bool:
if self.has_length and self.fp.tell() + num_bytes > self.length:
# Outside box: ensure we don't read past the known file length
return False
@@ -44,7 +45,7 @@ def _can_read(self, num_bytes):
else:
return True # No length known, just read
- def _read_bytes(self, num_bytes):
+ def _read_bytes(self, num_bytes: int) -> bytes:
if not self._can_read(num_bytes):
msg = "Not enough data in header"
raise SyntaxError(msg)
@@ -58,32 +59,32 @@ def _read_bytes(self, num_bytes):
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)
- def read_boxes(self):
+ def read_boxes(self) -> BoxReader:
size = self.remaining_in_box
data = self._read_bytes(size)
return BoxReader(io.BytesIO(data), size)
- def has_next_box(self):
+ def has_next_box(self) -> bool:
if self.has_length:
return self.fp.tell() + self.remaining_in_box < self.length
else:
return True
- def next_box_type(self):
+ def next_box_type(self) -> bytes:
# Skip the rest of the box if it has not been read
if self.remaining_in_box > 0:
self.fp.seek(self.remaining_in_box, os.SEEK_CUR)
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
@@ -96,7 +97,7 @@ def next_box_type(self):
return tbox
-def _parse_codestream(fp):
+def _parse_codestream(fp) -> 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."""
@@ -121,17 +122,19 @@ 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):
@@ -176,6 +179,10 @@ def _parse_jp2_header(fp):
mode = "RGB"
elif nc == 4:
mode = "RGBA"
+ elif tbox == b"colr" and nc == 4:
+ meth, _, _, enumcs = header.read_fields(">BBBI")
+ if meth == 1 and enumcs == 12:
+ mode = "CMYK"
elif tbox == b"pclr" and mode in ("L", "LA"):
ne, npc = header.read_fields(">HB")
bitdepths = header.read_fields(">" + ("B" * npc))
@@ -211,7 +218,7 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
format = "JPEG2000"
format_description = "JPEG 2000 (ISO 15444)"
- def _open(self):
+ def _open(self) -> None:
sig = self.fp.read(4)
if sig == b"\xff\x4f\xff\x51":
self.codec = "j2k"
@@ -231,10 +238,6 @@ def _open(self):
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
@@ -263,7 +266,7 @@ def _open(self):
)
]
- def _parse_comment(self):
+ def _parse_comment(self) -> None:
hdr = self.fp.read(2)
length = _binary.i16be(hdr)
self.fp.seek(length - 2, os.SEEK_CUR)
@@ -313,7 +316,7 @@ def load(self):
return ImageFile.ImageFile.load(self)
-def _accept(prefix):
+def _accept(prefix: bytes) -> bool:
return (
prefix[:4] == b"\xff\x4f\xff\x51"
or prefix[:12] == b"\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a"
@@ -324,11 +327,13 @@ def _accept(prefix):
# Save support
-def _save(im, fp, filename):
+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"
diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py
index 81b8749a332..b15bf06d2b1 100644
--- a/src/PIL/JpegImagePlugin.py
+++ b/src/PIL/JpegImagePlugin.py
@@ -42,6 +42,7 @@
import sys
import tempfile
import warnings
+from typing import IO, Any
from . import Image, ImageFile
from ._binary import i16be as i16
@@ -54,7 +55,7 @@
# Parser
-def Skip(self, marker):
+def Skip(self: JpegImageFile, marker: int) -> None:
n = i16(self.fp.read(2)) - 2
ImageFile._safe_read(self.fp, n)
@@ -94,6 +95,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
@@ -158,40 +161,8 @@ 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, marker):
+def COM(self: JpegImageFile, marker: int) -> None:
#
# Comment marker. Store these in the APP dictionary.
n = i16(self.fp.read(2)) - 2
@@ -202,7 +173,7 @@ def COM(self, marker):
self.applist.append(("COM", s))
-def SOF(self, marker):
+def SOF(self: JpegImageFile, marker: int) -> None:
#
# Start of frame marker. Defines the size and mode of the
# image. JPEG is colour blind, so we use some simple
@@ -250,7 +221,7 @@ def SOF(self, marker):
self.layer.append((t[0], t[1] // 16, t[1] & 15, t[2]))
-def DQT(self, marker):
+def DQT(self: JpegImageFile, marker: int) -> None:
#
# Define quantization table. Note that there might be more
# than one table in each marker.
@@ -344,7 +315,7 @@ def DQT(self, marker):
}
-def _accept(prefix):
+def _accept(prefix: bytes) -> bool:
# Magic number was taken from https://en.wikipedia.org/wiki/JPEG
return prefix[:3] == b"\xFF\xD8\xFF"
@@ -408,7 +379,9 @@ def _open(self):
msg = "no marker found"
raise SyntaxError(msg)
- def load_read(self, read_bytes):
+ self._read_dpi_from_exif()
+
+ def load_read(self, read_bytes: int) -> bytes:
"""
internal: read more image data
For premature EOF and LOAD_TRUNCATED_IMAGES adds EOI marker
@@ -424,13 +397,15 @@ def load_read(self, read_bytes):
return s
- def draft(self, mode, size):
+ def draft(
+ self, mode: str | None, size: tuple[int, int] | None
+ ) -> tuple[str, tuple[int, int, float, float]] | None:
if len(self.tile) != 1:
- return
+ return None
# Protect from second call
if self.decoderconfig:
- return
+ return None
d, e, o, a = self.tile[0]
scale = 1
@@ -460,7 +435,7 @@ def draft(self, mode, size):
box = (0, 0, original_size[0] / scale, original_size[1] / scale)
return self.mode, box
- def load_djpeg(self):
+ def load_djpeg(self) -> None:
# ALTERNATIVE: handle JPEGs via the IJG command line utilities
f, path = tempfile.mkstemp()
@@ -491,29 +466,43 @@ def load_djpeg(self):
self.tile = []
- def _getexif(self):
+ def _getexif(self) -> dict[str, Any] | None:
return _getexif(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):
return _getmp(self)
- def getxmp(self):
- """
- 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):
+def _getexif(self) -> dict[str, Any] | None:
if "exif" not in self.info:
return None
return self.getexif()._get_merged_dict()
@@ -641,7 +630,7 @@ def get_sampling(im):
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)
@@ -824,7 +813,7 @@ def validate_qtables(qtables):
ImageFile._save(im, fp, [("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])
@@ -841,6 +830,10 @@ def jpeg_factory(fp=None, filename=None):
try:
mpheader = im._getmp()
if 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/MicImagePlugin.py b/src/PIL/MicImagePlugin.py
index f4529d9ae74..07239887f9f 100644
--- a/src/PIL/MicImagePlugin.py
+++ b/src/PIL/MicImagePlugin.py
@@ -25,7 +25,7 @@
# --------------------------------------------------------------------
-def _accept(prefix):
+def _accept(prefix: bytes) -> bool:
return prefix[:8] == olefile.MAGIC
@@ -38,7 +38,7 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
format_description = "Microsoft Image Composer"
_close_exclusive_fp_after_loading = False
- def _open(self):
+ def _open(self) -> None:
# read the OLE directory and see if this is a likely
# to be a Microsoft Image Composer file
@@ -63,7 +63,7 @@ def _open(self):
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
@@ -85,15 +85,15 @@ def seek(self, frame):
self.frame = frame
- def tell(self):
+ def tell(self) -> int:
return self.frame
- def close(self):
+ def close(self) -> None:
self.__fp.close()
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/MpegImagePlugin.py b/src/PIL/MpegImagePlugin.py
index 1565612f869..ad4d3e93768 100644
--- a/src/PIL/MpegImagePlugin.py
+++ b/src/PIL/MpegImagePlugin.py
@@ -53,6 +53,10 @@ def read(self, bits: int) -> int:
return v
+def _accept(prefix: bytes) -> bool:
+ return prefix[:4] == b"\x00\x00\x01\xb3"
+
+
##
# Image plugin for MPEG streams. This plugin can identify a stream,
# but it cannot read it.
@@ -77,7 +81,7 @@ def _open(self) -> None:
# --------------------------------------------------------------------
# Registry stuff
-Image.register_open(MpegImageFile.format, MpegImageFile)
+Image.register_open(MpegImageFile.format, MpegImageFile, _accept)
Image.register_extensions(MpegImageFile.format, [".mpg", ".mpeg"])
diff --git a/src/PIL/MpoImagePlugin.py b/src/PIL/MpoImagePlugin.py
index ac9820bbf68..f21570661f8 100644
--- a/src/PIL/MpoImagePlugin.py
+++ b/src/PIL/MpoImagePlugin.py
@@ -22,6 +22,7 @@
import itertools
import os
import struct
+from typing import IO
from . import (
Image,
@@ -32,23 +33,18 @@
from ._binary import o32le
-def _save(im, fp, filename):
+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:
@@ -100,7 +96,7 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
format_description = "MPO (CIPA DC-007)"
_close_exclusive_fp_after_loading = False
- def _open(self):
+ def _open(self) -> None:
self.fp.seek(0) # prep the fp in order to pass the JPEG test
JpegImagePlugin.JpegImageFile._open(self)
self._after_jpeg_open()
@@ -124,10 +120,10 @@ def _after_jpeg_open(self, mpheader=None):
# for now we can only handle reading and individual frame extraction
self.readonly = 1
- def load_seek(self, pos):
+ def load_seek(self, pos: int) -> None:
self._fp.seek(pos)
- def seek(self, frame):
+ def seek(self, frame: int) -> None:
if not self._seek_check(frame):
return
self.fp = self._fp
@@ -149,7 +145,7 @@ def seek(self, frame):
self.tile = [("jpeg", (0, 0) + self.size, self.offset, self.tile[0][-1])]
self.__frame = frame
- def tell(self):
+ def tell(self) -> int:
return self.__frame
@staticmethod
diff --git a/src/PIL/MspImagePlugin.py b/src/PIL/MspImagePlugin.py
index 65cc70624b7..0a75c868b97 100644
--- a/src/PIL/MspImagePlugin.py
+++ b/src/PIL/MspImagePlugin.py
@@ -164,7 +164,7 @@ def decode(self, buffer: bytes) -> tuple[int, int]:
# 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)
diff --git a/src/PIL/PSDraw.py b/src/PIL/PSDraw.py
index 848fc2f716a..673eae1d12c 100644
--- a/src/PIL/PSDraw.py
+++ b/src/PIL/PSDraw.py
@@ -17,6 +17,7 @@
from __future__ import annotations
import sys
+from typing import TYPE_CHECKING
from . import EpsImagePlugin
@@ -38,7 +39,7 @@ def __init__(self, fp=None):
fp = sys.stdout
self.fp = fp
- def begin_document(self, id=None):
+ def begin_document(self, id: str | None = None) -> None:
"""Set up printing of a document. (Write PostScript DSC header.)"""
# FIXME: incomplete
self.fp.write(
@@ -52,30 +53,32 @@ def begin_document(self, id=None):
self.fp.write(EDROFF_PS)
self.fp.write(VDI_PS)
self.fp.write(b"%%EndProlog\n")
- self.isofont = {}
+ self.isofont: dict[bytes, int] = {}
- def end_document(self):
+ def end_document(self) -> None:
"""Ends printing. (Write PostScript DSC footer.)"""
self.fp.write(b"%%EndDocument\nrestore showpage\n%%End\n")
if hasattr(self.fp, "flush"):
self.fp.flush()
- def setfont(self, font, size):
+ def setfont(self, font: str, size: int) -> None:
"""
Selects which font to use.
:param font: A PostScript font name
:param size: Size in points.
"""
- font = bytes(font, "UTF-8")
- if font not in self.isofont:
+ font_bytes = bytes(font, "UTF-8")
+ if font_bytes not in self.isofont:
# reencode font
- self.fp.write(b"/PSDraw-%s ISOLatin1Encoding /%s E\n" % (font, font))
- self.isofont[font] = 1
+ self.fp.write(
+ b"/PSDraw-%s ISOLatin1Encoding /%s E\n" % (font_bytes, font_bytes)
+ )
+ self.isofont[font_bytes] = 1
# rough
- self.fp.write(b"/F0 %d /PSDraw-%s F\n" % (size, font))
+ self.fp.write(b"/F0 %d /PSDraw-%s F\n" % (size, font_bytes))
- def line(self, xy0, xy1):
+ def line(self, xy0: tuple[int, int], xy1: tuple[int, int]) -> None:
"""
Draws a line between the two points. Coordinates are given in
PostScript point coordinates (72 points per inch, (0, 0) is the lower
@@ -83,7 +86,7 @@ def line(self, xy0, xy1):
"""
self.fp.write(b"%d %d %d %d Vl\n" % (*xy0, *xy1))
- def rectangle(self, box):
+ def rectangle(self, box: tuple[int, int, int, int]) -> None:
"""
Draws a rectangle.
@@ -92,18 +95,22 @@ def rectangle(self, box):
"""
self.fp.write(b"%d %d M 0 %d %d Vr\n" % box)
- def text(self, xy, text):
+ def text(self, xy: tuple[int, int], text: str) -> None:
"""
Draws text at the given position. You must use
:py:meth:`~PIL.PSDraw.PSDraw.setfont` before calling this method.
"""
- text = bytes(text, "UTF-8")
- text = b"\\(".join(text.split(b"("))
- text = b"\\)".join(text.split(b")"))
- xy += (text,)
- self.fp.write(b"%d %d M (%s) S\n" % xy)
+ text_bytes = bytes(text, "UTF-8")
+ text_bytes = b"\\(".join(text_bytes.split(b"("))
+ text_bytes = b"\\)".join(text_bytes.split(b")"))
+ self.fp.write(b"%d %d M (%s) S\n" % (xy + (text_bytes,)))
- def image(self, box, im, dpi=None):
+ if TYPE_CHECKING:
+ from . import Image
+
+ def image(
+ self, box: tuple[int, int, int, int], im: Image.Image, dpi: int | None = None
+ ) -> None:
"""Draw a PIL image, centered in the given box."""
# default resolution depends on mode
if not dpi:
@@ -131,7 +138,7 @@ def image(self, box, im, dpi=None):
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/PaletteFile.py b/src/PIL/PaletteFile.py
index dc31754020e..81652e5eec2 100644
--- a/src/PIL/PaletteFile.py
+++ b/src/PIL/PaletteFile.py
@@ -14,6 +14,8 @@
#
from __future__ import annotations
+from typing import IO
+
from ._binary import o8
@@ -22,8 +24,8 @@ class PaletteFile:
rawmode = "RGB"
- def __init__(self, fp):
- self.palette = [(i, i, i) for i in range(256)]
+ def __init__(self, fp: IO[bytes]) -> None:
+ palette = [o8(i) * 3 for i in range(256)]
while True:
s = fp.readline()
@@ -44,9 +46,9 @@ def __init__(self, fp):
g = b = r
if 0 <= i <= 255:
- self.palette[i] = o8(r) + o8(g) + o8(b)
+ palette[i] = o8(r) + o8(g) + o8(b)
- self.palette = b"".join(self.palette)
+ self.palette = b"".join(palette)
- def getpalette(self):
+ def getpalette(self) -> tuple[bytes, str]:
return self.palette, self.rawmode
diff --git a/src/PIL/PalmImagePlugin.py b/src/PIL/PalmImagePlugin.py
index 65be7fef7b1..1735070f81b 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 @@
# 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 @@ def build_prototype_image():
# (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,22 +129,23 @@ 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"
- rawmode = "P;" + str(bpp)
+ im._mode = "P"
+ rawmode = f"P;{bpp}"
version = 1
elif im.mode == "1":
diff --git a/src/PIL/PcxImagePlugin.py b/src/PIL/PcxImagePlugin.py
index 026bfd9a01b..dd42003b5a3 100644
--- a/src/PIL/PcxImagePlugin.py
+++ b/src/PIL/PcxImagePlugin.py
@@ -144,7 +144,7 @@ def _open(self) -> None:
}
-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:
diff --git a/src/PIL/PdfImagePlugin.py b/src/PIL/PdfImagePlugin.py
index 1777f1f20db..f0da1e04797 100644
--- a/src/PIL/PdfImagePlugin.py
+++ b/src/PIL/PdfImagePlugin.py
@@ -25,6 +25,7 @@
import math
import os
import time
+from typing import IO
from . import Image, ImageFile, ImageSequence, PdfParser, __version__, features
@@ -39,7 +40,7 @@
# 5. page contents
-def _save_all(im, fp, filename):
+def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
_save(im, fp, filename, save_all=True)
diff --git a/src/PIL/PdfParser.py b/src/PIL/PdfParser.py
index 2542d4e9127..9e22313475e 100644
--- a/src/PIL/PdfParser.py
+++ b/src/PIL/PdfParser.py
@@ -13,7 +13,7 @@
# see 7.9.2.2 Text String Type on page 86 and D.3 PDFDocEncoding Character Set
# on page 656
-def encode_text(s):
+def encode_text(s: str) -> bytes:
return codecs.BOM_UTF16_BE + s.encode("utf_16_be")
@@ -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)
@@ -87,28 +87,27 @@ class IndirectReferenceTuple(NamedTuple):
class IndirectReference(IndirectReferenceTuple):
- def __str__(self):
+ def __str__(self) -> str:
return f"{self.object_id} {self.generation} R"
- def __bytes__(self):
+ 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):
return not (self == other)
- def __hash__(self):
+ def __hash__(self) -> int:
return hash((self.object_id, self.generation))
class IndirectObjectDef(IndirectReference):
- def __str__(self):
+ def __str__(self) -> str:
return f"{self.object_id} {self.generation} obj"
@@ -144,15 +143,13 @@ def __delitem__(self, key):
elif key in self.deleted_entries:
generation = self.deleted_entries[key]
else:
- msg = (
- "object ID " + str(key) + " cannot be deleted because it doesn't exist"
- )
+ msg = f"object ID {key} cannot be deleted because it doesn't exist"
raise IndexError(msg)
def __contains__(self, key):
return key in self.existing_entries or key in self.new_entries
- def __len__(self):
+ def __len__(self) -> int:
return len(
set(self.existing_entries.keys())
| set(self.new_entries.keys())
@@ -213,7 +210,7 @@ def __init__(self, name):
else:
self.name = name.encode("us-ascii")
- def name_as_str(self):
+ def name_as_str(self) -> str:
return self.name.decode("us-ascii")
def __eq__(self, other):
@@ -221,11 +218,11 @@ def __eq__(self, other):
isinstance(other, PdfName) and other.name == self.name
) or other == self.name
- def __hash__(self):
+ def __hash__(self) -> int:
return hash(self.name)
- def __repr__(self):
- return f"PdfName({repr(self.name)})"
+ def __repr__(self) -> str:
+ return f"{self.__class__.__name__}({repr(self.name)})"
@classmethod
def from_pdf_stream(cls, data):
@@ -233,7 +230,7 @@ def from_pdf_stream(cls, data):
allowed_chars = set(range(33, 127)) - {ord(c) for c in "#%/()<>[]{}"}
- def __bytes__(self):
+ def __bytes__(self) -> bytes:
result = bytearray(b"/")
for b in self.name:
if b in self.allowed_chars:
@@ -244,7 +241,7 @@ def __bytes__(self):
class PdfArray(List[Any]):
- def __bytes__(self):
+ def __bytes__(self) -> bytes:
return b"[ " + b" ".join(pdf_repr(x) for x in self) + b" ]"
@@ -288,7 +285,7 @@ def __getattr__(self, key):
value = time.gmtime(calendar.timegm(value) + offset)
return value
- def __bytes__(self):
+ def __bytes__(self) -> bytes:
out = bytearray(b"<<")
for key, value in self.items():
if value is None:
@@ -306,7 +303,7 @@ class PdfBinary:
def __init__(self, data):
self.data = data
- def __bytes__(self):
+ def __bytes__(self) -> bytes:
return b"<%s>" % b"".join(b"%02X" % b for b in self.data)
@@ -404,41 +401,40 @@ def __init__(self, filename=None, f=None, buf=None, start_offset=0, mode="rb"):
if f:
self.seek_end()
- def __enter__(self):
+ 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):
+ def start_writing(self) -> None:
self.close_buf()
self.seek_end()
- def close_buf(self):
+ def close_buf(self) -> None:
try:
self.buf.close()
except AttributeError:
pass
self.buf = None
- def close(self):
+ def close(self) -> None:
if self.should_close_buf:
self.close_buf()
if self.f is not None and self.should_close_file:
self.f.close()
self.f = None
- def seek_end(self):
+ def seek_end(self) -> None:
self.f.seek(0, os.SEEK_END)
- def write_header(self):
+ def write_header(self) -> None:
self.f.write(b"%PDF-1.4\n")
def write_comment(self, s):
self.f.write(f"% {s}\n".encode())
- def write_catalog(self):
+ def write_catalog(self) -> IndirectReference:
self.del_root()
self.root_ref = self.next_object_id(self.f.tell())
self.pages_ref = self.next_object_id(0)
@@ -452,7 +448,7 @@ def write_catalog(self):
)
return self.root_ref
- def rewrite_pages(self):
+ def rewrite_pages(self) -> None:
pages_tree_nodes_to_delete = []
for i, page_ref in enumerate(self.orig_pages):
page_info = self.cached_objects[page_ref]
@@ -531,7 +527,7 @@ def write_obj(self, ref, *objs, **dict_obj):
f.write(b"endobj\n")
return ref
- def del_root(self):
+ def del_root(self) -> None:
if self.root_ref is None:
return
del self.xref_table[self.root_ref.object_id]
@@ -549,7 +545,7 @@ def get_buf_from_file(f):
except ValueError: # cannot mmap an empty file
return b""
- def read_pdf_info(self):
+ def read_pdf_info(self) -> None:
self.file_size_total = len(self.buf)
self.file_size_this = self.file_size_total - self.start_offset
self.read_trailer()
@@ -825,11 +821,10 @@ def get_value(cls, data, offset, expect_indirect=None, max_nesting=-1):
m = cls.re_stream_start.match(data, offset)
if m:
try:
- stream_len = int(result[b"Length"])
- except (TypeError, KeyError, ValueError) as e:
- msg = "bad or missing Length in stream dict (%r)" % result.get(
- b"Length", None
- )
+ 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_data = data[m.end() : m.end() + stream_len]
m = cls.re_stream_end.match(data, m.end() + stream_len)
@@ -884,7 +879,7 @@ def get_value(cls, data, offset, expect_indirect=None, max_nesting=-1):
if m:
return cls.get_literal_string(data, m.end())
# return None, offset # fallback (only for debugging)
- msg = "unrecognized object: " + repr(data[offset : offset + 32])
+ msg = f"unrecognized object: {repr(data[offset : offset + 32])}"
raise PdfFormatError(msg)
re_lit_str_token = re.compile(
diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py
index d922bacfb9c..d2834929b8f 100644
--- a/src/PIL/PngImagePlugin.py
+++ b/src/PIL/PngImagePlugin.py
@@ -39,6 +39,7 @@
import warnings
import zlib
from enum import IntEnum
+from typing import IO, TYPE_CHECKING, Any, NoReturn
from . import Image, ImageChops, ImageFile, ImagePalette, ImageSequence
from ._binary import i16be as i16
@@ -47,6 +48,9 @@
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
@@ -149,14 +153,15 @@ def _crc32(data, seed=0):
class ChunkStream:
- def __init__(self, fp):
- self.fp = fp
- self.queue = []
+ def __init__(self, fp: IO[bytes]) -> None:
+ self.fp: IO[bytes] | None = fp
+ self.queue: list[tuple[bytes, int, int]] | None = []
- def read(self):
+ def read(self) -> tuple[bytes, int, int]:
"""Fetch a new chunk. Returns header information."""
cid = None
+ assert self.fp is not None
if self.queue:
cid, pos, length = self.queue.pop()
self.fp.seek(pos)
@@ -173,25 +178,26 @@ def read(self):
return cid, pos, length
- def __enter__(self):
+ def __enter__(self) -> ChunkStream:
return self
- def __exit__(self, *args):
+ def __exit__(self, *args: object) -> None:
self.close()
- def close(self):
+ def close(self) -> None:
self.queue = self.fp = None
- def push(self, cid, pos, length):
+ def push(self, cid: bytes, pos: int, length: int) -> None:
+ assert self.queue is not None
self.queue.append((cid, pos, length))
def call(self, cid, pos, length):
"""Call the appropriate chunk handler"""
logger.debug("STREAM %r %s %s", cid, pos, length)
- return getattr(self, "chunk_" + cid.decode("ascii"))(pos, length)
+ return getattr(self, f"chunk_{cid.decode('ascii')}")(pos, length)
- def crc(self, cid, data):
+ def crc(self, cid: bytes, data: bytes) -> None:
"""Read and verify checksum"""
# Skip CRC checks for ancillary chunks if allowed to load truncated
@@ -201,6 +207,7 @@ def crc(self, cid, data):
self.crc_skip(cid, data)
return
+ assert self.fp is not None
try:
crc1 = _crc32(data, _crc32(cid))
crc2 = i32(self.fp.read(4))
@@ -211,12 +218,13 @@ def crc(self, cid, data):
msg = f"broken PNG file (incomplete checksum in {repr(cid)})"
raise SyntaxError(msg) from e
- def crc_skip(self, cid, data):
+ def crc_skip(self, cid: bytes, data: bytes) -> None:
"""Read checksum"""
+ assert self.fp is not None
self.fp.read(4)
- def verify(self, endchunk=b"IEND"):
+ def verify(self, endchunk: bytes = b"IEND") -> list[bytes]:
# Simple approach; just calculate checksum for all remaining
# blocks. Must be called directly after open.
@@ -244,6 +252,9 @@ class iTXt(str):
"""
+ lang: str | bytes | None
+ tkey: str | bytes | None
+
@staticmethod
def __new__(cls, text, lang=None, tkey=None):
"""
@@ -265,10 +276,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.
@@ -278,12 +289,16 @@ def add(self, cid, data, after_idat=False):
"""
- 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
@@ -311,7 +326,9 @@ def add_itxt(self, key, value, lang="", tkey="", zip=False):
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
@@ -321,7 +338,13 @@ def add_text(self, key, value, zip=False):
"""
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):
@@ -361,7 +384,7 @@ def __init__(self, fp):
self.text_memory = 0
- def check_text_memory(self, chunklen):
+ def check_text_memory(self, chunklen: int) -> None:
self.text_memory += chunklen
if self.text_memory > MAX_TEXT_MEMORY:
msg = (
@@ -370,19 +393,19 @@ def check_text_memory(self, chunklen):
)
raise ValueError(msg)
- def save_rewind(self):
+ def save_rewind(self) -> None:
self.rewind_state = {
"info": self.im_info.copy(),
"tile": self.im_tile,
"seq_num": self._seq_num,
}
- def rewind(self):
+ 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"]
- def chunk_iCCP(self, pos, length):
+ def chunk_iCCP(self, pos: int, length: int) -> bytes:
# ICC profile
s = ImageFile._safe_read(self.fp, length)
# according to PNG spec, the iCCP chunk contains:
@@ -409,7 +432,7 @@ def chunk_iCCP(self, pos, length):
self.im_info["icc_profile"] = icc_profile
return s
- def chunk_IHDR(self, pos, length):
+ def chunk_IHDR(self, pos: int, length: int) -> bytes:
# image header
s = ImageFile._safe_read(self.fp, length)
if length < 13:
@@ -429,7 +452,7 @@ def chunk_IHDR(self, pos, length):
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)]
@@ -442,18 +465,18 @@ def chunk_IDAT(self, pos, 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, length):
+ def chunk_PLTE(self, pos: int, length: int) -> bytes:
# palette
s = ImageFile._safe_read(self.fp, length)
if self.im_mode == "P":
self.im_palette = "RGB", s
return s
- def chunk_tRNS(self, pos, length):
+ def chunk_tRNS(self, pos: int, length: int) -> bytes:
# transparency
s = ImageFile._safe_read(self.fp, length)
if self.im_mode == "P":
@@ -473,13 +496,13 @@ def chunk_tRNS(self, pos, length):
self.im_info["transparency"] = i16(s), i16(s, 2), i16(s, 4)
return s
- def chunk_gAMA(self, pos, length):
+ def chunk_gAMA(self, pos: int, length: int) -> bytes:
# gamma setting
s = ImageFile._safe_read(self.fp, length)
self.im_info["gamma"] = i32(s) / 100000.0
return s
- def chunk_cHRM(self, pos, length):
+ def chunk_cHRM(self, pos: int, length: int) -> bytes:
# chromaticity, 8 unsigned ints, actual value is scaled by 100,000
# WP x,y, Red x,y, Green x,y Blue x,y
@@ -488,7 +511,7 @@ def chunk_cHRM(self, pos, length):
self.im_info["chromaticity"] = tuple(elt / 100000.0 for elt in raw_vals)
return s
- def chunk_sRGB(self, pos, length):
+ def chunk_sRGB(self, pos: int, length: int) -> bytes:
# srgb rendering intent, 1 byte
# 0 perceptual
# 1 relative colorimetric
@@ -504,7 +527,7 @@ def chunk_sRGB(self, pos, length):
self.im_info["srgb"] = s[0]
return s
- def chunk_pHYs(self, pos, length):
+ def chunk_pHYs(self, pos: int, length: int) -> bytes:
# pixels per unit
s = ImageFile._safe_read(self.fp, length)
if length < 9:
@@ -521,7 +544,7 @@ def chunk_pHYs(self, pos, length):
self.im_info["aspect"] = px, py
return s
- def chunk_tEXt(self, pos, length):
+ def chunk_tEXt(self, pos: int, length: int) -> bytes:
# text
s = ImageFile._safe_read(self.fp, length)
try:
@@ -540,7 +563,7 @@ def chunk_tEXt(self, pos, length):
return s
- def chunk_zTXt(self, pos, length):
+ def chunk_zTXt(self, pos: int, length: int) -> bytes:
# compressed text
s = ImageFile._safe_read(self.fp, length)
try:
@@ -574,7 +597,7 @@ def chunk_zTXt(self, pos, length):
return s
- def chunk_iTXt(self, pos, length):
+ def chunk_iTXt(self, pos: int, length: int) -> bytes:
# international text
r = s = ImageFile._safe_read(self.fp, length)
try:
@@ -601,6 +624,8 @@ def chunk_iTXt(self, pos, length):
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")
@@ -614,13 +639,13 @@ def chunk_iTXt(self, pos, length):
return s
- def chunk_eXIf(self, pos, length):
+ def chunk_eXIf(self, pos: int, length: int) -> bytes:
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, length):
+ def chunk_acTL(self, pos: int, length: int) -> bytes:
s = ImageFile._safe_read(self.fp, length)
if length < 8:
if ImageFile.LOAD_TRUNCATED_IMAGES:
@@ -640,7 +665,7 @@ def chunk_acTL(self, pos, length):
self.im_custom_mimetype = "image/apng"
return s
- def chunk_fcTL(self, pos, length):
+ def chunk_fcTL(self, pos: int, length: int) -> bytes:
s = ImageFile._safe_read(self.fp, length)
if length < 26:
if ImageFile.LOAD_TRUNCATED_IMAGES:
@@ -669,7 +694,7 @@ def chunk_fcTL(self, pos, length):
self.im_info["blend"] = s[25]
return s
- def chunk_fdAT(self, pos, length):
+ def chunk_fdAT(self, pos: int, length: int) -> bytes:
if length < 4:
if ImageFile.LOAD_TRUNCATED_IMAGES:
s = ImageFile._safe_read(self.fp, length)
@@ -689,7 +714,7 @@ def chunk_fdAT(self, pos, length):
# PNG reader
-def _accept(prefix):
+def _accept(prefix: bytes) -> bool:
return prefix[:8] == _MAGIC
@@ -701,7 +726,7 @@ class PngImageFile(ImageFile.ImageFile):
format = "PNG"
format_description = "Portable network graphics"
- def _open(self):
+ def _open(self) -> None:
if not _accept(self.fp.read(8)):
msg = "not a PNG file"
raise SyntaxError(msg)
@@ -711,8 +736,8 @@ def _open(self):
#
# Parse headers up to the first IDAT or fDAT chunk
- self.private_chunks = []
- self.png = PngStream(self.fp)
+ self.private_chunks: list[tuple[bytes, bytes] | tuple[bytes, bytes, bool]] = []
+ self.png: PngStream | None = PngStream(self.fp)
while True:
#
@@ -783,7 +808,7 @@ def text(self):
self.seek(frame)
return self._text
- def verify(self):
+ def verify(self) -> None:
"""Verify PNG file"""
if self.fp is None:
@@ -793,6 +818,7 @@ def verify(self):
# back up to beginning of IDAT block
self.fp.seek(self.tile[0][2] - 8)
+ assert self.png is not None
self.png.verify()
self.png.close()
@@ -800,7 +826,7 @@ def verify(self):
self.fp.close()
self.fp = None
- def seek(self, frame):
+ def seek(self, frame: int) -> None:
if not self._seek_check(frame):
return
if frame < self.__frame:
@@ -815,7 +841,10 @@ def seek(self, frame):
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
if frame == 0:
if rewind:
self._fp.seek(self.__rewind)
@@ -900,19 +929,19 @@ def _seek(self, frame, rewind=False):
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):
+ def tell(self) -> int:
return self.__frame
- def load_prepare(self):
+ def load_prepare(self) -> None:
"""internal: prepare to read PNG file"""
if self.info.get("interlace"):
@@ -921,9 +950,10 @@ def load_prepare(self):
self.__idat = self.__prepare_idat # used by load_read()
ImageFile.ImageFile.load_prepare(self)
- def load_read(self, read_bytes):
+ def load_read(self, read_bytes: int) -> bytes:
"""internal: read more image data"""
+ assert self.png is not None
while self.__idat == 0:
# end of chunk, skip forward to next one
@@ -954,8 +984,9 @@ def load_read(self, read_bytes):
return self.fp.read(read_bytes)
- def load_end(self):
+ def load_end(self) -> None:
"""internal: finished reading image data"""
+ assert self.png is not None
if self.__idat != 0:
self.fp.read(self.__idat)
while True:
@@ -1011,53 +1042,40 @@ def load_end(self):
if self.pyaccess:
self.pyaccess = None
- def _getexif(self):
+ def _getexif(self) -> dict[str, 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):
- """
- 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
_OUTMODES = {
- # supported PIL modes, and corresponding rawmodes/bits/color combinations
- "1": ("1", b"\x01\x00"),
- "L;1": ("L;1", b"\x01\x00"),
- "L;2": ("L;2", b"\x02\x00"),
- "L;4": ("L;4", b"\x04\x00"),
- "L": ("L", b"\x08\x00"),
- "LA": ("LA", b"\x08\x04"),
- "I": ("I;16B", b"\x10\x00"),
- "I;16": ("I;16B", b"\x10\x00"),
- "I;16B": ("I;16B", b"\x10\x00"),
- "P;1": ("P;1", b"\x01\x03"),
- "P;2": ("P;2", b"\x02\x03"),
- "P;4": ("P;4", b"\x04\x03"),
- "P": ("P", b"\x08\x03"),
- "RGB": ("RGB", b"\x08\x02"),
- "RGBA": ("RGBA", b"\x08\x06"),
+ # supported PIL modes, and corresponding rawmode, bit depth and color type
+ "1": ("1", b"\x01", b"\x00"),
+ "L;1": ("L;1", b"\x01", b"\x00"),
+ "L;2": ("L;2", b"\x02", b"\x00"),
+ "L;4": ("L;4", b"\x04", b"\x00"),
+ "L": ("L", b"\x08", b"\x00"),
+ "LA": ("LA", b"\x08", b"\x04"),
+ "I": ("I;16B", b"\x10", b"\x00"),
+ "I;16": ("I;16B", b"\x10", b"\x00"),
+ "I;16B": ("I;16B", b"\x10", b"\x00"),
+ "P;1": ("P;1", b"\x01", b"\x03"),
+ "P;2": ("P;2", b"\x02", b"\x03"),
+ "P;4": ("P;4", b"\x04", b"\x03"),
+ "P": ("P", b"\x08", b"\x03"),
+ "RGB": ("RGB", b"\x08", b"\x02"),
+ "RGBA": ("RGBA", b"\x08", b"\x06"),
}
@@ -1079,7 +1097,7 @@ def __init__(self, fp, chunk):
self.fp = fp
self.chunk = chunk
- def write(self, data):
+ def write(self, data: bytes) -> None:
self.chunk(self.fp, b"IDAT", data)
@@ -1091,13 +1109,13 @@ def __init__(self, fp, chunk, seq_num):
self.chunk = chunk
self.seq_num = seq_num
- def write(self, data):
+ def write(self, data: bytes) -> None:
self.chunk(self.fp, b"fdAT", o32(self.seq_num), data)
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))
+def _write_multiple_frames(im, fp, chunk, mode, rawmode, default_image, append_images):
+ 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))
@@ -1111,13 +1129,15 @@ def _write_multiple_frames(im, fp, chunk, rawmode, default_image, append_images)
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)):
@@ -1152,15 +1172,12 @@ 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})
if len(im_frames) == 1 and not default_image:
@@ -1176,8 +1193,8 @@ 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)
+ if im.mode != mode:
+ im = im.convert(mode)
ImageFile._save(im, _idat(fp, chunk), [("zip", (0, 0) + im.size, 0, rawmode)])
seq_num = 0
@@ -1190,7 +1207,7 @@ def _write_multiple_frames(im, fp, chunk, rawmode, default_image, append_images)
im_frame = im_frame.crop(bbox)
size = im_frame.size
encoderinfo = frame_data["encoderinfo"]
- frame_duration = int(round(encoderinfo["duration"]))
+ frame_duration = int(round(encoderinfo.get("duration", 0)))
frame_disposal = encoderinfo.get("disposal", disposal)
frame_blend = encoderinfo.get("blend", blend)
# frame control
@@ -1226,7 +1243,7 @@ def _write_multiple_frames(im, fp, chunk, rawmode, default_image, append_images)
seq_num = fdat_chunks.seq_num
-def _save_all(im, fp, filename):
+def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
_save(im, fp, filename, save_all=True)
@@ -1254,6 +1271,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
@@ -1274,7 +1292,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 = (
@@ -1286,7 +1304,7 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False):
# get the corresponding PNG mode
try:
- rawmode, mode = _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
@@ -1301,7 +1319,8 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False):
b"IHDR",
o32(size[0]), # 0: size
o32(size[1]),
- mode, # 8: depth/type
+ bit_depth,
+ color_type,
b"\0", # 10: compression
b"\0", # 11: filter category
b"\0", # 12: interlace flag
@@ -1337,7 +1356,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)
@@ -1406,7 +1425,7 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False):
if save_all:
im = _write_multiple_frames(
- im, fp, chunk, rawmode, default_image, append_images
+ im, fp, chunk, mode, rawmode, default_image, append_images
)
if im:
ImageFile._save(im, _idat(fp, chunk), [("zip", (0, 0) + im.size, 0, rawmode)])
@@ -1416,7 +1435,7 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False):
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)
@@ -1436,10 +1455,10 @@ def getchunks(im, **params):
class collector:
data = []
- def write(self, data):
+ def write(self, data: bytes) -> None:
pass
- def append(self, chunk):
+ def append(self, chunk: bytes) -> None:
self.data.append(chunk)
def append(fp, cid, *data):
diff --git a/src/PIL/PpmImagePlugin.py b/src/PIL/PpmImagePlugin.py
index 94bf430b825..16c9ccbba72 100644
--- a/src/PIL/PpmImagePlugin.py
+++ b/src/PIL/PpmImagePlugin.py
@@ -328,7 +328,7 @@ def decode(self, buffer: bytes) -> tuple[int, int]:
# --------------------------------------------------------------------
-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":
diff --git a/src/PIL/PsdImagePlugin.py b/src/PIL/PsdImagePlugin.py
index d29bcf9970c..edf698bf02d 100644
--- a/src/PIL/PsdImagePlugin.py
+++ b/src/PIL/PsdImagePlugin.py
@@ -18,6 +18,7 @@
from __future__ import annotations
import io
+from functools import cached_property
from . import Image, ImageFile, ImagePalette
from ._binary import i8
@@ -44,7 +45,7 @@
# read PSD images
-def _accept(prefix):
+def _accept(prefix: bytes) -> bool:
return prefix[:4] == b"8BPS"
@@ -57,7 +58,7 @@ class PsdImageFile(ImageFile.ImageFile):
format_description = "Adobe Photoshop"
_close_exclusive_fp_after_loading = False
- def _open(self):
+ def _open(self) -> None:
read = self.fp.read
#
@@ -118,18 +119,17 @@ def _open(self):
#
# 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,23 +141,42 @@ def _open(self):
self.frame = 1
self._min_frame = 1
- def seek(self, layer):
+ @cached_property
+ def layers(self):
+ 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
# seek to given layer (1..max)
try:
- name, mode, bbox, tile = self.layers[layer - 1]
+ _, mode, _, tile = self.layers[layer - 1]
self._mode = mode
self.tile = tile
self.frame = layer
self.fp = self._fp
- return name, bbox
except IndexError as e:
msg = "no such layer"
raise EOFError(msg) from e
- def tell(self):
+ def tell(self) -> int:
# return layer number (0=image, 1..max=layers)
return self.frame
diff --git a/src/PIL/PyAccess.py b/src/PIL/PyAccess.py
index 2c831913d69..3be1ccace0c 100644
--- a/src/PIL/PyAccess.py
+++ b/src/PIL/PyAccess.py
@@ -22,6 +22,7 @@
import logging
import sys
+from typing import TYPE_CHECKING
from ._deprecate import deprecate
@@ -48,9 +49,12 @@
logger = logging.getLogger(__name__)
+if TYPE_CHECKING:
+ from . import Image
+
class PyAccess:
- def __init__(self, img, readonly=False):
+ def __init__(self, img: Image.Image, readonly: bool = False) -> None:
deprecate("PyAccess", 11)
vals = dict(img.im.unsafe_ptrs)
self.readonly = readonly
@@ -70,14 +74,19 @@ def __init__(self, img, readonly=False):
# logger.debug("%s", vals)
self._post_init()
- def _post_init(self):
+ def _post_init(self) -> None:
pass
- def __setitem__(self, xy, color):
+ def __setitem__(
+ self,
+ xy: tuple[int, int] | list[int],
+ color: float | tuple[int, ...] | list[int],
+ ) -> None:
"""
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
+ 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`.
@@ -102,13 +111,12 @@ def __setitem__(self, xy, color):
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)
+ palette_index = self._palette.getcolor(color, self._img)
+ color = (palette_index, alpha) if self._im.mode == "PA" else palette_index
return self.set_pixel(x, y, color)
- def __getitem__(self, xy):
+ def __getitem__(self, xy: tuple[int, int] | list[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
@@ -130,13 +138,21 @@ def __getitem__(self, xy):
putpixel = __setitem__
getpixel = __getitem__
- def check_xy(self, xy):
+ 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, ...] | list[int]
+ ) -> None:
+ raise NotImplementedError()
+
class _PyAccess32_2(PyAccess):
"""PA, LA, stored in first and last bytes of a 32 bit word"""
@@ -144,7 +160,7 @@ class _PyAccess32_2(PyAccess):
def _post_init(self, *args, **kwargs):
self.pixels = ffi.cast("struct Pixel_RGBA **", self.image32)
- def get_pixel(self, x, y):
+ def get_pixel(self, x: int, y: int) -> tuple[int, int]:
pixel = self.pixels[y][x]
return pixel.r, pixel.a
@@ -161,7 +177,7 @@ class _PyAccess32_3(PyAccess):
def _post_init(self, *args, **kwargs):
self.pixels = ffi.cast("struct Pixel_RGBA **", self.image32)
- def get_pixel(self, x, y):
+ def get_pixel(self, x: int, y: int) -> tuple[int, int, int]:
pixel = self.pixels[y][x]
return pixel.r, pixel.g, pixel.b
@@ -180,7 +196,7 @@ class _PyAccess32_4(PyAccess):
def _post_init(self, *args, **kwargs):
self.pixels = ffi.cast("struct Pixel_RGBA **", self.image32)
- def get_pixel(self, x, y):
+ 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
@@ -199,7 +215,7 @@ class _PyAccess8(PyAccess):
def _post_init(self, *args, **kwargs):
self.pixels = self.image8
- def get_pixel(self, x, y):
+ def get_pixel(self, x: int, y: int) -> int:
return self.pixels[y][x]
def set_pixel(self, x, y, color):
@@ -217,7 +233,7 @@ class _PyAccessI16_N(PyAccess):
def _post_init(self, *args, **kwargs):
self.pixels = ffi.cast("unsigned short **", self.image)
- def get_pixel(self, x, y):
+ def get_pixel(self, x: int, y: int) -> int:
return self.pixels[y][x]
def set_pixel(self, x, y, color):
@@ -235,7 +251,7 @@ class _PyAccessI16_L(PyAccess):
def _post_init(self, *args, **kwargs):
self.pixels = ffi.cast("struct Pixel_I16 **", self.image)
- def get_pixel(self, x, y):
+ def get_pixel(self, x: int, y: int) -> int:
pixel = self.pixels[y][x]
return pixel.l + pixel.r * 256
@@ -256,7 +272,7 @@ class _PyAccessI16_B(PyAccess):
def _post_init(self, *args, **kwargs):
self.pixels = ffi.cast("struct Pixel_I16 **", self.image)
- def get_pixel(self, x, y):
+ def get_pixel(self, x: int, y: int) -> int:
pixel = self.pixels[y][x]
return pixel.l * 256 + pixel.r
@@ -277,7 +293,7 @@ class _PyAccessI32_N(PyAccess):
def _post_init(self, *args, **kwargs):
self.pixels = self.image32
- def get_pixel(self, x, y):
+ def get_pixel(self, x: int, y: int) -> int:
return self.pixels[y][x]
def set_pixel(self, x, y, color):
@@ -296,7 +312,7 @@ def reverse(self, i):
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, y):
+ def get_pixel(self, x: int, y: int) -> int:
return self.reverse(self.pixels[y][x])
def set_pixel(self, x, y, color):
@@ -309,7 +325,7 @@ class _PyAccessF(PyAccess):
def _post_init(self, *args, **kwargs):
self.pixels = ffi.cast("float **", self.image32)
- def get_pixel(self, x, y):
+ def get_pixel(self, x: int, y: int) -> float:
return self.pixels[y][x]
def set_pixel(self, x, y, color):
@@ -357,7 +373,7 @@ def set_pixel(self, x, y, color):
mode_map["I;32B"] = _PyAccessI32_N
-def new(img, readonly=False):
+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)
diff --git a/src/PIL/QoiImagePlugin.py b/src/PIL/QoiImagePlugin.py
index f8aa720c134..202ef52d09b 100644
--- a/src/PIL/QoiImagePlugin.py
+++ b/src/PIL/QoiImagePlugin.py
@@ -13,7 +13,7 @@
from ._binary import i32be as i32
-def _accept(prefix):
+def _accept(prefix: bytes) -> bool:
return prefix[:4] == b"qoif"
@@ -21,7 +21,7 @@ class QoiImageFile(ImageFile.ImageFile):
format = "QOI"
format_description = "Quite OK Image"
- def _open(self):
+ def _open(self) -> None:
if not _accept(self.fp.read(4)):
msg = "not a QOI file"
raise SyntaxError(msg)
@@ -37,17 +37,20 @@ def _open(self):
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):
+ def _add_to_previous_pixels(self, value: bytes | bytearray) -> None:
self._previous_pixel = value
r, g, b, a = value
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) -> 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 @@ def decode(self, buffer):
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 @@ def decode(self, buffer):
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 @@ def decode(self, buffer):
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 @@ def decode(self, buffer):
)
)
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 7bd84ebd491..50d97910932 100644
--- a/src/PIL/SgiImagePlugin.py
+++ b/src/PIL/SgiImagePlugin.py
@@ -125,7 +125,7 @@ def _open(self) -> None:
]
-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 +171,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))
diff --git a/src/PIL/SpiderImagePlugin.py b/src/PIL/SpiderImagePlugin.py
index 86582fb128c..f5a09c3ef61 100644
--- a/src/PIL/SpiderImagePlugin.py
+++ b/src/PIL/SpiderImagePlugin.py
@@ -37,11 +37,12 @@
import os
import struct
import sys
+from typing import IO, TYPE_CHECKING, Any, Tuple, cast
from . import Image, ImageFile
-def isInt(f):
+def isInt(f: Any) -> int:
try:
i = int(f)
if f - i == 0:
@@ -61,7 +62,7 @@ def isInt(f):
# 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]:
@@ -81,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
@@ -97,7 +98,7 @@ class SpiderImageFile(ImageFile.ImageFile):
format_description = "Spider 2D image"
_close_exclusive_fp_after_loading = False
- def _open(self):
+ def _open(self) -> None:
# check header
n = 27 * 4 # read 27 float values
f = self.fp.read(n)
@@ -157,21 +158,21 @@ def _open(self):
self._fp = self.fp # FIXME: hack
@property
- def n_frames(self):
+ def n_frames(self) -> int:
return self._nimages
@property
- def is_animated(self):
+ def is_animated(self) -> bool:
return self._nimages > 1
# 1st image index is zero (although SPIDER imgnumber starts at 1)
- def tell(self):
+ def tell(self) -> int:
if self.imgnumber < 1:
return 0
else:
return self.imgnumber - 1
- def seek(self, frame):
+ def seek(self, frame: int) -> None:
if self.istack == 0:
msg = "attempt to seek in a non-stack file"
raise EOFError(msg)
@@ -183,16 +184,21 @@ def seek(self, frame):
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
# returns a ImageTk.PhotoImage object, after rescaling to 0..255
- def tkPhotoImage(self):
+ def tkPhotoImage(self) -> ImageTk.PhotoImage:
from . import ImageTk
return ImageTk.PhotoImage(self.convert2byte(), palette=256)
@@ -203,10 +209,10 @@ def tkPhotoImage(self):
# given a list of filenames, return a list of images
-def loadImageSeries(filelist=None):
+def loadImageSeries(filelist: list[str] | None = None) -> list[SpiderImageFile] | 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 = []
for img in filelist:
@@ -218,7 +224,7 @@ def loadImageSeries(filelist=None):
im = im.convert2byte()
except Exception:
if not isSpiderImage(img):
- print(img + " is not a Spider image file")
+ print(f"{img} is not a Spider image file")
continue
im.info["filename"] = img
imglist.append(im)
@@ -229,7 +235,7 @@ def loadImageSeries(filelist=None):
# For saving images in Spider format
-def makeSpiderHeader(im):
+def makeSpiderHeader(im: Image.Image) -> list[bytes]:
nsam, nrow = im.size
lenbyt = nsam * 4 # There are labrec records in the header
labrec = int(1024 / lenbyt)
@@ -259,7 +265,7 @@ def makeSpiderHeader(im):
return [struct.pack("f", v) for v in hdr]
-def _save(im, fp, filename):
+def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
if im.mode[0] != "F":
im = im.convert("F")
@@ -275,9 +281,10 @@ def _save(im, fp, filename):
ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, 1))])
-def _save_spider(im, fp, filename):
+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)
@@ -299,10 +306,10 @@ def _save_spider(im, fp, filename):
sys.exit()
with Image.open(filename) as im:
- print("image: " + str(im))
- print("format: " + str(im.format))
- print("size: " + str(im.size))
- print("mode: " + str(im.mode))
+ print(f"image: {im}")
+ print(f"format: {im.format}")
+ print(f"size: {im.size}")
+ print(f"mode: {im.mode}")
print("max, min: ", end=" ")
print(im.getextrema())
diff --git a/src/PIL/TarIO.py b/src/PIL/TarIO.py
index 7470663b4a1..cba26d4b059 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
@@ -61,12 +60,7 @@ def __init__(self, tarfile: str, file: str) -> None:
def __enter__(self) -> TarIO:
return self
- def __exit__(
- self,
- exc_type: type[BaseException] | None,
- exc_val: BaseException | None,
- exc_tb: TracebackType | None,
- ) -> None:
+ def __exit__(self, *args: object) -> None:
self.close()
def close(self) -> None:
diff --git a/src/PIL/TgaImagePlugin.py b/src/PIL/TgaImagePlugin.py
index 401a83f9fba..39104aeced9 100644
--- a/src/PIL/TgaImagePlugin.py
+++ b/src/PIL/TgaImagePlugin.py
@@ -36,7 +36,7 @@
(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 @@ def _open(self) -> None:
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 @@ def _open(self) -> None:
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"
@@ -178,7 +177,7 @@ def load_end(self) -> None:
}
-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:
diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py
index 8bfcd29075e..ac5b63c1bc4 100644
--- a/src/PIL/TiffImagePlugin.py
+++ b/src/PIL/TiffImagePlugin.py
@@ -50,12 +50,13 @@
from collections.abc import MutableMapping
from fractions import Fraction
from numbers import Number, Rational
-from typing import TYPE_CHECKING, Any, Callable
+from typing import IO, TYPE_CHECKING, Any, Callable, NoReturn
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 .TiffTags import TYPES
logger = logging.getLogger(__name__)
@@ -200,12 +201,12 @@
(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"),
@@ -224,8 +225,8 @@
(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"),
@@ -244,6 +245,7 @@
(MM, 3, (1,), 2, (4,), ()): ("P", "P;4R"),
(II, 3, (1,), 1, (8,), ()): ("P", "P"),
(MM, 3, (1,), 1, (8,), ()): ("P", "P"),
+ (II, 3, (1,), 1, (8, 8), (0,)): ("P", "PX"),
(II, 3, (1,), 1, (8, 8), (2,)): ("PA", "PA"),
(MM, 3, (1,), 1, (8, 8), (2,)): ("PA", "PA"),
(II, 3, (1,), 2, (8,), ()): ("P", "P;R"),
@@ -276,8 +278,11 @@
b"II\x2B\x00", # BigTIFF with little-endian byte order
]
+if not getattr(Image.core, "libtiff_support_custom_tags", True):
+ deprecate("Support for LibTIFF earlier than version 4", 12)
-def _accept(prefix):
+
+def _accept(prefix: bytes) -> bool:
return prefix[:4] in PREFIXES
@@ -376,13 +381,13 @@ def limit_rational(self, max_denominator):
f = self._val.limit_denominator(max_denominator)
return f.numerator, f.denominator
- def __repr__(self):
+ 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
@@ -464,7 +469,7 @@ def _register_basic(idx_fmt_name):
idx, fmt, name = idx_fmt_name
TYPES[idx] = name
- size = struct.calcsize("=" + fmt)
+ size = struct.calcsize(f"={fmt}")
_load_dispatch[idx] = ( # noqa: F821
size,
lambda self, data, legacy_api=True: (
@@ -546,7 +551,12 @@ class ImageFileDirectory_v2(_IFDv2Base):
_load_dispatch: dict[int, Callable[[ImageFileDirectory_v2, bytes, bool], Any]] = {}
_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
@@ -570,7 +580,7 @@ def __init__(self, ifh=b"II\052\0\0\0\0\0", prefix=None, group=None):
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,) = (
@@ -582,23 +592,23 @@ def __init__(self, ifh=b"II\052\0\0\0\0\0", prefix=None, group=None):
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
- def __str__(self):
+ def __str__(self) -> str:
return str(dict(self))
def named(self):
@@ -612,7 +622,7 @@ def named(self):
for code, value in self.items()
}
- def __len__(self):
+ def __len__(self) -> int:
return len(set(self._tagdata) | set(self._tags_v2))
def __getitem__(self, tag):
@@ -712,7 +722,7 @@ def _setitem(self, tag, value, legacy_api):
# 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)
@@ -982,8 +992,8 @@ def save(self, fp):
ImageFileDirectory_v2._write_dispatch = _write_dispatch
for idx, name in TYPES.items():
name = name.replace(" ", "_")
- setattr(ImageFileDirectory_v2, "load_" + name, _load_dispatch[idx][1])
- setattr(ImageFileDirectory_v2, "write_" + name, _write_dispatch[idx])
+ setattr(ImageFileDirectory_v2, f"load_{name}", _load_dispatch[idx][1])
+ setattr(ImageFileDirectory_v2, f"write_{name}", _write_dispatch[idx])
del _load_dispatch, _write_dispatch, idx, name
@@ -1036,7 +1046,7 @@ def from_v2(cls, original):
ifd.next = original.next # an indicator for multipage tiffs
return ifd
- def to_v2(self):
+ def to_v2(self) -> ImageFileDirectory_v2:
"""Returns an
:py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v2`
instance with the same data as is contained in the original
@@ -1056,7 +1066,7 @@ def to_v2(self):
def __contains__(self, tag):
return tag in self._tags_v1 or tag in self._tagdata
- def __len__(self):
+ def __len__(self) -> int:
return len(set(self._tagdata) | set(self._tags_v1))
def __iter__(self):
@@ -1101,7 +1111,7 @@ def __init__(self, fp=None, filename=None):
super().__init__(fp, filename)
- def _open(self):
+ def _open(self) -> None:
"""Open the first image in a TIFF file"""
# Header
@@ -1118,8 +1128,8 @@ def _open(self):
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)
@@ -1138,7 +1148,7 @@ def n_frames(self):
self.seek(current)
return self._n_frames
- def seek(self, frame):
+ def seek(self, frame: int) -> None:
"""Select a given frame as current image"""
if not self._seek_check(frame):
return
@@ -1149,7 +1159,7 @@ def seek(self, frame):
Image._decompression_bomb_check(self.size)
self.im = Image.core.new(self.mode, self.size)
- def _seek(self, frame):
+ def _seek(self, frame: int) -> None:
self.fp = self._fp
# reset buffered io handle in case fp
@@ -1187,25 +1197,20 @@ def _seek(self, frame):
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)
self.__frame = frame
self._setup()
- def tell(self):
+ def tell(self) -> int:
"""Return the current frame number"""
return self.__frame
- def getxmp(self):
- """
- 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):
"""
Returns a dictionary of Photoshop "Image Resource Blocks".
@@ -1232,7 +1237,7 @@ def load(self):
return self._load_libtiff()
return super().load()
- def load_end(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:
@@ -1653,6 +1658,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"):
@@ -1666,8 +1685,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
@@ -1807,16 +1832,6 @@ def _save(im, fp, filename):
# 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.
@@ -1937,7 +1952,7 @@ def __init__(self, fn, new=False):
self.beginning = self.f.tell()
self.setup()
- def setup(self):
+ def setup(self) -> None:
# Reset everything.
self.f.seek(self.beginning, os.SEEK_SET)
@@ -1962,7 +1977,7 @@ def setup(self):
self.skipIFDs()
self.goToEnd()
- def finalize(self):
+ def finalize(self) -> None:
if self.isFirst:
return
@@ -1985,20 +2000,19 @@ def finalize(self):
self.f.seek(ifd_offset)
self.fixIFD()
- def newFrame(self):
+ def newFrame(self) -> None:
# Call this to finish a frame.
self.finalize()
self.setup()
- def __enter__(self):
+ 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):
+ def tell(self) -> int:
return self.f.tell() - self.offsetOfNewPage
def seek(self, offset, whence=io.SEEK_SET):
@@ -2008,7 +2022,7 @@ def seek(self, offset, whence=io.SEEK_SET):
self.f.seek(offset, whence)
return self.tell()
- def goToEnd(self):
+ def goToEnd(self) -> None:
self.f.seek(0, os.SEEK_END)
pos = self.f.tell()
@@ -2018,13 +2032,13 @@ def goToEnd(self):
self.f.write(bytes(pad_bytes))
self.offsetOfNewPage = self.f.tell()
- def setEndian(self, endian):
+ def setEndian(self, endian: str) -> None:
self.endian = endian
- self.longFmt = self.endian + "L"
- self.shortFmt = self.endian + "H"
- self.tagFormat = self.endian + "HHL"
+ self.longFmt = f"{self.endian}L"
+ self.shortFmt = f"{self.endian}H"
+ self.tagFormat = f"{self.endian}HHL"
- def skipIFDs(self):
+ def skipIFDs(self) -> None:
while True:
ifd_offset = self.readLong()
if ifd_offset == 0:
@@ -2035,55 +2049,55 @@ def skipIFDs(self):
num_tags = self.readShort()
self.f.seek(num_tags * 12, os.SEEK_CUR)
- def write(self, data):
+ def write(self, data: bytes) -> int | None:
return self.f.write(data)
- def readShort(self):
+ def readShort(self) -> int:
(value,) = struct.unpack(self.shortFmt, self.f.read(2))
return value
- def readLong(self):
+ def readLong(self) -> int:
(value,) = struct.unpack(self.longFmt, self.f.read(4))
return value
- def rewriteLastShortToLong(self, value):
+ 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)
- def rewriteLastShort(self, value):
+ def rewriteLastShort(self, value: int) -> None:
self.f.seek(-2, os.SEEK_CUR)
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 rewriteLastLong(self, value):
+ def rewriteLastLong(self, value: int) -> None:
self.f.seek(-4, 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)
- def writeShort(self, value):
+ 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)
- def writeLong(self, value):
+ 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 close(self):
+ def close(self) -> None:
self.finalize()
self.f.close()
- def fixIFD(self):
+ def fixIFD(self) -> None:
num_tags = self.readShort()
for i in range(num_tags):
@@ -2092,9 +2106,9 @@ def fixIFD(self):
field_size = self.fieldSizes[field_type]
total_size = field_size * count
is_local = total_size <= 4
+ offset: int | None
if not is_local:
- offset = self.readLong()
- offset += self.offsetOfNewPage
+ offset = self.readLong() + self.offsetOfNewPage
self.rewriteLastLong(offset)
if tag in self.Tags:
@@ -2118,7 +2132,9 @@ def fixIFD(self):
# 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):
+ def fixOffsets(
+ self, count: int, isShort: bool = False, isLong: bool = False
+ ) -> None:
if not isShort and not isLong:
msg = "offset is neither short nor long"
raise RuntimeError(msg)
@@ -2144,7 +2160,7 @@ def fixOffsets(self, count, isShort=False, isLong=False):
self.rewriteLastLong(offset)
-def _save_all(im, fp, filename):
+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 89fad703343..e318c87398c 100644
--- a/src/PIL/TiffTags.py
+++ b/src/PIL/TiffTags.py
@@ -89,7 +89,7 @@ def lookup(tag, group=None):
IFD = 13
LONG8 = 16
-TAGS_V2 = {
+_tags_v2 = {
254: ("NewSubfileType", LONG, 1),
255: ("SubfileType", SHORT, 1),
256: ("ImageWidth", LONG, 1),
@@ -425,9 +425,11 @@ def lookup(tag, group=None):
50784: "Alias Layer Metadata",
}
+TAGS_V2: dict[int, TagInfo] = {}
+
def _populate():
- for k, v in TAGS_V2.items():
+ for k, v in _tags_v2.items():
# Populate legacy structure.
TAGS[k] = v[0]
if len(v) == 4:
diff --git a/src/PIL/WalImageFile.py b/src/PIL/WalImageFile.py
index c5bf3e04cf7..fbd7be6ed5c 100644
--- a/src/PIL/WalImageFile.py
+++ b/src/PIL/WalImageFile.py
@@ -32,7 +32,7 @@ class WalImageFile(ImageFile.ImageFile):
format = "WAL"
format_description = "Quake2 Texture"
- def _open(self):
+ def _open(self) -> None:
self._mode = "P"
# read header fields
diff --git a/src/PIL/WebPImagePlugin.py b/src/PIL/WebPImagePlugin.py
index c07abcaf928..59be5bf9d31 100644
--- a/src/PIL/WebPImagePlugin.py
+++ b/src/PIL/WebPImagePlugin.py
@@ -1,6 +1,7 @@
from __future__ import annotations
from io import BytesIO
+from typing import IO, Any
from . import Image, ImageFile
@@ -23,7 +24,7 @@
}
-def _accept(prefix):
+def _accept(prefix: bytes) -> bool | str:
is_riff_file_format = prefix[:4] == b"RIFF"
is_webp_file = prefix[8:12] == b"WEBP"
is_valid_vp8_mode = prefix[12:16] in _VP8_MODES_BY_IDENTIFIER
@@ -34,6 +35,7 @@ def _accept(prefix):
"image file could not be identified because WEBP support not installed"
)
return True
+ return False
class WebPImageFile(ImageFile.ImageFile):
@@ -42,7 +44,7 @@ class WebPImageFile(ImageFile.ImageFile):
__loaded = 0
__logical_frame = 0
- def _open(self):
+ def _open(self) -> None:
if not _webp.HAVE_WEBPANIM:
# Legacy mode
data, width, height, self._mode, icc_profile, exif = _webp.WebPDecode(
@@ -94,28 +96,19 @@ def _open(self):
# Initialize seek state
self._reset(reset=False)
- def _getexif(self):
+ def _getexif(self) -> dict[str, Any] | None:
if "exif" not in self.info:
return None
return self.getexif()._get_merged_dict()
- def getxmp(self):
- """
- 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):
+ def seek(self, frame: int) -> None:
if not self._seek_check(frame):
return
# Set logical frame to requested position
self.__logical_frame = frame
- def _reset(self, reset=True):
+ def _reset(self, reset: bool = True) -> None:
if reset:
self._decoder.reset()
self.__physical_frame = 0
@@ -143,7 +136,7 @@ def _get_next(self):
timestamp -= duration
return data, timestamp, duration
- def _seek(self, frame):
+ def _seek(self, frame: int) -> None:
if self.__physical_frame == frame:
return # Nothing to do
if frame < self.__physical_frame:
@@ -170,17 +163,17 @@ def load(self):
return super().load()
- def load_seek(self, pos):
+ def load_seek(self, pos: int) -> None:
pass
- def tell(self):
+ def tell(self) -> int:
if not _webp.HAVE_WEBPANIM:
return super().tell()
return self.__logical_frame
-def _save_all(im, fp, filename):
+def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
encoderinfo = im.encoderinfo.copy()
append_images = list(encoderinfo.get("append_images", []))
@@ -193,7 +186,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:
@@ -323,7 +316,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)
diff --git a/src/PIL/WmfImagePlugin.py b/src/PIL/WmfImagePlugin.py
index b5b8c69b171..3d5cddcc8f5 100644
--- a/src/PIL/WmfImagePlugin.py
+++ b/src/PIL/WmfImagePlugin.py
@@ -20,6 +20,8 @@
# http://wvware.sourceforge.net/caolan/ora-wmf.html
from __future__ import annotations
+from typing import IO
+
from . import Image, ImageFile
from ._binary import i16le as word
from ._binary import si16le as short
@@ -28,7 +30,7 @@
_handler = None
-def register_handler(handler):
+def register_handler(handler: ImageFile.StubHandler | None) -> None:
"""
Install application-specific WMF image handler.
@@ -41,12 +43,12 @@ def register_handler(handler):
if hasattr(Image.core, "drawwmf"):
# install default handler (windows only)
- class WmfHandler:
- def open(self, im):
+ class WmfHandler(ImageFile.StubHandler):
+ def open(self, im: ImageFile.StubImageFile) -> None:
im._mode = "RGB"
self.bbox = im.info["wmf_bbox"]
- def load(self, im):
+ def load(self, im: ImageFile.StubImageFile) -> Image.Image:
im.fp.seek(0) # rewind
return Image.frombytes(
"RGB",
@@ -65,7 +67,7 @@ def load(self, im):
# Read WMF file
-def _accept(prefix):
+def _accept(prefix: bytes) -> bool:
return (
prefix[:6] == b"\xd7\xcd\xc6\x9a\x00\x00" or prefix[:4] == b"\x01\x00\x00\x00"
)
@@ -79,7 +81,7 @@ class WmfStubImageFile(ImageFile.StubImageFile):
format = "WMF"
format_description = "Windows Metafile"
- def _open(self):
+ def _open(self) -> None:
self._inch = None
# check placable header
@@ -147,7 +149,7 @@ def _open(self):
if loader:
loader.open(self)
- def _load(self):
+ def _load(self) -> ImageFile.StubHandler | None:
return _handler
def load(self, dpi=None):
@@ -161,7 +163,7 @@ def load(self, dpi=None):
return super().load()
-def _save(im, fp, filename):
+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/XbmImagePlugin.py b/src/PIL/XbmImagePlugin.py
index eee7274361c..6d11bbfcf6b 100644
--- a/src/PIL/XbmImagePlugin.py
+++ b/src/PIL/XbmImagePlugin.py
@@ -70,7 +70,7 @@ def _open(self) -> None:
self.tile = [("xbm", (0, 0) + self.size, m.end(), None)]
-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)
diff --git a/src/PIL/XpmImagePlugin.py b/src/PIL/XpmImagePlugin.py
index 3125f8d52c6..8d56331e6aa 100644
--- a/src/PIL/XpmImagePlugin.py
+++ b/src/PIL/XpmImagePlugin.py
@@ -24,7 +24,7 @@
xpm_head = re.compile(b'"([0-9]*) ([0-9]*) ([0-9]*) ([0-9]*)')
-def _accept(prefix):
+def _accept(prefix: bytes) -> bool:
return prefix[:9] == b"/* XPM */"
@@ -36,7 +36,7 @@ class XpmImageFile(ImageFile.ImageFile):
format = "XPM"
format_description = "X11 Pixel Map"
- def _open(self):
+ def _open(self) -> None:
if not _accept(self.fp.read(9)):
msg = "not an XPM file"
raise SyntaxError(msg)
@@ -103,16 +103,13 @@ def _open(self):
self.tile = [("raw", (0, 0) + self.size, self.fp.tell(), ("P", 0, 1))]
- def load_read(self, read_bytes):
+ def load_read(self, read_bytes: int) -> bytes:
#
# load all image data in one chunk
xsize, ysize = self.size
- s = [None] * ysize
-
- for i in range(ysize):
- s[i] = self.fp.readline()[1 : xsize + 1].ljust(xsize)
+ s = [self.fp.readline()[1 : xsize + 1].ljust(xsize) for i in range(ysize)]
return b"".join(s)
diff --git a/src/PIL/_imaging.pyi b/src/PIL/_imaging.pyi
index e27843e5338..8cccd3ac73e 100644
--- a/src/PIL/_imaging.pyi
+++ b/src/PIL/_imaging.pyi
@@ -1,3 +1,30 @@
from typing import Any
+class ImagingCore:
+ def __getattr__(self, name: str) -> Any: ...
+
+class ImagingFont:
+ def __getattr__(self, name: str) -> Any: ...
+
+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: ...
+
+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 036521b0e5e..2abd6d0f7a9 100644
--- a/src/PIL/_imagingcms.pyi
+++ b/src/PIL/_imagingcms.pyi
@@ -2,7 +2,7 @@ import datetime
import sys
from typing import Literal, SupportsFloat, TypedDict
-littlecms_version: str
+littlecms_version: str | None
_Tuple3f = tuple[float, float, float]
_Tuple2x3f = tuple[_Tuple3f, _Tuple3f]
@@ -108,10 +108,6 @@ class CmsProfile:
def is_intent_supported(self, intent: int, direction: int, /) -> int: ...
class CmsTransform:
- @property
- def inputMode(self) -> str: ...
- @property
- def outputMode(self) -> str: ...
def apply(self, id_in: int, id_out: int) -> int: ...
def profile_open(profile: str, /) -> CmsProfile: ...
diff --git a/src/PIL/_imagingft.pyi b/src/PIL/_imagingft.pyi
index e27843e5338..5e97b40b2e0 100644
--- a/src/PIL/_imagingft.pyi
+++ b/src/PIL/_imagingft.pyi
@@ -1,3 +1,69 @@
-from typing import Any
+from typing import Any, TypedDict
+from . import _imaging
+
+class _Axis(TypedDict):
+ minimum: int | None
+ default: int | None
+ maximum: int | None
+ name: bytes | None
+
+class Font:
+ @property
+ def family(self) -> str | None: ...
+ @property
+ def style(self) -> str | None: ...
+ @property
+ def ascent(self) -> int: ...
+ @property
+ def descent(self) -> int: ...
+ @property
+ def height(self) -> int: ...
+ @property
+ def x_ppem(self) -> int: ...
+ @property
+ def y_ppem(self) -> int: ...
+ @property
+ def glyphs(self) -> int: ...
+ def render(
+ self,
+ string: str | bytes,
+ fill,
+ mode=...,
+ dir=...,
+ features=...,
+ lang=...,
+ stroke_width=...,
+ anchor=...,
+ foreground_ink_long=...,
+ x_start=...,
+ y_start=...,
+ /,
+ ) -> tuple[_imaging.ImagingCore, tuple[int, int]]: ...
+ def getsize(
+ self,
+ string: str | bytes | bytearray,
+ mode=...,
+ dir=...,
+ features=...,
+ lang=...,
+ anchor=...,
+ /,
+ ) -> tuple[tuple[int, int], tuple[int, int]]: ...
+ def getlength(
+ self, string: str | bytes, mode=..., dir=..., features=..., lang=..., /
+ ) -> float: ...
+ def getvarnames(self) -> list[bytes]: ...
+ def getvaraxes(self) -> list[_Axis] | None: ...
+ 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=...,
+) -> Font: ...
def __getattr__(name: str) -> Any: ...
diff --git a/src/PIL/_typing.py b/src/PIL/_typing.py
index 7075e86726a..09ece18fa60 100644
--- a/src/PIL/_typing.py
+++ b/src/PIL/_typing.py
@@ -2,7 +2,14 @@
import os
import sys
-from typing import Protocol, Sequence, TypeVar, Union
+from typing import Any, Protocol, Sequence, TypeVar, Union
+
+try:
+ import numpy.typing as npt
+
+ NumpyArray = npt.NDArray[Any]
+except ImportError:
+ pass
if sys.version_info >= (3, 10):
from typing import TypeGuard
@@ -10,7 +17,6 @@
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]:
diff --git a/src/PIL/_version.py b/src/PIL/_version.py
index dbed769edc7..cebfd868cfd 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.3.0"
+__version__ = "10.4.0"
diff --git a/src/PIL/features.py b/src/PIL/features.py
index 8a0b140049d..13908c4eb78 100644
--- a/src/PIL/features.py
+++ b/src/PIL/features.py
@@ -4,6 +4,7 @@
import os
import sys
import warnings
+from typing import IO
import PIL
@@ -18,7 +19,7 @@
}
-def check_module(feature):
+def check_module(feature: str) -> bool:
"""
Checks if a module is available.
@@ -42,7 +43,7 @@ def check_module(feature):
return False
-def version_module(feature):
+def version_module(feature: str) -> str | None:
"""
:param feature: The module to check for.
:returns:
@@ -54,13 +55,10 @@ def version_module(feature):
module, ver = modules[feature]
- if ver is None:
- return None
-
return getattr(__import__(module, fromlist=[ver]), ver)
-def get_supported_modules():
+def get_supported_modules() -> list[str]:
"""
:returns: A list of all supported modules.
"""
@@ -75,7 +73,7 @@ def get_supported_modules():
}
-def check_codec(feature):
+def check_codec(feature: str) -> bool:
"""
Checks if a codec is available.
@@ -89,10 +87,10 @@ def check_codec(feature):
codec, lib = codecs[feature]
- return codec + "_encoder" in dir(Image.core)
+ return f"{codec}_encoder" in dir(Image.core)
-def version_codec(feature):
+def version_codec(feature: str) -> str | None:
"""
:param feature: The codec to check for.
:returns:
@@ -105,7 +103,7 @@ def version_codec(feature):
codec, lib = codecs[feature]
- version = getattr(Image.core, lib + "_version")
+ version = getattr(Image.core, f"{lib}_version")
if feature == "libtiff":
return version.split("\n")[0].split("Version ")[1]
@@ -113,7 +111,7 @@ def version_codec(feature):
return version
-def get_supported_codecs():
+def get_supported_codecs() -> list[str]:
"""
:returns: A list of all supported codecs.
"""
@@ -133,7 +131,7 @@ def get_supported_codecs():
}
-def check_feature(feature):
+def check_feature(feature: str) -> bool | None:
"""
Checks if a feature is available.
@@ -157,7 +155,7 @@ def check_feature(feature):
return None
-def version_feature(feature):
+def version_feature(feature: str) -> str | None:
"""
:param feature: The feature to check for.
:returns: The version number as a string, or ``None`` if not available.
@@ -174,14 +172,14 @@ def version_feature(feature):
return getattr(__import__(module, fromlist=[ver]), ver)
-def get_supported_features():
+def get_supported_features() -> list[str]:
"""
:returns: A list of all supported features.
"""
return [f for f in features if check_feature(f)]
-def check(feature):
+def check(feature: str) -> bool | None:
"""
:param feature: A module, codec, or feature name.
:returns:
@@ -199,7 +197,7 @@ def check(feature):
return False
-def version(feature):
+def version(feature: str) -> str | None:
"""
:param feature:
The module, codec, or feature to check for.
@@ -215,7 +213,7 @@ def version(feature):
return None
-def get_supported():
+def get_supported() -> list[str]:
"""
:returns: A list of all supported modules, features, and codecs.
"""
@@ -226,7 +224,7 @@ def get_supported():
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``.
@@ -247,9 +245,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)
@@ -285,9 +283,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")
diff --git a/src/Tk/tkImaging.c b/src/Tk/tkImaging.c
index bd3cafe9596..ef1c00a94e8 100644
--- a/src/Tk/tkImaging.c
+++ b/src/Tk/tkImaging.c
@@ -128,14 +128,7 @@ 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;
}
@@ -287,7 +280,7 @@ load_tkinter_funcs(void) {
* Return 0 for success, non-zero for failure.
*/
- HMODULE* hMods = NULL;
+ HMODULE *hMods = NULL;
HANDLE hProcess;
DWORD cbNeeded;
unsigned int i;
@@ -313,7 +306,7 @@ load_tkinter_funcs(void) {
#endif
return 1;
}
- if (!(hMods = (HMODULE*) malloc(cbNeeded))) {
+ if (!(hMods = (HMODULE *)malloc(cbNeeded))) {
PyErr_NoMemory();
return 1;
}
@@ -345,7 +338,7 @@ load_tkinter_funcs(void) {
} else if (found_tk == 0) {
PyErr_SetString(PyExc_RuntimeError, "Could not find Tk routines");
}
- return (int) ((found_tcl != 1) || (found_tk != 1));
+ return (int)((found_tcl != 1) || (found_tk != 1));
}
#else /* not Windows */
@@ -400,8 +393,8 @@ _func_loader(void *lib) {
return 1;
}
return (
- (TK_PHOTO_PUT_BLOCK =
- (Tk_PhotoPutBlock_t)_dfunc(lib, "Tk_PhotoPutBlock")) == NULL);
+ (TK_PHOTO_PUT_BLOCK = (Tk_PhotoPutBlock_t)_dfunc(lib, "Tk_PhotoPutBlock")) ==
+ NULL);
}
int
diff --git a/src/_imaging.c b/src/_imaging.c
index 520e5079346..ddc8d288550 100644
--- a/src/_imaging.c
+++ b/src/_imaging.c
@@ -110,7 +110,7 @@
#define B16(p, i) ((((int)p[(i)]) << 8) + p[(i) + 1])
#define L16(p, i) ((((int)p[(i) + 1]) << 8) + p[(i)])
-#define S16(v) ((v) < 32768 ? (v) : ((v)-65536))
+#define S16(v) ((v) < 32768 ? (v) : ((v) - 65536))
/* -------------------------------------------------------------------- */
/* OBJECT ADMINISTRATION */
@@ -533,7 +533,9 @@ getink(PyObject *color, Imaging im, char *ink) {
/* unsigned integer, single layer */
if (rIsInt != 1) {
if (tupleSize != 1) {
- PyErr_SetString(PyExc_TypeError, "color must be int or single-element tuple");
+ PyErr_SetString(
+ PyExc_TypeError,
+ "color must be int or single-element tuple");
return NULL;
} else if (!PyArg_ParseTuple(color, "L", &r)) {
return NULL;
@@ -552,7 +554,9 @@ getink(PyObject *color, Imaging im, char *ink) {
a = 255;
if (im->bands == 2) {
if (tupleSize != 1 && tupleSize != 2) {
- PyErr_SetString(PyExc_TypeError, "color must be int, or tuple of one or two elements");
+ PyErr_SetString(
+ PyExc_TypeError,
+ "color must be int, or tuple of one or two elements");
return NULL;
} else if (!PyArg_ParseTuple(color, "L|i", &r, &a)) {
return NULL;
@@ -560,7 +564,10 @@ getink(PyObject *color, Imaging im, char *ink) {
g = b = r;
} else {
if (tupleSize != 3 && tupleSize != 4) {
- PyErr_SetString(PyExc_TypeError, "color must be int, or tuple of one, three or four elements");
+ PyErr_SetString(
+ PyExc_TypeError,
+ "color must be int, or tuple of one, three or four "
+ "elements");
return NULL;
} else if (!PyArg_ParseTuple(color, "Lii|i", &r, &g, &b, &a)) {
return NULL;
@@ -599,7 +606,9 @@ getink(PyObject *color, Imaging im, char *ink) {
g = (UINT8)(r >> 8);
r = (UINT8)r;
} else if (tupleSize != 3) {
- PyErr_SetString(PyExc_TypeError, "color must be int, or tuple of one or three elements");
+ PyErr_SetString(
+ PyExc_TypeError,
+ "color must be int, or tuple of one or three elements");
return NULL;
} else if (!PyArg_ParseTuple(color, "iiL", &b, &g, &r)) {
return NULL;
@@ -1537,14 +1546,14 @@ _putdata(ImagingObject *self, PyObject *args) {
return NULL;
}
-#define set_value_to_item(seq, i) \
-op = PySequence_Fast_GET_ITEM(seq, i); \
-if (PySequence_Check(op)) { \
- PyErr_SetString(PyExc_TypeError, "sequence must be flattened"); \
- return NULL; \
-} else { \
- value = PyFloat_AsDouble(op); \
-}
+#define set_value_to_item(seq, i) \
+ op = PySequence_Fast_GET_ITEM(seq, i); \
+ if (PySequence_Check(op)) { \
+ PyErr_SetString(PyExc_TypeError, "sequence must be flattened"); \
+ return NULL; \
+ } else { \
+ value = PyFloat_AsDouble(op); \
+ }
if (image->image8) {
if (PyBytes_Check(data)) {
unsigned char *p;
@@ -1596,8 +1605,10 @@ if (PySequence_Check(op)) { \
value = value * scale + offset;
}
if (image->type == IMAGING_TYPE_SPECIAL) {
- image->image8[y][x * 2 + (bigendian ? 1 : 0)] = CLIP8((int)value % 256);
- image->image8[y][x * 2 + (bigendian ? 0 : 1)] = CLIP8((int)value >> 8);
+ image->image8[y][x * 2 + (bigendian ? 1 : 0)] =
+ CLIP8((int)value % 256);
+ image->image8[y][x * 2 + (bigendian ? 0 : 1)] =
+ CLIP8((int)value >> 8);
} else {
image->image8[y][x] = (UINT8)CLIP8(value);
}
@@ -1639,8 +1650,7 @@ if (PySequence_Check(op)) { \
for (i = x = y = 0; i < n; i++) {
double value;
set_value_to_item(seq, i);
- IMAGING_PIXEL_INT32(image, x, y) =
- (INT32)(value * scale + offset);
+ IMAGING_PIXEL_INT32(image, x, y) = (INT32)(value * scale + offset);
if (++x >= (int)image->xsize) {
x = 0, y++;
}
@@ -1715,10 +1725,11 @@ _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;
}
@@ -1728,7 +1739,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);
@@ -2018,7 +2028,7 @@ im_setmode(ImagingObject *self, PyObject *args) {
}
static PyObject *
-_transform2(ImagingObject *self, PyObject *args) {
+_transform(ImagingObject *self, PyObject *args) {
static const char *wrong_number = "wrong number of matrix entries";
Imaging imOut;
@@ -2785,8 +2795,8 @@ _font_getmask(ImagingFontObject *self, PyObject *args) {
glyph = &self->glyphs[text[i]];
if (i == 0 || text[i] != text[i - 1]) {
ImagingDelete(bitmap);
- bitmap =
- ImagingCrop(self->bitmap, glyph->sx0, glyph->sy0, glyph->sx1, glyph->sy1);
+ bitmap = ImagingCrop(
+ self->bitmap, glyph->sx0, glyph->sy0, glyph->sx1, glyph->sy1);
if (!bitmap) {
goto failed;
}
@@ -3315,7 +3325,8 @@ _draw_polygon(ImagingDrawObject *self, PyObject *args) {
free(xy);
- if (ImagingDrawPolygon(self->image->image, n, ixy, &ink, fill, width, self->blend) < 0) {
+ if (ImagingDrawPolygon(self->image->image, n, ixy, &ink, fill, width, self->blend) <
+ 0) {
free(ixy);
return NULL;
}
@@ -3636,7 +3647,7 @@ static struct PyMethodDef methods[] = {
{"resize", (PyCFunction)_resize, METH_VARARGS},
{"reduce", (PyCFunction)_reduce, METH_VARARGS},
{"transpose", (PyCFunction)_transpose, METH_VARARGS},
- {"transform2", (PyCFunction)_transform2, METH_VARARGS},
+ {"transform", (PyCFunction)_transform, METH_VARARGS},
{"isblock", (PyCFunction)_isblock, METH_NOARGS},
@@ -3737,7 +3748,7 @@ _getattr_unsafe_ptrs(ImagingObject *self, void *closure) {
self->image->image32,
"image",
self->image->image);
-};
+}
static struct PyGetSetDef getsetters[] = {
{"mode", (getter)_getattr_mode},
@@ -4411,7 +4422,8 @@ setup_module(PyObject *m) {
PyModule_AddObject(m, "HAVE_XCB", have_xcb);
PyObject *pillow_version = PyUnicode_FromString(version);
- PyDict_SetItemString(d, "PILLOW_VERSION", pillow_version ? pillow_version : Py_None);
+ PyDict_SetItemString(
+ d, "PILLOW_VERSION", pillow_version ? pillow_version : Py_None);
Py_XDECREF(pillow_version);
return 0;
diff --git a/src/_imagingcms.c b/src/_imagingcms.c
index 84b8a7e71f9..590e1b983a9 100644
--- a/src/_imagingcms.c
+++ b/src/_imagingcms.c
@@ -181,9 +181,7 @@ cms_profile_dealloc(CmsProfileObject *self) {
/* a transform represents the mapping between two profiles */
typedef struct {
- PyObject_HEAD char mode_in[8];
- char mode_out[8];
- cmsHTRANSFORM transform;
+ PyObject_HEAD cmsHTRANSFORM transform;
} CmsTransformObject;
static PyTypeObject CmsTransform_Type;
@@ -191,7 +189,7 @@ static PyTypeObject CmsTransform_Type;
#define CmsTransform_Check(op) (Py_TYPE(op) == &CmsTransform_Type)
static PyObject *
-cms_transform_new(cmsHTRANSFORM transform, char *mode_in, char *mode_out) {
+cms_transform_new(cmsHTRANSFORM transform) {
CmsTransformObject *self;
self = PyObject_New(CmsTransformObject, &CmsTransform_Type);
@@ -201,9 +199,6 @@ cms_transform_new(cmsHTRANSFORM transform, char *mode_in, char *mode_out) {
self->transform = transform;
- strncpy(self->mode_in, mode_in, 8);
- strncpy(self->mode_out, mode_out, 8);
-
return (PyObject *)self;
}
@@ -218,34 +213,33 @@ cms_transform_dealloc(CmsTransformObject *self) {
static cmsUInt32Number
findLCMStype(char *PILmode) {
- if (strcmp(PILmode, "RGB") == 0) {
- return TYPE_RGBA_8;
- } else if (strcmp(PILmode, "RGBA") == 0) {
- return TYPE_RGBA_8;
- } else if (strcmp(PILmode, "RGBX") == 0) {
+ if (strcmp(PILmode, "RGB") == 0 || strcmp(PILmode, "RGBA") == 0 ||
+ strcmp(PILmode, "RGBX") == 0) {
return TYPE_RGBA_8;
- } else if (strcmp(PILmode, "RGBA;16B") == 0) {
+ }
+ if (strcmp(PILmode, "RGBA;16B") == 0) {
return TYPE_RGBA_16;
- } else if (strcmp(PILmode, "CMYK") == 0) {
+ }
+ if (strcmp(PILmode, "CMYK") == 0) {
return TYPE_CMYK_8;
- } else if (strcmp(PILmode, "L") == 0) {
- return TYPE_GRAY_8;
- } else 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;
- } else if (strcmp(PILmode, "L;16B") == 0) {
+ }
+ if (strcmp(PILmode, "I;16B") == 0 || strcmp(PILmode, "L;16B") == 0) {
return TYPE_GRAY_16_SE;
- } else if (strcmp(PILmode, "YCCA") == 0) {
- return TYPE_YCbCr_8;
- } else if (strcmp(PILmode, "YCC") == 0) {
+ }
+ if (strcmp(PILmode, "YCbCr") == 0 || strcmp(PILmode, "YCCA") == 0 ||
+ strcmp(PILmode, "YCC") == 0) {
return TYPE_YCbCr_8;
- } else if (strcmp(PILmode, "LAB") == 0) {
+ }
+ 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));
}
- else {
- /* take a wild guess... */
- return TYPE_GRAY_8;
- }
+ /* presume "1" or "L" by default */
+ return TYPE_GRAY_8;
}
#define Cms_Min(a, b) ((a) < (b) ? (a) : (b))
@@ -393,9 +387,9 @@ _buildTransform(
iRenderingIntent,
cmsFLAGS);
- Py_END_ALLOW_THREADS
+ Py_END_ALLOW_THREADS;
- if (!hTransform) {
+ if (!hTransform) {
PyErr_SetString(PyExc_ValueError, "cannot build transform");
}
@@ -427,9 +421,9 @@ _buildProofTransform(
iProofIntent,
cmsFLAGS);
- Py_END_ALLOW_THREADS
+ Py_END_ALLOW_THREADS;
- if (!hTransform) {
+ if (!hTransform) {
PyErr_SetString(PyExc_ValueError, "cannot build proof transform");
}
@@ -476,7 +470,7 @@ buildTransform(PyObject *self, PyObject *args) {
return NULL;
}
- return cms_transform_new(transform, sInMode, sOutMode);
+ return cms_transform_new(transform);
}
static PyObject *
@@ -523,7 +517,7 @@ buildProofTransform(PyObject *self, PyObject *args) {
return NULL;
}
- return cms_transform_new(transform, sInMode, sOutMode);
+ return cms_transform_new(transform);
}
static PyObject *
@@ -624,7 +618,7 @@ cms_profile_is_intent_supported(CmsProfileObject *self, PyObject *args) {
static PyObject *
cms_get_display_profile_win32(PyObject *self, PyObject *args) {
char filename[MAX_PATH];
- cmsUInt32Number filename_size;
+ DWORD filename_size;
BOOL ok;
HANDLE handle = 0;
@@ -1456,21 +1450,6 @@ static struct PyMethodDef cms_transform_methods[] = {
{"apply", (PyCFunction)cms_transform_apply, 1}, {NULL, NULL} /* sentinel */
};
-static PyObject *
-cms_transform_getattr_inputMode(CmsTransformObject *self, void *closure) {
- return PyUnicode_FromString(self->mode_in);
-}
-
-static PyObject *
-cms_transform_getattr_outputMode(CmsTransformObject *self, void *closure) {
- return PyUnicode_FromString(self->mode_out);
-}
-
-static struct PyGetSetDef cms_transform_getsetters[] = {
- {"inputMode", (getter)cms_transform_getattr_inputMode},
- {"outputMode", (getter)cms_transform_getattr_outputMode},
- {NULL}};
-
static PyTypeObject CmsTransform_Type = {
PyVarObject_HEAD_INIT(NULL, 0) "PIL.ImageCms.core.CmsTransform", /*tp_name*/
sizeof(CmsTransformObject), /*tp_basicsize*/
@@ -1501,7 +1480,7 @@ static PyTypeObject CmsTransform_Type = {
0, /*tp_iternext*/
cms_transform_methods, /*tp_methods*/
0, /*tp_members*/
- cms_transform_getsetters, /*tp_getset*/
+ 0, /*tp_getset*/
};
static int
diff --git a/src/_imagingft.c b/src/_imagingft.c
index 6e24fcf95ed..ba36cc72c23 100644
--- a/src/_imagingft.c
+++ b/src/_imagingft.c
@@ -47,17 +47,17 @@
;
#ifdef HAVE_RAQM
-# ifdef HAVE_RAQM_SYSTEM
-# include
-# else
-# include "thirdparty/raqm/raqm.h"
-# ifdef HAVE_FRIBIDI_SYSTEM
-# include
-# else
-# include "thirdparty/fribidi-shim/fribidi.h"
-# include
-# endif
-# endif
+#ifdef HAVE_RAQM_SYSTEM
+#include
+#else
+#include "thirdparty/raqm/raqm.h"
+#ifdef HAVE_FRIBIDI_SYSTEM
+#include
+#else
+#include "thirdparty/fribidi-shim/fribidi.h"
+#include
+#endif
+#endif
#endif
static int have_raqm = 0;
@@ -233,18 +233,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
@@ -266,28 +254,34 @@ 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 {
+ 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;
- }
- }
- } else {
- PyErr_SetString(PyExc_TypeError, "expected string");
+ set_text = raqm_set_text_utf8(rq, buffer, size);
+ }
+ 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;
}
@@ -405,13 +399,13 @@ text_layout_fallback(
GlyphInfo **glyph_info,
int mask,
int color) {
- int error, load_flags;
+ 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(
@@ -419,14 +413,11 @@ text_layout_fallback(
"setting text direction, language or font features is not supported "
"without libraqm");
}
- if (!PyUnicode_Check(string)) {
- PyErr_SetString(PyExc_TypeError, "expected string");
- return 0;
- }
- count = 0;
- while (font_getchar(string, count, &ch)) {
- count++;
+ if (PyUnicode_Check(string)) {
+ count = PyUnicode_GET_LENGTH(string);
+ } else {
+ PyBytes_AsStringAndSize(string, &buffer, &count);
}
if (count == 0) {
return 0;
@@ -445,7 +436,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) {
@@ -490,8 +486,7 @@ text_layout(
size_t count;
#ifdef HAVE_RAQM
if (have_raqm && self->layout_engine == LAYOUT_RAQM) {
- count = text_layout_raqm(
- string, self, dir, features, lang, glyph_info);
+ count = text_layout_raqm(string, self, dir, features, lang, glyph_info);
} else
#endif
{
@@ -550,7 +545,17 @@ font_getlength(FontObject *self, PyObject *args) {
}
static int
-bounding_box_and_anchors(FT_Face face, const char *anchor, int horizontal_dir, GlyphInfo *glyph_info, size_t count, int load_flags, int *width, int *height, int *x_offset, int *y_offset) {
+bounding_box_and_anchors(
+ FT_Face face,
+ const char *anchor,
+ int horizontal_dir,
+ GlyphInfo *glyph_info,
+ size_t count,
+ int load_flags,
+ int *width,
+ int *height,
+ int *x_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 */
@@ -558,8 +563,8 @@ bounding_box_and_anchors(FT_Face face, const char *anchor, int horizontal_dir, G
int x_anchor, y_anchor; /* offset of point drawn at (0, 0), in pixels */
int error;
FT_Glyph glyph;
- FT_BBox bbox; /* glyph bounding box */
- size_t i; /* glyph_info index */
+ FT_BBox bbox; /* glyph bounding box */
+ size_t i; /* glyph_info index */
/*
* text bounds are given by:
* - bounding boxes of individual glyphs
@@ -654,8 +659,7 @@ bounding_box_and_anchors(FT_Face face, const char *anchor, int horizontal_dir, G
break;
case 'm': // middle (ascender + descender) / 2
y_anchor = PIXEL(
- (face->size->metrics.ascender +
- face->size->metrics.descender) /
+ (face->size->metrics.ascender + face->size->metrics.descender) /
2);
break;
case 's': // horizontal baseline
@@ -719,7 +723,7 @@ bounding_box_and_anchors(FT_Face face, const char *anchor, int horizontal_dir, G
static PyObject *
font_getsize(FontObject *self, PyObject *args) {
int width, height, x_offset, y_offset;
- int load_flags; /* FreeType load_flags parameter */
+ int load_flags; /* FreeType load_flags parameter */
int error;
GlyphInfo *glyph_info = NULL; /* computed text layout */
size_t count; /* glyph_info length */
@@ -758,7 +762,17 @@ font_getsize(FontObject *self, PyObject *args) {
load_flags |= FT_LOAD_COLOR;
}
- error = bounding_box_and_anchors(self->face, anchor, horizontal_dir, glyph_info, count, load_flags, &width, &height, &x_offset, &y_offset);
+ error = bounding_box_and_anchors(
+ self->face,
+ anchor,
+ horizontal_dir,
+ glyph_info,
+ count,
+ load_flags,
+ &width,
+ &height,
+ &x_offset,
+ &y_offset);
if (glyph_info) {
PyMem_Free(glyph_info);
glyph_info = NULL;
@@ -767,12 +781,7 @@ font_getsize(FontObject *self, PyObject *args) {
return NULL;
}
- return Py_BuildValue(
- "(ii)(ii)",
- width,
- height,
- x_offset,
- y_offset);
+ return Py_BuildValue("(ii)(ii)", width, height, x_offset, y_offset);
}
static PyObject *
@@ -869,7 +878,17 @@ font_render(FontObject *self, PyObject *args) {
horizontal_dir = dir && strcmp(dir, "ttb") == 0 ? 0 : 1;
- error = bounding_box_and_anchors(self->face, anchor, horizontal_dir, glyph_info, count, load_flags, &width, &height, &x_offset, &y_offset);
+ error = bounding_box_and_anchors(
+ self->face,
+ anchor,
+ horizontal_dir,
+ glyph_info,
+ count,
+ load_flags,
+ &width,
+ &height,
+ &x_offset,
+ &y_offset);
if (error) {
PyMem_Del(glyph_info);
return NULL;
@@ -1066,17 +1085,26 @@ font_render(FontObject *self, PyObject *args) {
/* paste only if source has data */
if (src_alpha > 0) {
/* unpremultiply BGRa */
- int src_red = CLIP8((255 * (int)source[k * 4 + 2]) / src_alpha);
- int src_green = CLIP8((255 * (int)source[k * 4 + 1]) / src_alpha);
- int src_blue = CLIP8((255 * (int)source[k * 4 + 0]) / src_alpha);
+ int src_red =
+ CLIP8((255 * (int)source[k * 4 + 2]) / src_alpha);
+ int src_green =
+ CLIP8((255 * (int)source[k * 4 + 1]) / src_alpha);
+ int src_blue =
+ CLIP8((255 * (int)source[k * 4 + 0]) / src_alpha);
/* blend required if target has data */
if (target[k * 4 + 3] > 0) {
/* blend RGBA colors */
- target[k * 4 + 0] = BLEND(src_alpha, target[k * 4 + 0], src_red, tmp);
- target[k * 4 + 1] = BLEND(src_alpha, target[k * 4 + 1], src_green, tmp);
- target[k * 4 + 2] = 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));
+ target[k * 4 + 0] =
+ BLEND(src_alpha, target[k * 4 + 0], src_red, tmp);
+ target[k * 4 + 1] =
+ BLEND(src_alpha, target[k * 4 + 1], src_green, tmp);
+ target[k * 4 + 2] =
+ 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));
} else {
/* paste unpremultiplied RGBA values */
target[k * 4 + 0] = src_red;
@@ -1093,10 +1121,16 @@ font_render(FontObject *self, PyObject *args) {
unsigned int src_alpha = source[k] * convert_scale;
if (src_alpha > 0) {
if (target[k * 4 + 3] > 0) {
- target[k * 4 + 0] = BLEND(src_alpha, target[k * 4 + 0], ink[0], tmp);
- target[k * 4 + 1] = BLEND(src_alpha, target[k * 4 + 1], ink[1], tmp);
- target[k * 4 + 2] = BLEND(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 + 0] = BLEND(
+ src_alpha, target[k * 4 + 0], ink[0], tmp);
+ target[k * 4 + 1] = BLEND(
+ src_alpha, target[k * 4 + 1], ink[1], tmp);
+ target[k * 4 + 2] = BLEND(
+ 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));
} else {
target[k * 4 + 0] = ink[0];
target[k * 4 + 1] = ink[1];
@@ -1109,7 +1143,13 @@ font_render(FontObject *self, PyObject *args) {
for (k = x0; k < x1; k++) {
unsigned int src_alpha = source[k] * convert_scale;
if (src_alpha > 0) {
- target[k] = target[k] > 0 ? CLIP8(src_alpha + MULDIV255(target[k], (255 - src_alpha), tmp)) : src_alpha;
+ target[k] =
+ target[k] > 0
+ ? CLIP8(
+ src_alpha +
+ MULDIV255(
+ target[k], (255 - src_alpha), tmp))
+ : src_alpha;
}
}
}
@@ -1249,7 +1289,8 @@ 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);
+ PyDict_SetItemString(
+ list_axis, "name", axis_name ? axis_name : Py_None);
Py_XDECREF(axis_name);
break;
}
@@ -1299,7 +1340,7 @@ font_setvaraxes(FontObject *self, PyObject *args) {
}
num_coords = PyObject_Length(axes);
- coords = (FT_Fixed*)malloc(num_coords * sizeof(FT_Fixed));
+ coords = (FT_Fixed *)malloc(num_coords * sizeof(FT_Fixed));
if (coords == NULL) {
return PyErr_NoMemory();
}
diff --git a/src/display.c b/src/display.c
index ef2ff3754f1..990f4b0a5c9 100644
--- a/src/display.c
+++ b/src/display.c
@@ -427,7 +427,6 @@ PyImaging_GrabScreenWin32(PyObject *self, PyObject *args) {
PyObject *
PyImaging_GrabClipboardWin32(PyObject *self, PyObject *args) {
- int clip;
HANDLE handle = NULL;
int size;
void *data;
@@ -619,7 +618,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;
@@ -717,7 +716,7 @@ PyImaging_DrawWmf(PyObject *self, PyObject *args) {
HDC dc;
RECT rect;
PyObject *buffer = NULL;
- char *ptr;
+ void *ptr;
char *data;
Py_ssize_t datasize;
diff --git a/src/encode.c b/src/encode.c
index c7dd510150e..442b5d04f88 100644
--- a/src/encode.c
+++ b/src/encode.c
@@ -1163,8 +1163,10 @@ PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) {
((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)->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;
@@ -1333,9 +1335,7 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args) {
if (comment && comment_size > 0) {
/* Size is stored as as an uint16, subtract 4 bytes for the header */
if (comment_size >= 65532) {
- PyErr_SetString(
- PyExc_ValueError,
- "JPEG 2000 comment is too long");
+ PyErr_SetString(PyExc_ValueError, "JPEG 2000 comment is too long");
Py_DECREF(encoder);
return NULL;
}
diff --git a/src/libImaging/Access.c b/src/libImaging/Access.c
index 091c84e18fa..3a5e918e837 100644
--- a/src/libImaging/Access.c
+++ b/src/libImaging/Access.c
@@ -81,12 +81,6 @@ get_pixel_16B(Imaging im, int x, int y, void *color) {
#endif
}
-static void
-get_pixel_16(Imaging im, int x, int y, void *color) {
- UINT8 *in = (UINT8 *)&im->image[y][x + x];
- memcpy(color, in, sizeof(UINT16));
-}
-
static void
get_pixel_BGR15(Imaging im, int x, int y, void *color) {
UINT8 *in = (UINT8 *)&im->image8[y][x * 2];
@@ -191,11 +185,11 @@ put_pixel_32(Imaging im, int x, int y, const void *color) {
void
ImagingAccessInit() {
-#define ADD(mode_, get_pixel_, put_pixel_) \
- { \
- ImagingAccess access = add_item(mode_); \
- access->get_pixel = get_pixel_; \
- access->put_pixel = put_pixel_; \
+#define ADD(mode_, get_pixel_, put_pixel_) \
+ { \
+ ImagingAccess access = add_item(mode_); \
+ access->get_pixel = get_pixel_; \
+ access->put_pixel = put_pixel_; \
}
/* populate access table */
@@ -207,7 +201,11 @@ ImagingAccessInit() {
ADD("I;16", get_pixel_16L, put_pixel_16L);
ADD("I;16L", get_pixel_16L, put_pixel_16L);
ADD("I;16B", get_pixel_16B, put_pixel_16B);
- ADD("I;16N", get_pixel_16, put_pixel_16L);
+#ifdef WORDS_BIGENDIAN
+ ADD("I;16N", get_pixel_16B, put_pixel_16B);
+#else
+ ADD("I;16N", get_pixel_16L, put_pixel_16L);
+#endif
ADD("I;32L", get_pixel_32L, put_pixel_32L);
ADD("I;32B", get_pixel_32B, put_pixel_32B);
ADD("F", get_pixel_32, put_pixel_32);
diff --git a/src/libImaging/BcnDecode.c b/src/libImaging/BcnDecode.c
index 5e4296eeba1..72f478d8d60 100644
--- a/src/libImaging/BcnDecode.c
+++ b/src/libImaging/BcnDecode.c
@@ -83,7 +83,6 @@ decode_bc1_color(rgba *dst, const UINT8 *src, int separate_alpha) {
g1 = p[1].g;
b1 = p[1].b;
-
/* NOTE: BC2 and BC3 reuse BC1 color blocks but always act like c0 > c1 */
if (col.c0 > col.c1 || separate_alpha) {
p[2].r = (2 * r0 + 1 * r1) / 3;
@@ -354,8 +353,7 @@ decode_bc7_block(rgba *col, const UINT8 *src) {
}
return;
}
- while (!(mode & (1 << bit++)))
- ;
+ while (!(mode & (1 << bit++)));
mode = bit - 1;
info = &bc7_modes[mode];
/* color selection bits: {subset}{endpoint} */
@@ -546,7 +544,7 @@ static const UINT8 bc6_bit_packings[][75] = {
21, 22, 23, 24, 25, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41,
48, 49, 50, 51, 52, 10, 112, 113, 114, 115, 64, 65, 66, 67, 26,
176, 160, 161, 162, 163, 80, 81, 82, 83, 42, 177, 128, 129, 130, 131,
- 96, 97, 98, 99, 100, 178, 144, 145, 146, 147, 148, 179},
+ 96, 97, 98, 99, 100, 178, 144, 145, 146, 147, 148, 179},
{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, 10, 164, 112, 113, 114, 115, 64, 65, 66, 67, 68,
@@ -684,7 +682,7 @@ bc6_clamp(float value) {
} else if (value > 1.0f) {
return 255;
} else {
- return (UINT8) (value * 255.0f);
+ return (UINT8)(value * 255.0f);
}
}
@@ -826,7 +824,13 @@ put_block(Imaging im, ImagingCodecState state, const char *col, int sz, int C) {
static int
decode_bcn(
- Imaging im, ImagingCodecState state, const UINT8 *src, int bytes, int N, int C, char *pixel_format) {
+ Imaging im,
+ ImagingCodecState state,
+ const UINT8 *src,
+ int bytes,
+ int N,
+ int C,
+ char *pixel_format) {
int ymax = state->ysize + state->yoff;
const UINT8 *ptr = src;
switch (N) {
@@ -849,8 +853,7 @@ decode_bcn(
DECODE_LOOP(2, 16, rgba);
DECODE_LOOP(3, 16, rgba);
DECODE_LOOP(4, 8, lum);
- case 5:
- {
+ case 5: {
int sign = strcmp(pixel_format, "BC5S") == 0 ? 1 : 0;
while (bytes >= 16) {
rgba col[16];
@@ -865,8 +868,7 @@ decode_bcn(
}
break;
}
- case 6:
- {
+ case 6: {
int sign = strcmp(pixel_format, "BC6HS") == 0 ? 1 : 0;
while (bytes >= 16) {
rgba col[16];
@@ -880,7 +882,7 @@ decode_bcn(
}
break;
}
- DECODE_LOOP(7, 16, rgba);
+ DECODE_LOOP(7, 16, rgba);
#undef DECODE_LOOP
}
return (int)(ptr - src);
diff --git a/src/libImaging/BoxBlur.c b/src/libImaging/BoxBlur.c
index adf425d0dbd..4ea9c77178a 100644
--- a/src/libImaging/BoxBlur.c
+++ b/src/libImaging/BoxBlur.c
@@ -313,12 +313,12 @@ _gaussian_blur_radius(float radius, int passes) {
}
Imaging
-ImagingGaussianBlur(Imaging imOut, Imaging imIn, float xradius, float yradius, int passes) {
+ImagingGaussianBlur(
+ 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
- );
+ imOut,
+ imIn,
+ _gaussian_blur_radius(xradius, passes),
+ _gaussian_blur_radius(yradius, passes),
+ passes);
}
diff --git a/src/libImaging/Convert.c b/src/libImaging/Convert.c
index 7e60a960c1b..fcb5f7ad95e 100644
--- a/src/libImaging/Convert.c
+++ b/src/libImaging/Convert.c
@@ -254,9 +254,8 @@ static void
rgb2i16l(UINT8 *out_, const UINT8 *in, int xsize) {
int x;
for (x = 0; x < xsize; x++, in += 4) {
- UINT8 v = CLIP16(L24(in) >> 16);
- *out_++ = v;
- *out_++ = v >> 8;
+ *out_++ = L24(in) >> 16;
+ *out_++ = 0;
}
}
@@ -264,9 +263,8 @@ static void
rgb2i16b(UINT8 *out_, const UINT8 *in, int xsize) {
int x;
for (x = 0; x < xsize; x++, in += 4) {
- UINT8 v = CLIP16(L24(in) >> 16);
- *out_++ = v >> 8;
- *out_++ = v;
+ *out_++ = 0;
+ *out_++ = L24(in) >> 16;
}
}
@@ -520,8 +518,8 @@ rgba2rgb_(UINT8 *out, const UINT8 *in, int xsize) {
/*
* Conversion of RGB + single transparent color either to
- * RGBA or LA, where any pixel matching the color will have the alpha channel set to 0, or
- * RGBa or La, where any pixel matching the color will have all channels set to 0
+ * RGBA or LA, where any pixel matching the color will have the alpha channel set to 0,
+ * or RGBa or La, where any pixel matching the color will have all channels set to 0
*/
static void
@@ -1678,7 +1676,8 @@ convert(
return (Imaging)ImagingError_ValueError("conversion not supported");
#else
static char buf[100];
- snprintf(buf, 100, "conversion from %.10s to %.10s not supported", imIn->mode, mode);
+ snprintf(
+ buf, 100, "conversion from %.10s to %.10s not supported", imIn->mode, mode);
return (Imaging)ImagingError_ValueError(buf);
#endif
}
@@ -1722,25 +1721,24 @@ ImagingConvertTransparent(Imaging imIn, const char *mode, int r, int g, int b) {
return (Imaging)ImagingError_ModeError();
}
- if (strcmp(imIn->mode, "RGB") == 0 && (strcmp(mode, "RGBA") == 0 || strcmp(mode, "RGBa") == 0)) {
+ if (strcmp(imIn->mode, "RGB") == 0 &&
+ (strcmp(mode, "RGBA") == 0 || strcmp(mode, "RGBa") == 0)) {
convert = rgb2rgba;
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) {
diff --git a/src/libImaging/Dib.c b/src/libImaging/Dib.c
index 1b5bfe1324e..269be10587b 100644
--- a/src/libImaging/Dib.c
+++ b/src/libImaging/Dib.c
@@ -94,8 +94,8 @@ ImagingNewDIB(const char *mode, int xsize, int ysize) {
return (ImagingDIB)ImagingError_MemoryError();
}
- dib->bitmap =
- CreateDIBSection(dib->dc, dib->info, DIB_RGB_COLORS, &dib->bits, NULL, 0);
+ dib->bitmap = CreateDIBSection(
+ dib->dc, dib->info, DIB_RGB_COLORS, (void **)&dib->bits, NULL, 0);
if (!dib->bitmap) {
free(dib->info);
free(dib);
diff --git a/src/libImaging/Draw.c b/src/libImaging/Draw.c
index 0ccf22d58dd..133696dd86f 100644
--- a/src/libImaging/Draw.c
+++ b/src/libImaging/Draw.c
@@ -48,7 +48,7 @@
* This guarantees that ROUND_UP|DOWN(f) == -ROUND_UP|DOWN(-f)
*/
#define ROUND_UP(f) ((int)((f) >= 0.0 ? floor((f) + 0.5F) : -floor(fabs(f) + 0.5F)))
-#define ROUND_DOWN(f) ((int)((f) >= 0.0 ? ceil((f)-0.5F) : -ceil(fabs(f) - 0.5F)))
+#define ROUND_DOWN(f) ((int)((f) >= 0.0 ? ceil((f) - 0.5F) : -ceil(fabs(f) - 0.5F)))
/* -------------------------------------------------------------------- */
/* Primitives */
@@ -439,7 +439,14 @@ draw_horizontal_lines(
* Filled polygon draw function using scan line algorithm.
*/
static inline int
-polygon_generic(Imaging im, int n, Edge *e, int ink, int eofill, hline_handler hline, int hasAlpha) {
+polygon_generic(
+ Imaging im,
+ int n,
+ Edge *e,
+ int ink,
+ int eofill,
+ hline_handler hline,
+ int hasAlpha) {
Edge **edge_table;
float *xx;
int edge_count = 0;
@@ -499,7 +506,7 @@ polygon_generic(Imaging im, int n, Edge *e, int ink, int eofill, hline_handler h
// 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 && roundf(xx[j - 1]) == xx[j - 1]) {
// Connect discontiguous corners
for (k = 0; k < i; k++) {
Edge *other_edge = edge_table[k];
@@ -510,23 +517,38 @@ polygon_generic(Imaging im, int n, Edge *e, int ink, int eofill, hline_handler h
// 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) {
+ 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;
- adjacent_line_x = (ymin + offset - current->y0) * current->dx + current->x0;
- adjacent_line_x_other_edge = (ymin + offset - other_edge->y0) * other_edge->dx + other_edge->x0;
+ adjacent_line_x =
+ (ymin + offset - current->y0) * current->dx +
+ current->x0;
+ adjacent_line_x_other_edge =
+ (ymin + offset - other_edge->y0) * other_edge->dx +
+ 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);
+ xx[k] = fmin(
+ 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;
@@ -552,7 +574,8 @@ polygon_generic(Imaging im, int n, Edge *e, int ink, int eofill, hline_handler h
int x_start = ROUND_UP(xx[i - 1]);
if (x_pos > x_start) {
- // Line would be partway through x_pos, so increase the starting point
+ // Line would be partway through x_pos, so increase the starting
+ // point
x_start = x_pos;
if (x_end < x_start) {
// Line would now end before it started
@@ -776,7 +799,8 @@ ImagingDrawRectangle(
}
int
-ImagingDrawPolygon(Imaging im, int count, int *xy, const void *ink_, int fill, int width, int op) {
+ImagingDrawPolygon(
+ 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;
@@ -803,7 +827,7 @@ ImagingDrawPolygon(Imaging im, int count, int *xy, const void *ink_, int fill, i
if (y0 == y1 && i != 0 && y0 == xy[i * 2 - 1]) {
// This is a horizontal line,
// that immediately follows another horizontal line
- Edge *last_e = &e[n-1];
+ Edge *last_e = &e[n - 1];
if (x1 > x0 && x0 > xy[i * 2 - 2]) {
// They are both increasing in x
last_e->xmax = x1;
@@ -826,14 +850,24 @@ ImagingDrawPolygon(Imaging im, int count, int *xy, const void *ink_, int fill, i
/* Outline */
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);
+ draw->line(
+ 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 {
for (i = 0; i < count - 1; i++) {
- ImagingDrawWideLine(im, xy[i * 2], xy[i * 2 + 1], xy[i * 2 + 2], xy[i * 2 + 3], ink_, width, op);
+ ImagingDrawWideLine(
+ im,
+ xy[i * 2],
+ xy[i * 2 + 1],
+ xy[i * 2 + 2],
+ xy[i * 2 + 3],
+ ink_,
+ width,
+ op);
}
- ImagingDrawWideLine(im, xy[i * 2], xy[i * 2 + 1], xy[0], xy[1], ink_, width, op);
+ ImagingDrawWideLine(
+ im, xy[i * 2], xy[i * 2 + 1], xy[0], xy[1], ink_, width, op);
}
}
diff --git a/src/libImaging/Filter.c b/src/libImaging/Filter.c
index 4dcd368ca80..85de77fcbbc 100644
--- a/src/libImaging/Filter.c
+++ b/src/libImaging/Filter.c
@@ -106,7 +106,7 @@ ImagingExpand(Imaging imIn, int xmargin, int ymargin) {
void
ImagingFilter3x3(Imaging imOut, Imaging im, const float *kernel, float offset) {
-#define KERNEL1x3(in0, x, kernel, d) \
+#define KERNEL1x3(in0, x, kernel, d) \
(_i2f(in0[x - d]) * (kernel)[0] + _i2f(in0[x]) * (kernel)[1] + \
_i2f(in0[x + d]) * (kernel)[2])
@@ -224,10 +224,9 @@ ImagingFilter3x3(Imaging imOut, Imaging im, const float *kernel, float offset) {
void
ImagingFilter5x5(Imaging imOut, Imaging im, const float *kernel, float offset) {
-#define KERNEL1x5(in0, x, kernel, d) \
- (_i2f(in0[x - d - d]) * (kernel)[0] + \
- _i2f(in0[x - d]) * (kernel)[1] + _i2f(in0[x]) * (kernel)[2] + \
- _i2f(in0[x + d]) * (kernel)[3] + \
+#define KERNEL1x5(in0, x, kernel, d) \
+ (_i2f(in0[x - d - d]) * (kernel)[0] + _i2f(in0[x - d]) * (kernel)[1] + \
+ _i2f(in0[x]) * (kernel)[2] + _i2f(in0[x + d]) * (kernel)[3] + \
_i2f(in0[x + d + d]) * (kernel)[4])
int x = 0, y = 0;
diff --git a/src/libImaging/FliDecode.c b/src/libImaging/FliDecode.c
index d6e4ea0ff9d..6b2518d35ce 100644
--- a/src/libImaging/FliDecode.c
+++ b/src/libImaging/FliDecode.c
@@ -231,8 +231,9 @@ ImagingFliDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t byt
}
/* Note, have to check Data + size, not just ptr + size) */
if (data + (state->xsize * state->ysize) > ptr + bytes) {
- /* not enough data for frame */
- /* UNDONE Unclear that we're actually going to leave the buffer at the right place. */
+ // not enough data for frame
+ // UNDONE Unclear that we're actually going to leave the buffer at
+ // the right place.
return ptr - buf; /* bytes consumed */
}
for (y = 0; y < state->ysize; y++) {
@@ -251,7 +252,7 @@ ImagingFliDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t byt
return -1;
}
advance = I32(ptr);
- if (advance == 0 ) {
+ if (advance == 0) {
// If there's no advance, we're in an infinite loop
state->errcode = IMAGING_CODEC_BROKEN;
return -1;
diff --git a/src/libImaging/Geometry.c b/src/libImaging/Geometry.c
index 0c591579217..cf3bc997942 100644
--- a/src/libImaging/Geometry.c
+++ b/src/libImaging/Geometry.c
@@ -565,13 +565,13 @@ bilinear_filter32RGB(void *out, Imaging im, double xin, double yin) {
#undef BILINEAR_HEAD
#undef BILINEAR_BODY
-#define BICUBIC(v, v1, v2, v3, v4, d) \
- { \
- double p1 = v2; \
- double p2 = -v1 + v3; \
- double p3 = 2 * (v1 - v2) + v3 - v4; \
- double p4 = -v1 + v2 - v3 + v4; \
- v = p1 + (d) * (p2 + (d) * (p3 + (d)*p4)); \
+#define BICUBIC(v, v1, v2, v3, v4, d) \
+ { \
+ double p1 = v2; \
+ double p2 = -v1 + v3; \
+ double p3 = 2 * (v1 - v2) + v3 - v4; \
+ double p4 = -v1 + v2 - v3 + v4; \
+ v = p1 + (d) * (p2 + (d) * (p3 + (d) * p4)); \
}
#define BICUBIC_HEAD(type) \
@@ -966,7 +966,7 @@ affine_fixed(
ysize = (int)imIn->ysize;
/* use 16.16 fixed point arithmetics */
-#define FIX(v) FLOOR((v)*65536.0 + 0.5)
+#define FIX(v) FLOOR((v) * 65536.0 + 0.5)
a0 = FIX(a[0]);
a1 = FIX(a[1]);
diff --git a/src/libImaging/GetBBox.c b/src/libImaging/GetBBox.c
index 86c687ca0a8..bd2a2778cdb 100644
--- a/src/libImaging/GetBBox.c
+++ b/src/libImaging/GetBBox.c
@@ -58,11 +58,11 @@ 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
diff --git a/src/libImaging/Gif.h b/src/libImaging/Gif.h
index 5d7e2bdaa96..8edfbc2edc0 100644
--- a/src/libImaging/Gif.h
+++ b/src/libImaging/Gif.h
@@ -9,10 +9,10 @@
/* Max size for a LZW code word. */
-#define GIFBITS 12
+#define GIFBITS 12
-#define GIFTABLE (1<next_code = st->end_code + 1;
st->max_code = 2 * st->clear_code - 1;
st->code_width = st->bits + 1;
memset(st->codes, 0, sizeof(st->codes));
}
-static void glzwe_init(GIFENCODERSTATE *st) {
+static void
+glzwe_init(GIFENCODERSTATE *st) {
st->clear_code = 1 << st->bits;
st->end_code = st->clear_code + 1;
glzwe_reset(st);
@@ -64,156 +72,157 @@ static void glzwe_init(GIFENCODERSTATE *st) {
st->code_buffer = 0;
}
-static int glzwe(GIFENCODERSTATE *st, const UINT8 *in_ptr, UINT8 *out_ptr,
- UINT32 *in_avail, UINT32 *out_avail,
- UINT32 end_of_data) {
+static int
+glzwe(
+ GIFENCODERSTATE *st,
+ const UINT8 *in_ptr,
+ UINT8 *out_ptr,
+ UINT32 *in_avail,
+ UINT32 *out_avail,
+ UINT32 end_of_data) {
switch (st->entry_state) {
-
- case LZW_TRY_IN1:
+ case LZW_TRY_IN1:
get_first_byte:
- if (!*in_avail) {
- if (end_of_data) {
- goto end_of_data;
+ if (!*in_avail) {
+ if (end_of_data) {
+ goto end_of_data;
+ }
+ st->entry_state = LZW_TRY_IN1;
+ return GLZW_NO_INPUT_AVAIL;
}
- st->entry_state = LZW_TRY_IN1;
- return GLZW_NO_INPUT_AVAIL;
- }
- st->head = *in_ptr++;
- (*in_avail)--;
+ st->head = *in_ptr++;
+ (*in_avail)--;
- case LZW_TRY_IN2:
+ case LZW_TRY_IN2:
encode_loop:
- if (!*in_avail) {
- if (end_of_data) {
- st->code = st->head;
- st->put_state = PUT_LAST_HEAD;
- goto put_code;
- }
- st->entry_state = LZW_TRY_IN2;
- return GLZW_NO_INPUT_AVAIL;
- }
- st->tail = *in_ptr++;
- (*in_avail)--;
-
- /* Knuth TAOCP vol 3 sec. 6.4 algorithm D. */
- /* Hash found experimentally to be pretty good. */
- /* This works ONLY with TABLE_SIZE a power of 2. */
- st->probe = ((st->head ^ (st->tail << 6)) * 31) & (TABLE_SIZE - 1);
- while (st->codes[st->probe]) {
- if ((st->codes[st->probe] & 0xFFFFF) ==
- ((st->head << 8) | st->tail)) {
- st->head = st->codes[st->probe] >> 20;
- goto encode_loop;
- } else {
- /* Reprobe decrement must be non-zero and relatively prime to table
- * size. So, any odd positive number for power-of-2 size. */
- if ((st->probe -= ((st->tail << 2) | 1)) < 0) {
- st->probe += TABLE_SIZE;
+ if (!*in_avail) {
+ if (end_of_data) {
+ st->code = st->head;
+ st->put_state = PUT_LAST_HEAD;
+ goto put_code;
}
+ st->entry_state = LZW_TRY_IN2;
+ return GLZW_NO_INPUT_AVAIL;
}
- }
- /* Key not found, probe is at empty slot. */
- st->code = st->head;
- st->put_state = PUT_HEAD;
- goto put_code;
-insert_code_or_clear: /* jump here after put_code */
- if (st->next_code < CODE_LIMIT) {
- st->codes[st->probe] = (st->next_code << 20) |
- (st->head << 8) | st->tail;
- if (st->next_code > st->max_code) {
- st->max_code = st->max_code * 2 + 1;
- st->code_width++;
+ st->tail = *in_ptr++;
+ (*in_avail)--;
+
+ /* Knuth TAOCP vol 3 sec. 6.4 algorithm D. */
+ /* Hash found experimentally to be pretty good. */
+ /* This works ONLY with TABLE_SIZE a power of 2. */
+ st->probe = ((st->head ^ (st->tail << 6)) * 31) & (TABLE_SIZE - 1);
+ while (st->codes[st->probe]) {
+ if ((st->codes[st->probe] & 0xFFFFF) == ((st->head << 8) | st->tail)) {
+ st->head = st->codes[st->probe] >> 20;
+ goto encode_loop;
+ } else {
+ // Reprobe decrement must be non-zero and relatively prime to table
+ // size. So, any odd positive number for power-of-2 size.
+ if ((st->probe -= ((st->tail << 2) | 1)) < 0) {
+ st->probe += TABLE_SIZE;
+ }
+ }
}
- st->next_code++;
- } else {
- st->code = st->clear_code;
- st->put_state = PUT_CLEAR;
+ /* Key not found, probe is at empty slot. */
+ st->code = st->head;
+ st->put_state = PUT_HEAD;
goto put_code;
+insert_code_or_clear: /* jump here after put_code */
+ if (st->next_code < CODE_LIMIT) {
+ st->codes[st->probe] =
+ (st->next_code << 20) | (st->head << 8) | st->tail;
+ if (st->next_code > st->max_code) {
+ st->max_code = st->max_code * 2 + 1;
+ st->code_width++;
+ }
+ st->next_code++;
+ } else {
+ st->code = st->clear_code;
+ st->put_state = PUT_CLEAR;
+ goto put_code;
reset_after_clear: /* jump here after put_code */
- glzwe_reset(st);
- }
- st->head = st->tail;
- goto encode_loop;
+ glzwe_reset(st);
+ }
+ st->head = st->tail;
+ goto encode_loop;
- case LZW_INITIAL:
- glzwe_reset(st);
- st->code = st->clear_code;
- st->put_state = PUT_INIT_CLEAR;
+ case LZW_INITIAL:
+ glzwe_reset(st);
+ st->code = st->clear_code;
+ st->put_state = PUT_INIT_CLEAR;
put_code:
- st->code_bits_left = st->code_width;
+ st->code_bits_left = st->code_width;
check_buf_bits:
- if (!st->buf_bits_left) { /* out buffer full */
-
- case LZW_TRY_OUT1:
- if (!*out_avail) {
- st->entry_state = LZW_TRY_OUT1;
- return GLZW_NO_OUTPUT_AVAIL;
+ if (!st->buf_bits_left) { /* out buffer full */
+
+ case LZW_TRY_OUT1:
+ if (!*out_avail) {
+ st->entry_state = LZW_TRY_OUT1;
+ return GLZW_NO_OUTPUT_AVAIL;
+ }
+ *out_ptr++ = st->code_buffer;
+ (*out_avail)--;
+ st->code_buffer = 0;
+ st->buf_bits_left = 8;
+ }
+ /* code bits to pack */
+ UINT32 n = st->buf_bits_left < st->code_bits_left ? st->buf_bits_left
+ : st->code_bits_left;
+ st->code_buffer |= (st->code & ((1 << n) - 1)) << (8 - st->buf_bits_left);
+ st->code >>= n;
+ st->buf_bits_left -= n;
+ st->code_bits_left -= n;
+ if (st->code_bits_left) {
+ goto check_buf_bits;
+ }
+ switch (st->put_state) {
+ case PUT_INIT_CLEAR:
+ goto get_first_byte;
+ case PUT_HEAD:
+ goto insert_code_or_clear;
+ case PUT_CLEAR:
+ goto reset_after_clear;
+ case PUT_LAST_HEAD:
+ goto end_of_data;
+ case PUT_END:
+ goto flush_code_buffer;
+ default:
+ return GLZW_INTERNAL_ERROR;
}
- *out_ptr++ = st->code_buffer;
- (*out_avail)--;
- st->code_buffer = 0;
- st->buf_bits_left = 8;
- }
- /* code bits to pack */
- UINT32 n = st->buf_bits_left < st->code_bits_left
- ? st->buf_bits_left : st->code_bits_left;
- st->code_buffer |=
- (st->code & ((1 << n) - 1)) << (8 - st->buf_bits_left);
- st->code >>= n;
- st->buf_bits_left -= n;
- st->code_bits_left -= n;
- if (st->code_bits_left) {
- goto check_buf_bits;
- }
- switch (st->put_state) {
- case PUT_INIT_CLEAR:
- goto get_first_byte;
- case PUT_HEAD:
- goto insert_code_or_clear;
- case PUT_CLEAR:
- goto reset_after_clear;
- case PUT_LAST_HEAD:
- goto end_of_data;
- case PUT_END:
- goto flush_code_buffer;
- default:
- return GLZW_INTERNAL_ERROR;
- }
end_of_data:
- st->code = st->end_code;
- st->put_state = PUT_END;
- goto put_code;
+ st->code = st->end_code;
+ st->put_state = PUT_END;
+ goto put_code;
flush_code_buffer: /* jump here after put_code */
- if (st->buf_bits_left < 8) {
-
- case LZW_TRY_OUT2:
- if (!*out_avail) {
- st->entry_state = LZW_TRY_OUT2;
- return GLZW_NO_OUTPUT_AVAIL;
+ if (st->buf_bits_left < 8) {
+ case LZW_TRY_OUT2:
+ if (!*out_avail) {
+ st->entry_state = LZW_TRY_OUT2;
+ return GLZW_NO_OUTPUT_AVAIL;
+ }
+ *out_ptr++ = st->code_buffer;
+ (*out_avail)--;
}
- *out_ptr++ = st->code_buffer;
- (*out_avail)--;
- }
- st->entry_state = LZW_FINISHED;
- return GLZW_OK;
+ st->entry_state = LZW_FINISHED;
+ return GLZW_OK;
- case LZW_FINISHED:
- return GLZW_OK;
+ case LZW_FINISHED:
+ return GLZW_OK;
- default:
- return GLZW_INTERNAL_ERROR;
+ default:
+ return GLZW_INTERNAL_ERROR;
}
}
/* -END- GIF LZW encoder. */
int
-ImagingGifEncode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) {
- UINT8* ptr;
- UINT8* sub_block_ptr;
- UINT8* sub_block_limit;
- UINT8* buf_limit;
- GIFENCODERSTATE *context = (GIFENCODERSTATE*) state->context;
+ImagingGifEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
+ UINT8 *ptr;
+ UINT8 *sub_block_ptr;
+ UINT8 *sub_block_limit;
+ UINT8 *buf_limit;
+ GIFENCODERSTATE *context = (GIFENCODERSTATE *)state->context;
int r;
UINT32 in_avail, in_used;
@@ -278,9 +287,9 @@ ImagingGifEncode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) {
return ptr - buf;
}
sub_block_ptr = ptr;
- sub_block_limit = sub_block_ptr +
- (256 < buf_limit - sub_block_ptr ?
- 256 : buf_limit - sub_block_ptr);
+ sub_block_limit =
+ sub_block_ptr +
+ (256 < buf_limit - sub_block_ptr ? 256 : buf_limit - sub_block_ptr);
*ptr++ = 0;
}
@@ -301,9 +310,9 @@ ImagingGifEncode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) {
/* get another line of data */
state->shuffle(
state->buffer,
- (UINT8*) im->image[state->y + state->yoff] +
- state->xoff * im->pixelsize, state->xsize
- );
+ (UINT8 *)im->image[state->y + state->yoff] +
+ state->xoff * im->pixelsize,
+ state->xsize);
state->x = 0;
/* step forward, according to the interlace settings */
@@ -331,10 +340,15 @@ ImagingGifEncode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) {
}
}
- in_avail = state->xsize - state->x; /* bytes left in line */
+ in_avail = state->xsize - state->x; /* bytes left in line */
out_avail = sub_block_limit - ptr; /* bytes left in sub-block */
- r = glzwe(context, &state->buffer[state->x], ptr, &in_avail,
- &out_avail, state->state == FINISH);
+ r = glzwe(
+ context,
+ &state->buffer[state->x],
+ ptr,
+ &in_avail,
+ &out_avail,
+ state->state == FINISH);
out_used = sub_block_limit - ptr - out_avail;
*sub_block_ptr += out_used;
ptr += out_used;
diff --git a/src/libImaging/Imaging.h b/src/libImaging/Imaging.h
index afcd2229bde..1f2c03e934f 100644
--- a/src/libImaging/Imaging.h
+++ b/src/libImaging/Imaging.h
@@ -108,15 +108,15 @@ struct ImagingMemoryInstance {
#define IMAGING_PIXEL_1(im, x, y) ((im)->image8[(y)][(x)])
#define IMAGING_PIXEL_L(im, x, y) ((im)->image8[(y)][(x)])
-#define IMAGING_PIXEL_LA(im, x, y) ((im)->image[(y)][(x)*4])
+#define IMAGING_PIXEL_LA(im, x, y) ((im)->image[(y)][(x) * 4])
#define IMAGING_PIXEL_P(im, x, y) ((im)->image8[(y)][(x)])
-#define IMAGING_PIXEL_PA(im, x, y) ((im)->image[(y)][(x)*4])
+#define IMAGING_PIXEL_PA(im, x, y) ((im)->image[(y)][(x) * 4])
#define IMAGING_PIXEL_I(im, x, y) ((im)->image32[(y)][(x)])
#define IMAGING_PIXEL_F(im, x, y) (((FLOAT32 *)(im)->image32[y])[x])
-#define IMAGING_PIXEL_RGB(im, x, y) ((im)->image[(y)][(x)*4])
-#define IMAGING_PIXEL_RGBA(im, x, y) ((im)->image[(y)][(x)*4])
-#define IMAGING_PIXEL_CMYK(im, x, y) ((im)->image[(y)][(x)*4])
-#define IMAGING_PIXEL_YCbCr(im, x, y) ((im)->image[(y)][(x)*4])
+#define IMAGING_PIXEL_RGB(im, x, y) ((im)->image[(y)][(x) * 4])
+#define IMAGING_PIXEL_RGBA(im, x, y) ((im)->image[(y)][(x) * 4])
+#define IMAGING_PIXEL_CMYK(im, x, y) ((im)->image[(y)][(x) * 4])
+#define IMAGING_PIXEL_YCbCr(im, x, y) ((im)->image[(y)][(x) * 4])
#define IMAGING_PIXEL_UINT8(im, x, y) ((im)->image8[(y)][(x)])
#define IMAGING_PIXEL_INT32(im, x, y) ((im)->image32[(y)][(x)])
@@ -161,7 +161,7 @@ typedef struct ImagingMemoryArena {
int stats_reallocated_blocks; /* Number of blocks which were actually reallocated
after retrieving */
int stats_freed_blocks; /* Number of freed blocks */
-} * ImagingMemoryArena;
+} *ImagingMemoryArena;
/* Objects */
/* ------- */
@@ -309,7 +309,8 @@ ImagingFlipLeftRight(Imaging imOut, Imaging imIn);
extern Imaging
ImagingFlipTopBottom(Imaging imOut, Imaging imIn);
extern Imaging
-ImagingGaussianBlur(Imaging imOut, Imaging imIn, float xradius, float yradius, int passes);
+ImagingGaussianBlur(
+ Imaging imOut, Imaging imIn, float xradius, float yradius, int passes);
extern Imaging
ImagingGetBand(Imaging im, int band);
extern Imaging
@@ -487,7 +488,8 @@ ImagingDrawPieslice(
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);
+ImagingDrawPolygon(
+ Imaging im, int points, int *xy, const void *ink, int fill, int width, int op);
extern int
ImagingDrawRectangle(
Imaging im,
diff --git a/src/libImaging/ImagingUtils.h b/src/libImaging/ImagingUtils.h
index 0c0c1eda917..714458ad02a 100644
--- a/src/libImaging/ImagingUtils.h
+++ b/src/libImaging/ImagingUtils.h
@@ -21,7 +21,7 @@
#define DIV255(a, tmp) (tmp = (a) + 128, SHIFTFORDIV255(tmp))
-#define BLEND(mask, in1, in2, tmp1) DIV255(in1 *(255 - mask) + in2 * mask, tmp1)
+#define BLEND(mask, in1, in2, tmp1) DIV255(in1 * (255 - mask) + in2 * mask, tmp1)
#define PREBLEND(mask, in1, in2, tmp1) (MULDIV255(in1, (255 - mask), tmp1) + in2)
diff --git a/src/libImaging/Jpeg2KDecode.c b/src/libImaging/Jpeg2KDecode.c
index 13f363422da..dd066c10b7a 100644
--- a/src/libImaging/Jpeg2KDecode.c
+++ b/src/libImaging/Jpeg2KDecode.c
@@ -183,9 +183,9 @@ j2ku_gray_i(
UINT16 *row = (UINT16 *)im->image[y0 + y] + x0;
for (x = 0; x < w; ++x) {
UINT16 pixel = j2ku_shift(offset + *data++, shift);
- #ifdef WORDS_BIGENDIAN
- pixel = (pixel >> 8) | (pixel << 8);
- #endif
+#ifdef WORDS_BIGENDIAN
+ pixel = (pixel >> 8) | (pixel << 8);
+#endif
*row++ = pixel;
}
}
@@ -632,6 +632,7 @@ static const struct j2k_decode_unpacker j2k_unpackers[] = {
{"RGBA", OPJ_CLRSPC_SYCC, 3, 1, j2ku_sycc_rgb},
{"RGBA", OPJ_CLRSPC_SRGB, 4, 1, j2ku_srgba_rgba},
{"RGBA", OPJ_CLRSPC_SYCC, 4, 1, j2ku_sycca_rgba},
+ {"CMYK", OPJ_CLRSPC_CMYK, 4, 1, j2ku_srgba_rgba},
};
/* -------------------------------------------------------------------- */
@@ -777,7 +778,7 @@ j2k_decode_entry(Imaging im, ImagingCodecState state) {
color_space = OPJ_CLRSPC_SYCC;
break;
}
- break;
+ break;
}
}
@@ -863,7 +864,7 @@ j2k_decode_entry(Imaging im, ImagingCodecState state) {
a, and then a malicious file could have a smaller tile_bytes
*/
- for (n=0; n < tile_info.nb_comps; n++) {
+ for (n = 0; n < tile_info.nb_comps; n++) {
// see csize /acsize calcs
int csize = (image->comps[n].prec + 7) >> 3;
csize = (csize == 3) ? 4 : csize;
diff --git a/src/libImaging/Jpeg2KEncode.c b/src/libImaging/Jpeg2KEncode.c
index 3295373fd64..7f1aeaddb9b 100644
--- a/src/libImaging/Jpeg2KEncode.c
+++ b/src/libImaging/Jpeg2KEncode.c
@@ -383,8 +383,7 @@ j2k_encode_entry(Imaging im, ImagingCodecState state) {
float *pq;
if (len > 0) {
- if ((size_t)len >
- sizeof(params.tcp_rates) / sizeof(params.tcp_rates[0])) {
+ if ((size_t)len > sizeof(params.tcp_rates) / sizeof(params.tcp_rates[0])) {
len = sizeof(params.tcp_rates) / sizeof(params.tcp_rates[0]);
}
@@ -464,7 +463,8 @@ j2k_encode_entry(Imaging im, ImagingCodecState state) {
}
if (!context->num_resolutions) {
- while (tile_width < (1U << (params.numresolution - 1U)) || tile_height < (1U << (params.numresolution - 1U))) {
+ while (tile_width < (1U << (params.numresolution - 1U)) ||
+ tile_height < (1U << (params.numresolution - 1U))) {
params.numresolution -= 1;
}
}
diff --git a/src/libImaging/JpegEncode.c b/src/libImaging/JpegEncode.c
index 00f3d5f74db..ba8353c2d86 100644
--- a/src/libImaging/JpegEncode.c
+++ b/src/libImaging/JpegEncode.c
@@ -145,8 +145,8 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
case JCS_EXT_RGBX:
#endif
switch (context->subsampling) {
- case -1: /* Default */
- case 0: /* No subsampling */
+ case -1: /* Default */
+ case 0: /* No subsampling */
break;
default:
/* Would subsample the green and blue
@@ -305,7 +305,11 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
case 4:
if (context->comment) {
- jpeg_write_marker(&context->cinfo, JPEG_COM, (unsigned char *)context->comment, context->comment_size);
+ jpeg_write_marker(
+ &context->cinfo,
+ JPEG_COM,
+ (unsigned char *)context->comment,
+ context->comment_size);
}
state->state++;
diff --git a/src/libImaging/Matrix.c b/src/libImaging/Matrix.c
index 182eb62a7e6..ec7f4d93e06 100644
--- a/src/libImaging/Matrix.c
+++ b/src/libImaging/Matrix.c
@@ -24,11 +24,11 @@ ImagingConvertMatrix(Imaging im, const char *mode, float m[]) {
ImagingSectionCookie cookie;
/* Assume there's enough data in the buffer */
- if (!im) {
+ if (!im || im->bands != 3) {
return (Imaging)ImagingError_ModeError();
}
- if (strcmp(mode, "L") == 0 && im->bands == 3) {
+ if (strcmp(mode, "L") == 0) {
imOut = ImagingNewDirty("L", im->xsize, im->ysize);
if (!imOut) {
return NULL;
@@ -47,7 +47,7 @@ ImagingConvertMatrix(Imaging im, const char *mode, float m[]) {
}
ImagingSectionLeave(&cookie);
- } else if (strlen(mode) == 3 && im->bands == 3) {
+ } else if (strlen(mode) == 3) {
imOut = ImagingNewDirty(mode, im->xsize, im->ysize);
if (!imOut) {
return NULL;
diff --git a/src/libImaging/Pack.c b/src/libImaging/Pack.c
index d47344245cc..f3b71421595 100644
--- a/src/libImaging/Pack.c
+++ b/src/libImaging/Pack.c
@@ -339,7 +339,7 @@ ImagingPackXBGR(UINT8 *out, const UINT8 *in, int pixels) {
void
ImagingPackBGRA(UINT8 *out, const UINT8 *in, int pixels) {
int i;
- /* BGRX, reversed bytes with right padding */
+ /* BGRA, reversed bytes with right alpha */
for (i = 0; i < pixels; i++) {
out[0] = in[B];
out[1] = in[G];
@@ -353,7 +353,7 @@ ImagingPackBGRA(UINT8 *out, const UINT8 *in, int pixels) {
void
ImagingPackABGR(UINT8 *out, const UINT8 *in, int pixels) {
int i;
- /* XBGR, reversed bytes with left padding */
+ /* ABGR, reversed bytes with left alpha */
for (i = 0; i < pixels; i++) {
out[0] = in[A];
out[1] = in[B];
diff --git a/src/libImaging/Paste.c b/src/libImaging/Paste.c
index 6684b11efe3..dc67cb41d2f 100644
--- a/src/libImaging/Paste.c
+++ b/src/libImaging/Paste.c
@@ -65,15 +65,32 @@ paste_mask_1(
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++;
}
}
@@ -415,15 +432,16 @@ fill_mask_L(
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);
}
@@ -432,11 +450,10 @@ fill_mask_L(
}
} else {
- int alpha_channel = strcmp(imOut->mode, "RGBa") == 0 ||
- strcmp(imOut->mode, "RGBA") == 0 ||
- strcmp(imOut->mode, "La") == 0 ||
- strcmp(imOut->mode, "LA") == 0 ||
- strcmp(imOut->mode, "PA") == 0;
+ int alpha_channel =
+ strcmp(imOut->mode, "RGBa") == 0 || strcmp(imOut->mode, "RGBA") == 0 ||
+ strcmp(imOut->mode, "La") == 0 || strcmp(imOut->mode, "LA") == 0 ||
+ strcmp(imOut->mode, "PA") == 0;
for (y = 0; y < ysize; y++) {
UINT8 *out = (UINT8 *)imOut->image[y + dy] + dx * pixelsize;
UINT8 *mask = (UINT8 *)imMask->image[y + sy] + sx;
diff --git a/src/libImaging/Point.c b/src/libImaging/Point.c
index 8883578cbff..dd06f3940d1 100644
--- a/src/libImaging/Point.c
+++ b/src/libImaging/Point.c
@@ -134,7 +134,7 @@ ImagingPoint(Imaging imIn, const char *mode, const void *table) {
ImagingSectionCookie cookie;
Imaging imOut;
im_point_context context;
- void (*point)(Imaging imIn, Imaging imOut, im_point_context * context);
+ void (*point)(Imaging imIn, Imaging imOut, im_point_context *context);
if (!imIn) {
return (Imaging)ImagingError_ModeError();
diff --git a/src/libImaging/Quant.c b/src/libImaging/Quant.c
index 2582830c4a4..cdc614536da 100644
--- a/src/libImaging/Quant.c
+++ b/src/libImaging/Quant.c
@@ -260,8 +260,7 @@ mergesort_pixels(PixelList *head, int i) {
return head;
}
for (c = t = head; c && t;
- c = c->next[i], t = (t->next[i]) ? t->next[i]->next[i] : NULL)
- ;
+ c = c->next[i], t = (t->next[i]) ? t->next[i]->next[i] : NULL);
if (c) {
if (c->prev[i]) {
c->prev[i]->next[i] = NULL;
@@ -354,12 +353,10 @@ splitlists(
for (_i = 0; _i < 3; _i++) {
for (_nextCount[_i] = 0, _nextTest = h[_i];
_nextTest && _nextTest->next[_i];
- _nextTest = _nextTest->next[_i], _nextCount[_i]++)
- ;
+ _nextTest = _nextTest->next[_i], _nextCount[_i]++);
for (_prevCount[_i] = 0, _prevTest = t[_i];
_prevTest && _prevTest->prev[_i];
- _prevTest = _prevTest->prev[_i], _prevCount[_i]++)
- ;
+ _prevTest = _prevTest->prev[_i], _prevCount[_i]++);
if (_nextTest != t[_i]) {
printf("next-list of axis %d does not end at tail\n", _i);
exit(1);
@@ -368,10 +365,8 @@ splitlists(
printf("prev-list of axis %d does not end at head\n", _i);
exit(1);
}
- for (; _nextTest && _nextTest->prev[_i]; _nextTest = _nextTest->prev[_i])
- ;
- for (; _prevTest && _prevTest->next[_i]; _prevTest = _prevTest->next[_i])
- ;
+ for (; _nextTest && _nextTest->prev[_i]; _nextTest = _nextTest->prev[_i]);
+ for (; _prevTest && _prevTest->next[_i]; _prevTest = _prevTest->next[_i]);
if (_nextTest != h[_i]) {
printf("next-list of axis %d does not loop back to head\n", _i);
exit(1);
@@ -548,22 +543,18 @@ split(BoxNode *node) {
for (_i = 0; _i < 3; _i++) {
for (_nextCount[_i] = 0, _nextTest = node->head[_i];
_nextTest && _nextTest->next[_i];
- _nextTest = _nextTest->next[_i], _nextCount[_i]++)
- ;
+ _nextTest = _nextTest->next[_i], _nextCount[_i]++);
for (_prevCount[_i] = 0, _prevTest = node->tail[_i];
_prevTest && _prevTest->prev[_i];
- _prevTest = _prevTest->prev[_i], _prevCount[_i]++)
- ;
+ _prevTest = _prevTest->prev[_i], _prevCount[_i]++);
if (_nextTest != node->tail[_i]) {
printf("next-list of axis %d does not end at tail\n", _i);
}
if (_prevTest != node->head[_i]) {
printf("prev-list of axis %d does not end at head\n", _i);
}
- for (; _nextTest && _nextTest->prev[_i]; _nextTest = _nextTest->prev[_i])
- ;
- for (; _prevTest && _prevTest->next[_i]; _prevTest = _prevTest->next[_i])
- ;
+ for (; _nextTest && _nextTest->prev[_i]; _nextTest = _nextTest->prev[_i]);
+ for (; _prevTest && _prevTest->next[_i]; _prevTest = _prevTest->next[_i]);
if (_nextTest != node->head[_i]) {
printf("next-list of axis %d does not loop back to head\n", _i);
}
@@ -668,8 +659,7 @@ median_cut(PixelList *hl[3], uint32_t imPixelCount, int nPixels) {
return NULL;
}
for (i = 0; i < 3; i++) {
- for (tl[i] = hl[i]; tl[i] && tl[i]->next[i]; tl[i] = tl[i]->next[i])
- ;
+ for (tl[i] = hl[i]; tl[i] && tl[i]->next[i]; tl[i] = tl[i]->next[i]);
root->head[i] = hl[i];
root->tail[i] = tl[i];
}
@@ -832,16 +822,9 @@ build_distance_tables(
}
for (i = 0; i < nEntries; i++) {
for (j = 0; j < nEntries; j++) {
- dwi[j] = (DistanceWithIndex){
- &(avgDist[i * nEntries + j]),
- j
- };
+ dwi[j] = (DistanceWithIndex){&(avgDist[i * nEntries + j]), j};
}
- qsort(
- dwi,
- nEntries,
- sizeof(DistanceWithIndex),
- _distance_index_cmp);
+ qsort(dwi, nEntries, sizeof(DistanceWithIndex), _distance_index_cmp);
for (j = 0; j < nEntries; j++) {
avgDistSortKey[i * nEntries + j] = dwi[j].distance;
}
@@ -1213,7 +1196,7 @@ k_means(
compute_palette_from_quantized_pixels(
pixelData, nPixels, paletteData, nPaletteEntries, avg, count, qp);
if (!build_distance_tables(
- avgDist, avgDistSortKey, paletteData, nPaletteEntries)) {
+ avgDist, avgDistSortKey, paletteData, nPaletteEntries)) {
goto error_3;
}
built = 1;
@@ -1452,15 +1435,17 @@ quantize(
hashtable_insert(h2, pixelData[i], bestmatch);
}
if (qp[i] != bestmatch) {
- printf ("discrepancy in matching algorithms pixel %d [%d %d] %f %f\n",
- i,qp[i],bestmatch,
- sqrt((double)(_SQR(pixelData[i].c.r-p[qp[i]].c.r)+
- _SQR(pixelData[i].c.g-p[qp[i]].c.g)+
- _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)))
- );
+ printf(
+ "discrepancy in matching algorithms pixel %d [%d %d] %f %f\n",
+ i,
+ qp[i],
+ bestmatch,
+ sqrt((double)(_SQR(pixelData[i].c.r - p[qp[i]].c.r) +
+ _SQR(pixelData[i].c.g - p[qp[i]].c.g) +
+ _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))));
}
}
hashtable_free(h2);
diff --git a/src/libImaging/QuantOctree.c b/src/libImaging/QuantOctree.c
index 5e79bce358a..1331a30ad45 100644
--- a/src/libImaging/QuantOctree.c
+++ b/src/libImaging/QuantOctree.c
@@ -38,7 +38,7 @@ typedef struct _ColorBucket {
uint64_t g;
uint64_t b;
uint64_t a;
-} * ColorBucket;
+} *ColorBucket;
typedef struct _ColorCube {
unsigned int rBits, gBits, bBits, aBits;
@@ -47,7 +47,7 @@ typedef struct _ColorCube {
unsigned long size;
ColorBucket buckets;
-} * ColorCube;
+} *ColorCube;
#define MAX(a, b) (a) > (b) ? (a) : (b)
diff --git a/src/libImaging/Reduce.c b/src/libImaging/Reduce.c
index 60928d2bc36..61566f0c506 100644
--- a/src/libImaging/Reduce.c
+++ b/src/libImaging/Reduce.c
@@ -2,7 +2,7 @@
#include
-#define ROUND_UP(f) ((int)((f) >= 0.0 ? (f) + 0.5F : (f)-0.5F))
+#define ROUND_UP(f) ((int)((f) >= 0.0 ? (f) + 0.5F : (f) - 0.5F))
UINT32
division_UINT32(int divider, int result_bits) {
diff --git a/src/libImaging/Resample.c b/src/libImaging/Resample.c
index cf79d8a4e4d..59c27b3f421 100644
--- a/src/libImaging/Resample.c
+++ b/src/libImaging/Resample.c
@@ -2,7 +2,7 @@
#include
-#define ROUND_UP(f) ((int)((f) >= 0.0 ? (f) + 0.5F : (f)-0.5F))
+#define ROUND_UP(f) ((int)((f) >= 0.0 ? (f) + 0.5F : (f) - 0.5F))
struct filter {
double (*filter)(double x);
diff --git a/src/libImaging/SgiRleDecode.c b/src/libImaging/SgiRleDecode.c
index 4eef44ba510..89dedb5252f 100644
--- a/src/libImaging/SgiRleDecode.c
+++ b/src/libImaging/SgiRleDecode.c
@@ -113,7 +113,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) {
+expandrow2(
+ UINT8 *dest, const UINT8 *src, int n, int z, int xsize, UINT8 *end_of_buffer) {
UINT8 pixel, count;
int x = 0;
@@ -197,7 +198,6 @@ ImagingSgiRleDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t
return -1;
}
-
/* decoder initialization */
state->count = 0;
state->y = 0;
@@ -252,7 +252,7 @@ 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 +260,7 @@ 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;
@@ -268,7 +268,6 @@ ImagingSgiRleDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t
} else if (status == 1) {
goto sgi_finish_decode;
}
-
}
/* store decompressed data in image */
diff --git a/src/libImaging/Storage.c b/src/libImaging/Storage.c
index b1b03c515ad..b27195a3587 100644
--- a/src/libImaging/Storage.c
+++ b/src/libImaging/Storage.c
@@ -418,9 +418,8 @@ ImagingAllocateArray(Imaging im, int dirty, int block_size) {
}
im->blocks[current_block] = block;
/* Bulletproof code from libc _int_memalign */
- aligned_ptr = (char *)(
- ((size_t) (block.ptr + arena->alignment - 1)) &
- -((Py_ssize_t) arena->alignment));
+ aligned_ptr = (char *)(((size_t)(block.ptr + arena->alignment - 1)) &
+ -((Py_ssize_t)arena->alignment));
}
im->image[y] = aligned_ptr + aligned_linesize * line_in_block;
diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c
index e3b81590ec2..abffdeabcd5 100644
--- a/src/libImaging/TiffDecode.c
+++ b/src/libImaging/TiffDecode.c
@@ -60,7 +60,11 @@ _tiffReadProc(thandle_t hdata, tdata_t buf, tsize_t size) {
dump_state(state);
if (state->loc > state->eof) {
- TIFFError("_tiffReadProc", "Invalid Read at loc %" PRIu64 ", eof: %" PRIu64, state->loc, state->eof);
+ TIFFError(
+ "_tiffReadProc",
+ "Invalid Read at loc %" PRIu64 ", eof: %" PRIu64,
+ state->loc,
+ state->eof);
return 0;
}
to_read = min(size, min(state->size, (tsize_t)state->eof) - (tsize_t)state->loc);
@@ -217,7 +221,12 @@ ImagingLibTiffInit(ImagingCodecState state, int fp, uint32_t offset) {
}
int
-_pickUnpackers(Imaging im, ImagingCodecState state, TIFF *tiff, uint16_t planarconfig, ImagingShuffler *unpackers) {
+_pickUnpackers(
+ Imaging im,
+ ImagingCodecState state,
+ TIFF *tiff,
+ uint16_t planarconfig,
+ 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;
@@ -232,10 +241,14 @@ _pickUnpackers(Imaging im, ImagingCodecState state, TIFF *tiff, uint16_t planarc
// We'll pick appropriate set of unpackers depending on planar_configuration
// It does not matter if data is RGB(A), CMYK or LUV really,
// we just copy it plane by plane
- unpackers[0] = ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "R;16N" : "R", NULL);
- unpackers[1] = ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "G;16N" : "G", NULL);
- unpackers[2] = ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "B;16N" : "B", NULL);
- unpackers[3] = ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "A;16N" : "A", NULL);
+ unpackers[0] =
+ ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "R;16N" : "R", NULL);
+ unpackers[1] =
+ ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "G;16N" : "G", NULL);
+ unpackers[2] =
+ ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "B;16N" : "B", NULL);
+ unpackers[3] =
+ ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "A;16N" : "A", NULL);
return im->bands;
} else {
@@ -247,10 +260,10 @@ _pickUnpackers(Imaging im, ImagingCodecState state, TIFF *tiff, uint16_t planarc
int
_decodeAsRGBA(Imaging im, ImagingCodecState state, TIFF *tiff) {
- // To avoid dealing with YCbCr subsampling and other complications, let libtiff handle it
- // Use a TIFFRGBAImage wrapping the tiff image, and let libtiff handle
- // all of the conversion. Metadata read from the TIFFRGBAImage could
- // be different from the metadata that the base tiff returns.
+ // To avoid dealing with YCbCr subsampling and other complications, let libtiff
+ // handle it Use a TIFFRGBAImage wrapping the tiff image, and let libtiff handle all
+ // of the conversion. Metadata read from the TIFFRGBAImage could be different from
+ // the metadata that the base tiff returns.
INT32 current_row;
UINT8 *new_data;
@@ -259,17 +272,16 @@ _decodeAsRGBA(Imaging im, ImagingCodecState state, TIFF *tiff) {
TIFFRGBAImage img;
char emsg[1024] = "";
- // Since using TIFFRGBAImage* functions, we can read whole tiff into rastrr in one call
- // Let's select smaller block size. Multiplying image width by (tile length OR rows per strip)
- // gives us manageable block size in pixels
+ // Since using TIFFRGBAImage* functions, we can read whole tiff into rastrr in one
+ // call Let's select smaller block size. Multiplying image width by (tile length OR
+ // rows per strip) gives us manageable block size in pixels
if (TIFFIsTiled(tiff)) {
ret = TIFFGetFieldDefaulted(tiff, TIFFTAG_TILELENGTH, &rows_per_block);
- }
- else {
+ } else {
ret = TIFFGetFieldDefaulted(tiff, TIFFTAG_ROWSPERSTRIP, &rows_per_block);
}
- if (ret != 1 || rows_per_block==(UINT32)(-1)) {
+ if (ret != 1 || rows_per_block == (UINT32)(-1)) {
rows_per_block = state->ysize;
}
@@ -357,7 +369,12 @@ _decodeAsRGBA(Imaging im, ImagingCodecState state, TIFF *tiff) {
}
int
-_decodeTile(Imaging im, ImagingCodecState state, TIFF *tiff, int planes, ImagingShuffler *unpackers) {
+_decodeTile(
+ Imaging im,
+ ImagingCodecState state,
+ TIFF *tiff,
+ int planes,
+ 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;
@@ -396,8 +413,8 @@ _decodeTile(Imaging im, ImagingCodecState state, TIFF *tiff, int planes, Imaging
if (tile_bytes_size > ((tile_length * state->bits / planes + 7) / 8) * tile_width) {
// If the tile size as expected by LibTiff isn't what we're expecting, abort.
- // man: TIFFTileSize returns the equivalent size for a tile of data as it would be returned in a
- // call to TIFFReadTile ...
+ // man: TIFFTileSize returns the equivalent size for a tile of data as it
+ // would be returned in a call to TIFFReadTile ...
state->errcode = IMAGING_CODEC_BROKEN;
return -1;
}
@@ -428,19 +445,24 @@ _decodeTile(Imaging im, ImagingCodecState state, TIFF *tiff, int planes, Imaging
TRACE(("Read tile at %dx%d; \n\n", x, y));
- current_tile_width = min((INT32) tile_width, state->xsize - x);
- current_tile_length = min((INT32) tile_length, state->ysize - y);
+ current_tile_width = min((INT32)tile_width, state->xsize - x);
+ current_tile_length = min((INT32)tile_length, state->ysize - y);
// iterate over each line in the tile and stuff data into image
for (tile_y = 0; tile_y < current_tile_length; tile_y++) {
- TRACE(("Writing tile data at %dx%d using tile_width: %d; \n", tile_y + y, x, current_tile_width));
+ TRACE(
+ ("Writing tile data at %dx%d using tile_width: %d; \n",
+ tile_y + y,
+ x,
+ 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], ((UINT8 *)bbb)[2], ((UINT8 *)bbb)[3]));
+ // TRACE(("chars: %x%x%x%x\n", ((UINT8 *)bbb)[0], ((UINT8 *)bbb)[1],
+ // ((UINT8 *)bbb)[2], ((UINT8 *)bbb)[3]));
- shuffler((UINT8*) im->image[tile_y + y] + x * im->pixelsize,
- state->buffer + tile_y * row_byte_size,
- current_tile_width
- );
+ shuffler(
+ (UINT8 *)im->image[tile_y + y] + x * im->pixelsize,
+ state->buffer + tile_y * row_byte_size,
+ current_tile_width);
}
}
}
@@ -450,7 +472,12 @@ _decodeTile(Imaging im, ImagingCodecState state, TIFF *tiff, int planes, Imaging
}
int
-_decodeStrip(Imaging im, ImagingCodecState state, TIFF *tiff, int planes, ImagingShuffler *unpackers) {
+_decodeStrip(
+ Imaging im,
+ ImagingCodecState state,
+ TIFF *tiff,
+ int planes,
+ ImagingShuffler *unpackers) {
INT32 strip_row = 0;
UINT8 *new_data;
UINT32 rows_per_strip;
@@ -458,7 +485,7 @@ _decodeStrip(Imaging im, ImagingCodecState state, TIFF *tiff, int planes, Imagin
tsize_t strip_size, row_byte_size, unpacker_row_byte_size;
ret = TIFFGetField(tiff, TIFFTAG_ROWSPERSTRIP, &rows_per_strip);
- if (ret != 1 || rows_per_strip==(UINT32)(-1)) {
+ if (ret != 1 || rows_per_strip == (UINT32)(-1)) {
rows_per_strip = state->ysize;
}
@@ -478,8 +505,8 @@ _decodeStrip(Imaging im, ImagingCodecState state, TIFF *tiff, int planes, Imagin
unpacker_row_byte_size = (state->xsize * state->bits / planes + 7) / 8;
if (strip_size > (unpacker_row_byte_size * rows_per_strip)) {
// If the strip size as expected by LibTiff isn't what we're expecting, abort.
- // man: TIFFStripSize returns the equivalent size for a strip of data as it would be returned in a
- // call to TIFFReadEncodedStrip ...
+ // man: TIFFStripSize returns the equivalent size for a strip of data as it
+ // would be returned in a call to TIFFReadEncodedStrip ...
state->errcode = IMAGING_CODEC_BROKEN;
return -1;
}
@@ -513,8 +540,13 @@ _decodeStrip(Imaging im, ImagingCodecState state, TIFF *tiff, int planes, Imagin
int plane;
for (plane = 0; plane < planes; plane++) {
ImagingShuffler shuffler = unpackers[plane];
- if (TIFFReadEncodedStrip(tiff, TIFFComputeStrip(tiff, state->y, plane), (tdata_t)state->buffer, strip_size) == -1) {
- TRACE(("Decode Error, strip %d\n", TIFFComputeStrip(tiff, state->y, 0)));
+ if (TIFFReadEncodedStrip(
+ tiff,
+ TIFFComputeStrip(tiff, state->y, plane),
+ (tdata_t)state->buffer,
+ strip_size) == -1) {
+ TRACE(
+ ("Decode Error, strip %d\n", TIFFComputeStrip(tiff, state->y, 0)));
state->errcode = IMAGING_CODEC_BROKEN;
return -1;
}
@@ -523,16 +555,17 @@ _decodeStrip(Imaging im, ImagingCodecState state, TIFF *tiff, int planes, Imagin
// iterate over each row in the strip and stuff data into image
for (strip_row = 0;
- strip_row < min((INT32) rows_per_strip, state->ysize - state->y);
+ strip_row < min((INT32)rows_per_strip, state->ysize - state->y);
strip_row++) {
TRACE(("Writing data into line %d ; \n", state->y + strip_row));
- // UINT8 * bbb = state->buffer + strip_row * (state->bytes / rows_per_strip);
- // TRACE(("chars: %x %x %x %x\n", ((UINT8 *)bbb)[0], ((UINT8 *)bbb)[1], ((UINT8 *)bbb)[2], ((UINT8 *)bbb)[3]));
+ // UINT8 * bbb = state->buffer + strip_row * (state->bytes /
+ // rows_per_strip); TRACE(("chars: %x %x %x %x\n", ((UINT8 *)bbb)[0],
+ // ((UINT8 *)bbb)[1], ((UINT8 *)bbb)[2], ((UINT8 *)bbb)[3]));
shuffler(
- (UINT8*) im->image[state->y + state->yoff + strip_row] +
- state->xoff * im->pixelsize,
+ (UINT8 *)im->image[state->y + state->yoff + strip_row] +
+ state->xoff * im->pixelsize,
state->buffer + strip_row * row_byte_size,
state->xsize);
}
@@ -666,7 +699,6 @@ ImagingLibTiffDecode(
goto decode_err;
}
-
TIFFGetField(tiff, TIFFTAG_PHOTOMETRIC, &photometric);
TIFFGetField(tiff, TIFFTAG_COMPRESSION, &compression);
TIFFGetFieldDefaulted(tiff, TIFFTAG_PLANARCONFIG, &planarconfig);
@@ -675,16 +707,17 @@ ImagingLibTiffDecode(
// Let LibTiff read them as RGBA
readAsRGBA = photometric == PHOTOMETRIC_YCBCR;
- if (readAsRGBA && compression == COMPRESSION_JPEG && planarconfig == PLANARCONFIG_CONTIG) {
- // If using new JPEG compression, let libjpeg do RGB conversion for performance reasons
+ if (readAsRGBA && compression == COMPRESSION_JPEG &&
+ planarconfig == PLANARCONFIG_CONTIG) {
+ // If using new JPEG compression, let libjpeg do RGB conversion for performance
+ // reasons
TIFFSetField(tiff, TIFFTAG_JPEGCOLORMODE, JPEGCOLORMODE_RGB);
readAsRGBA = 0;
}
if (readAsRGBA) {
_decodeAsRGBA(im, state, tiff);
- }
- else {
+ } else {
planes = _pickUnpackers(im, state, tiff, planarconfig, unpackers);
if (planes <= 0) {
goto decode_err;
@@ -692,8 +725,7 @@ ImagingLibTiffDecode(
if (TIFFIsTiled(tiff)) {
_decodeTile(im, state, tiff, planes, unpackers);
- }
- else {
+ } else {
_decodeStrip(im, state, tiff, planes, unpackers);
}
@@ -702,20 +734,20 @@ ImagingLibTiffDecode(
// so we have to convert it to RGBA
if (planes > 3 && strcmp(im->mode, "RGBA") == 0) {
uint16_t extrasamples;
- uint16_t* sampleinfo;
+ uint16_t *sampleinfo;
ImagingShuffler shuffle;
INT32 y;
- TIFFGetFieldDefaulted(tiff, TIFFTAG_EXTRASAMPLES, &extrasamples, &sampleinfo);
+ TIFFGetFieldDefaulted(
+ tiff, TIFFTAG_EXTRASAMPLES, &extrasamples, &sampleinfo);
- if (extrasamples >= 1 &&
- (sampleinfo[0] == EXTRASAMPLE_UNSPECIFIED || sampleinfo[0] == EXTRASAMPLE_ASSOCALPHA)
- ) {
+ if (extrasamples >= 1 && (sampleinfo[0] == EXTRASAMPLE_UNSPECIFIED ||
+ sampleinfo[0] == EXTRASAMPLE_ASSOCALPHA)) {
shuffle = ImagingFindUnpacker("RGBA", "RGBa", NULL);
for (y = state->yoff; y < state->ysize; y++) {
- UINT8* ptr = (UINT8*) im->image[y + state->yoff] +
- state->xoff * im->pixelsize;
+ UINT8 *ptr = (UINT8 *)im->image[y + state->yoff] +
+ state->xoff * im->pixelsize;
shuffle(ptr, ptr, state->xsize);
}
}
@@ -723,7 +755,7 @@ ImagingLibTiffDecode(
}
}
- decode_err:
+decode_err:
// TIFFClose in libtiff calls tif_closeproc and TIFFCleanup
if (clientstate->fp) {
// Pillow will manage the closing of the file rather than libtiff
@@ -973,7 +1005,8 @@ ImagingLibTiffEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int byt
}
if (state->state == 1 && !clientstate->fp) {
- int read = (int)_tiffReadProc(clientstate, (tdata_t)buffer, (tsize_t)bytes);
+ int read =
+ (int)_tiffReadProc((thandle_t)clientstate, (tdata_t)buffer, (tsize_t)bytes);
TRACE(
("Buffer: %p: %c%c%c%c\n",
buffer,
diff --git a/src/libImaging/TiffDecode.h b/src/libImaging/TiffDecode.h
index 02454ba0396..212b7dee6b0 100644
--- a/src/libImaging/TiffDecode.h
+++ b/src/libImaging/TiffDecode.h
@@ -30,7 +30,7 @@ typedef struct {
* Should be uint32 for libtiff 3.9.x
* uint64 for libtiff 4.0.x
*/
- TIFF *tiff; /* Used in write */
+ TIFF *tiff; /* Used in write */
toff_t eof;
int flrealloc; /* may we realloc */
} TIFFSTATE;
diff --git a/src/libImaging/Unpack.c b/src/libImaging/Unpack.c
index 6c7d52f58d1..eaa4374e3e9 100644
--- a/src/libImaging/Unpack.c
+++ b/src/libImaging/Unpack.c
@@ -718,6 +718,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;
@@ -790,6 +805,17 @@ ImagingUnpackBGRX(UINT8 *_out, const UINT8 *in, int pixels) {
}
}
+static void
+ImagingUnpackBGXR(UINT8 *_out, const UINT8 *in, int pixels) {
+ int i;
+ for (i = 0; i < pixels; i++) {
+ UINT32 iv = MAKE_UINT32(in[3], in[1], in[0], 255);
+ memcpy(_out, &iv, sizeof(iv));
+ in += 4;
+ _out += 4;
+ }
+}
+
static void
ImagingUnpackXRGB(UINT8 *_out, const UINT8 *in, int pixels) {
int i;
@@ -1090,6 +1116,17 @@ unpackBGRA16B(UINT8 *_out, const UINT8 *in, int pixels) {
}
}
+static void
+unpackBGAR(UINT8 *_out, const UINT8 *in, int pixels) {
+ int i;
+ for (i = 0; i < pixels; i++) {
+ UINT32 iv = MAKE_UINT32(in[3], in[1], in[0], in[2]);
+ memcpy(_out, &iv, sizeof(iv));
+ in += 4;
+ _out += 4;
+ }
+}
+
/* Unpack to "CMYK" image */
static void
@@ -1415,90 +1452,90 @@ band3I(UINT8 *out, const UINT8 *in, int pixels) {
}
static void
-band016B(UINT8* out, const UINT8* in, int pixels)
-{
+band016B(UINT8 *out, const UINT8 *in, int pixels) {
int i;
/* band 0 only, big endian */
for (i = 0; i < pixels; i++) {
out[0] = in[0];
- out += 4; in += 2;
+ out += 4;
+ in += 2;
}
}
static void
-band116B(UINT8* out, const UINT8* in, int pixels)
-{
+band116B(UINT8 *out, const UINT8 *in, int pixels) {
int i;
/* band 1 only, big endian */
for (i = 0; i < pixels; i++) {
out[1] = in[0];
- out += 4; in += 2;
+ out += 4;
+ in += 2;
}
}
static void
-band216B(UINT8* out, const UINT8* in, int pixels)
-{
+band216B(UINT8 *out, const UINT8 *in, int pixels) {
int i;
/* band 2 only, big endian */
for (i = 0; i < pixels; i++) {
out[2] = in[0];
- out += 4; in += 2;
+ out += 4;
+ in += 2;
}
}
static void
-band316B(UINT8* out, const UINT8* in, int pixels)
-{
+band316B(UINT8 *out, const UINT8 *in, int pixels) {
int i;
/* band 3 only, big endian */
for (i = 0; i < pixels; i++) {
out[3] = in[0];
- out += 4; in += 2;
+ out += 4;
+ in += 2;
}
}
static void
-band016L(UINT8* out, const UINT8* in, int pixels)
-{
+band016L(UINT8 *out, const UINT8 *in, int pixels) {
int i;
/* band 0 only, little endian */
for (i = 0; i < pixels; i++) {
out[0] = in[1];
- out += 4; in += 2;
+ out += 4;
+ in += 2;
}
}
static void
-band116L(UINT8* out, const UINT8* in, int pixels)
-{
+band116L(UINT8 *out, const UINT8 *in, int pixels) {
int i;
/* band 1 only, little endian */
for (i = 0; i < pixels; i++) {
out[1] = in[1];
- out += 4; in += 2;
+ out += 4;
+ in += 2;
}
}
static void
-band216L(UINT8* out, const UINT8* in, int pixels)
-{
+band216L(UINT8 *out, const UINT8 *in, int pixels) {
int i;
/* band 2 only, little endian */
for (i = 0; i < pixels; i++) {
out[2] = in[1];
- out += 4; in += 2;
+ out += 4;
+ in += 2;
}
}
static void
-band316L(UINT8* out, const UINT8* in, int pixels)
-{
+band316L(UINT8 *out, const UINT8 *in, int pixels) {
int i;
/* band 3 only, little endian */
for (i = 0; i < pixels; i++) {
out[3] = in[1];
- out += 4; in += 2;
+ out += 4;
+ in += 2;
}
}
@@ -1516,7 +1553,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 */
@@ -1560,6 +1597,7 @@ static struct {
{"P", "P", 8, copy1},
{"P", "P;R", 8, unpackLR},
{"P", "L", 8, copy1},
+ {"P", "PX", 16, unpackL16B},
/* palette w. alpha */
{"PA", "PA", 16, unpackLA},
@@ -1577,13 +1615,18 @@ 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},
+ {"RGB", "BGXR", 32, ImagingUnpackBGXR},
{"RGB", "XRGB", 32, ImagingUnpackXRGB},
{"RGB", "XBGR", 32, ImagingUnpackXBGR},
{"RGB", "YCC;P", 24, ImagingUnpackYCC},
@@ -1618,12 +1661,14 @@ 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},
{"RGBA", "BGRA", 32, unpackBGRA},
{"RGBA", "BGRA;16L", 64, unpackBGRA16L},
{"RGBA", "BGRA;16B", 64, unpackBGRA16B},
+ {"RGBA", "BGAR", 32, unpackBGAR},
{"RGBA", "ARGB", 32, unpackARGB},
{"RGBA", "ABGR", 32, unpackABGR},
{"RGBA", "YCCA;P", 32, ImagingUnpackYCCA},
@@ -1662,7 +1707,6 @@ static struct {
{"RGB", "G;16N", 16, band116L},
{"RGB", "B;16N", 16, band216L},
-
{"RGBA", "R;16N", 16, band016L},
{"RGBA", "G;16N", 16, band116L},
{"RGBA", "B;16N", 16, band216L},
diff --git a/src/path.c b/src/path.c
index cc0698c4d2a..6bc90abed86 100644
--- a/src/path.c
+++ b/src/path.c
@@ -162,24 +162,24 @@ PyPath_Flatten(PyObject *data, double **pxy) {
return -1;
}
-#define assign_item_to_array(op, decref) \
-if (PyFloat_Check(op)) { \
- xy[j++] = PyFloat_AS_DOUBLE(op); \
-} else if (PyLong_Check(op)) { \
- xy[j++] = (float)PyLong_AS_LONG(op); \
-} else if (PyNumber_Check(op)) { \
- xy[j++] = PyFloat_AsDouble(op); \
-} else if (PyArg_ParseTuple(op, "dd", &x, &y)) { \
- xy[j++] = x; \
- xy[j++] = y; \
-} else { \
- PyErr_SetString(PyExc_ValueError, "incorrect coordinate type"); \
- if (decref) { \
- Py_DECREF(op); \
- } \
- free(xy); \
- return -1; \
-}
+#define assign_item_to_array(op, decref) \
+ if (PyFloat_Check(op)) { \
+ xy[j++] = PyFloat_AS_DOUBLE(op); \
+ } else if (PyLong_Check(op)) { \
+ xy[j++] = (float)PyLong_AS_LONG(op); \
+ } else if (PyNumber_Check(op)) { \
+ xy[j++] = PyFloat_AsDouble(op); \
+ } else if (PyArg_ParseTuple(op, "dd", &x, &y)) { \
+ xy[j++] = x; \
+ xy[j++] = y; \
+ } else { \
+ PyErr_SetString(PyExc_ValueError, "incorrect coordinate type"); \
+ if (decref) { \
+ Py_DECREF(op); \
+ } \
+ free(xy); \
+ return -1; \
+ }
/* Copy table to path array */
if (PyList_Check(data)) {
diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py
index 0d6da77549f..9837589b2b1 100644
--- a/winbuild/build_prepare.py
+++ b/winbuild/build_prepare.py
@@ -112,12 +112,12 @@ def cmd_msbuild(
V = {
"BROTLI": "1.1.0",
"FREETYPE": "2.13.2",
- "FRIBIDI": "1.0.13",
- "HARFBUZZ": "8.4.0",
- "JPEGTURBO": "3.0.2",
+ "FRIBIDI": "1.0.15",
+ "HARFBUZZ": "8.5.0",
+ "JPEGTURBO": "3.0.3",
"LCMS2": "2.16",
"LIBPNG": "1.6.43",
- "LIBWEBP": "1.3.2",
+ "LIBWEBP": "1.4.0",
"OPENJPEG": "2.5.2",
"TIFF": "4.6.0",
"XZ": "5.4.5",