Merge branch 'main' into arrow_malloc_guard

This commit is contained in:
Andrew Murray 2026-03-29 19:13:53 +11:00 committed by GitHub
commit f298638632
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
51 changed files with 510 additions and 182 deletions

View File

@ -24,6 +24,9 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
FORCE_COLOR: 1
jobs:
Fuzzing:
runs-on: ubuntu-latest

View File

@ -14,6 +14,9 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
FORCE_COLOR: 1
jobs:
update_release_draft:
permissions:

View File

@ -12,6 +12,9 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
FORCE_COLOR: 1
jobs:
stale:
if: github.repository_owner == 'python-pillow'

View File

@ -26,6 +26,9 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
FORCE_COLOR: 1
jobs:
build:

View File

@ -28,6 +28,7 @@ concurrency:
env:
COVERAGE_CORE: sysmon
FORCE_COLOR: 1
jobs:
build:

View File

@ -26,6 +26,9 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
FORCE_COLOR: 1
jobs:
build:

View File

@ -24,6 +24,9 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
FORCE_COLOR: 1
jobs:
build:

View File

@ -28,6 +28,7 @@ concurrency:
env:
COVERAGE_CORE: sysmon
FORCE_COLOR: 1
jobs:
build:

View File

@ -90,9 +90,9 @@ fi
ARCHIVE_SDIR=pillow-depends-main
# Package versions for fresh source builds.
FREETYPE_VERSION=2.14.2
FREETYPE_VERSION=2.14.3
HARFBUZZ_VERSION=13.2.1
LIBPNG_VERSION=1.6.55
LIBPNG_VERSION=1.6.56
JPEGTURBO_VERSION=3.1.3
OPENJPEG_VERSION=2.5.4
XZ_VERSION=5.8.2

View File

@ -104,7 +104,7 @@ jobs:
cibw_arch: arm64_iphonesimulator
- name: "iOS x86_64 simulator"
platform: ios
os: macos-26-intel
os: macos-15-intel
cibw_arch: x86_64_iphonesimulator
steps:
- uses: actions/checkout@v6

View File

@ -56,7 +56,7 @@ def test_questionable() -> None:
im.load()
if os.path.basename(f) not in supported:
print(f"Please add {f} to the partially supported bmp specs.")
except Exception: # as msg:
except Exception: # noqa: PERF203
if os.path.basename(f) in supported:
raise
@ -106,7 +106,7 @@ def test_good() -> None:
assert_image_similar(im_converted, compare_converted, 5)
except Exception as msg:
except Exception as msg: # noqa: PERF203
# there are three here that are unsupported:
unsupported = (
os.path.join(base, "g", "rgb32bf.bmp"),

View File

@ -179,9 +179,7 @@ def test_iter(bytesmode: bool) -> None:
container = ContainerIO.ContainerIO(fh, 0, 120)
# Act
data = []
for line in container:
data.append(line)
data = list(container)
# Assert
if bytesmode:

View File

@ -148,6 +148,22 @@ def test_prog_res_rt(card: ImageFile.ImageFile) -> None:
assert_image_equal(im, card)
def test_unknown_progression(tmp_path: Path) -> None:
outfile = tmp_path / "temp.jp2"
im = Image.new("1", (1, 1))
with pytest.raises(ValueError, match="unknown progression"):
im.save(outfile, progression="invalid")
def test_unknown_cinema_mode(tmp_path: Path) -> None:
outfile = tmp_path / "temp.jp2"
im = Image.new("1", (1, 1))
with pytest.raises(ValueError, match="unknown cinema mode"):
im.save(outfile, cinema_mode="invalid")
@pytest.mark.parametrize("num_resolutions", range(2, 6))
def test_default_num_resolutions(
card: ImageFile.ImageFile, num_resolutions: int
@ -440,6 +456,13 @@ def test_pclr() -> None:
assert len(im.palette.colors) == 256
assert im.palette.colors[(255, 255, 255)] == 0
for enumcs in (0, 15, 17):
with open(f"{EXTRA_DIR}/issue104_jpxstream.jp2", "rb") as fp:
data = bytearray(fp.read())
data[114:115] = bytes([enumcs])
with Image.open(BytesIO(data)) as im:
assert im.mode == "L"
with Image.open(
f"{EXTRA_DIR}/147af3f1083de4393666b7d99b01b58b_signal_sigsegv_130c531_6155_5136.jp2"
) as im:

View File

@ -224,10 +224,7 @@ class TestFileLibTiff(LibTiffTestCase):
with Image.open("Tests/images/hopper_g4.tif") as im:
assert isinstance(im, TiffImagePlugin.TiffImageFile)
for tag in im.tag_v2:
try:
del core_items[tag]
except KeyError:
pass
core_items.pop(tag, None)
del core_items[320] # colormap is special, tested below
# Type codes:

View File

@ -6,7 +6,14 @@ from typing import Any
import pytest
from PIL import Image, ImageFile, JpegImagePlugin, MpoImagePlugin
from PIL import (
Image,
ImageFile,
JpegImagePlugin,
MpoImagePlugin,
TiffImagePlugin,
_binary,
)
from .helper import (
assert_image_equal,
@ -145,6 +152,32 @@ def test_parallax() -> None:
assert exif.get_ifd(0x927C)[0xB211] == -3.125
def test_truncated_makernote() -> None:
def check(ifd: TiffImagePlugin.ImageFileDirectory_v2) -> None:
fp = BytesIO()
ifd.save(fp)
e = Image.Exif()
e.load(fp.getvalue())
assert e.get_ifd(37500) == {}
# Nintendo
ifd = TiffImagePlugin.ImageFileDirectory_v2()
ifd[271] = "Nintendo"
ifd[34665] = {37500: b" "}
check(ifd)
# Fujifilm
for data in (
b"FUJIFILM",
b"FUJIFILM" + _binary.o32le(50),
b"FUJIFILM" + _binary.o32le(0),
):
ifd = TiffImagePlugin.ImageFileDirectory_v2()
ifd[34665] = {37500: data}
check(ifd)
def test_reload_exif_after_seek() -> None:
with Image.open("Tests/images/sugarshack.mpo") as im:
exif = im.getexif()

View File

@ -1,11 +1,12 @@
from __future__ import annotations
import os
from io import BytesIO
from pathlib import Path
import pytest
from PIL import Image, UnidentifiedImageError
from PIL import Image, UnidentifiedImageError, _binary
from .helper import assert_image_equal, assert_image_equal_tofile, hopper
@ -92,6 +93,25 @@ def test_rgba_16() -> None:
assert im.getpixel((1, 0)) == (0, 255, 82, 0)
def test_v2_no_alpha() -> None:
test_file = "Tests/images/tga/common/200x32_rgba_tl_rle.tga"
with open(test_file, "rb") as fp:
data = fp.read()
data += (
b"\x00" * 495
+ _binary.o32le(len(data))
+ _binary.o32le(0)
+ b"TRUEVISION-XFILE.\x00"
)
with Image.open(BytesIO(data)) as im:
with Image.open(test_file) as im2:
r, g, b = im2.split()[:3]
a = Image.new("L", im2.size, 255)
expected = Image.merge("RGBA", (r, g, b, a))
assert_image_equal(im, expected)
def test_id_field() -> None:
# tga file with id field
test_file = "Tests/images/tga_id_field.tga"

View File

@ -16,6 +16,7 @@ from PIL import (
TiffImagePlugin,
TiffTags,
UnidentifiedImageError,
_binary,
)
from PIL.TiffImagePlugin import RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION
@ -941,6 +942,15 @@ class TestFileTiff:
4001,
]
def test_truncated_photoshop_blocks(self) -> None:
with Image.open("Tests/images/hopper.tif") as im:
assert isinstance(im, TiffImagePlugin.TiffImageFile)
im.tag_v2[34377] = b"8BIM"
assert im.get_photoshop_blocks() == {}
im.tag_v2[34377] = b"8BIM" + _binary.o16be(0) + _binary.o8(2) + b" " * 5
assert im.get_photoshop_blocks() == {}
def test_tiff_chunks(self, tmp_path: Path) -> None:
tmpfile = tmp_path / "temp.tif"

View File

@ -5,7 +5,10 @@ archive=$1
url=$2
if [ ! -f $archive.tar.gz ]; then
wget --no-verbose -O $archive.tar.gz $url
wget -O $archive.tar.gz $url \
--no-verbose \
--retry-connrefused \
--retry-on-http-error=429,503,504
fi
rmdir $archive

View File

@ -0,0 +1,52 @@
12.2.0
------
Security
========
TODO
^^^^
TODO
:cve:`YYYY-XXXXX`: TODO
^^^^^^^^^^^^^^^^^^^^^^^
TODO
API changes
===========
Error when encoding an empty image
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Attempting to encode an image with zero width or height would previously raise
a :py:exc:`SystemError`. That has now been changed to a :py:exc:`ValueError`.
This does not add any new errors. SGI, ICNS and ICO formats are still able to
save (0, 0) images.
API additions
=============
FontFile.to_imagefont()
^^^^^^^^^^^^^^^^^^^^^^^
:py:class:`~PIL.FontFile.FontFile` instances can now be directly converted to
:py:class:`~PIL.ImageFont.ImageFont` instances::
>>> from PIL import PcfFontFile
>>> with open("Tests/fonts/10x20-ISO8859-1.pcf", "rb") as fp:
... pcffont = PcfFontFile.PcfFontFile(fp)
... pcffont.to_imagefont()
...
<PIL.ImageFont.ImageFont object at 0x10457bb80>
Other changes
=============
Support reading JPEG2000 images with CMYK palettes
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
JPEG2000 images with CMYK palettes can now be read. This is the first integration of
CMYK palettes into Pillow.

View File

@ -15,6 +15,7 @@ expected to be backported to earlier versions.
:maxdepth: 2
versioning
12.2.0
12.1.1
12.1.0
12.0.0

View File

@ -146,6 +146,7 @@ lint.select = [
"I", # isort
"ISC", # flake8-implicit-str-concat
"LOG", # flake8-logging
"PERF", # perflint
"PGH", # pygrep-hooks
"PIE", # flake8-pie
"PT", # flake8-pytest-style

View File

@ -302,7 +302,7 @@ def _pkg_config(name: str) -> tuple[list[str], list[str]] | None:
subprocess.check_output(command_cflags).decode("utf8").strip(),
)[::2][1:]
return libs, cflags
except Exception:
except Exception: # noqa: PERF203
pass
return None
@ -1078,10 +1078,10 @@ libraries: list[tuple[str, _BuildInfo]] = [
]
files: list[str | os.PathLike[str]] = ["src/_imaging.c"]
for src_file in _IMAGING:
files.append("src/" + src_file + ".c")
for src_file in _LIB_IMAGING:
files.append(os.path.join("src/libImaging", src_file + ".c"))
files.extend("src/" + src_file + ".c" for src_file in _IMAGING)
files.extend(
os.path.join("src/libImaging", src_file + ".c") for src_file in _LIB_IMAGING
)
ext_modules = [
Extension("PIL._imaging", files),
Extension("PIL._imagingft", ["src/_imagingft.c"]),

View File

@ -164,13 +164,13 @@ class GifImageFile(ImageFile.ImageFile):
self._seek(0)
last_frame = self.__frame
for f in range(self.__frame + 1, frame + 1):
try:
try:
for f in range(self.__frame + 1, frame + 1):
self._seek(f)
except EOFError as e:
self.seek(last_frame)
msg = "no more images in GIF file"
raise EOFError(msg) from e
except EOFError as e:
self.seek(last_frame)
msg = "no more images in GIF file"
raise EOFError(msg) from e
def _seek(self, frame: int, update_image: bool = True) -> None:
if isinstance(self._fp, DeferredError):

View File

@ -80,8 +80,7 @@ def read_32(
if byte_int & 0x80:
blocksize = byte_int - 125
byte = fobj.read(1)
for i in range(blocksize):
data.append(byte)
data.extend([byte] * blocksize)
else:
blocksize = byte_int + 1
data.append(fobj.read(blocksize))

View File

@ -488,7 +488,7 @@ def init() -> bool:
try:
logger.debug("Importing %s", plugin)
__import__(f"{__spec__.parent}.{plugin}", globals(), locals(), [])
except ImportError as e:
except ImportError as e: # noqa: PERF203
logger.debug("Image: failed to import %s: %s", plugin, e)
if OPEN or SAVE:
@ -4234,80 +4234,83 @@ class Exif(_ExifBase):
if tag == ExifTags.IFD.MakerNote:
from .TiffImagePlugin import ImageFileDirectory_v2
if tag_data.startswith(b"FUJIFILM"):
ifd_offset = i32le(tag_data, 8)
ifd_data = tag_data[ifd_offset:]
try:
if tag_data.startswith(b"FUJIFILM"):
ifd_offset = i32le(tag_data, 8)
ifd_data = tag_data[ifd_offset:]
makernote = {}
for i in range(struct.unpack("<H", ifd_data[:2])[0]):
ifd_tag, typ, count, data = struct.unpack(
"<HHL4s", ifd_data[i * 12 + 2 : (i + 1) * 12 + 2]
)
try:
(
unit_size,
handler,
) = ImageFileDirectory_v2._load_dispatch[typ]
except KeyError:
continue
size = count * unit_size
if size > 4:
(offset,) = struct.unpack("<L", data)
data = ifd_data[offset - 12 : offset + size - 12]
else:
data = data[:size]
if len(data) != size:
warnings.warn(
"Possibly corrupt EXIF MakerNote data. "
f"Expecting to read {size} bytes but only got "
f"{len(data)}. Skipping tag {ifd_tag}"
makernote = {}
for i in range(struct.unpack("<H", ifd_data[:2])[0]):
ifd_tag, typ, count, data = struct.unpack(
"<HHL4s", ifd_data[i * 12 + 2 : (i + 1) * 12 + 2]
)
continue
try:
(
unit_size,
handler,
) = ImageFileDirectory_v2._load_dispatch[typ]
except KeyError:
continue
size = count * unit_size
if size > 4:
(offset,) = struct.unpack("<L", data)
data = ifd_data[offset - 12 : offset + size - 12]
else:
data = data[:size]
if not data:
continue
if len(data) != size:
warnings.warn(
"Possibly corrupt EXIF MakerNote data. "
f"Expecting to read {size} bytes but only got "
f"{len(data)}. Skipping tag {ifd_tag}"
)
continue
makernote[ifd_tag] = handler(
ImageFileDirectory_v2(), data, False
)
self._ifds[tag] = dict(self._fixup_dict(makernote))
elif self.get(0x010F) == "Nintendo":
makernote = {}
for i in range(struct.unpack(">H", tag_data[:2])[0]):
ifd_tag, typ, count, data = struct.unpack(
">HHL4s", tag_data[i * 12 + 2 : (i + 1) * 12 + 2]
)
if ifd_tag == 0x1101:
# CameraInfo
(offset,) = struct.unpack(">L", data)
self.fp.seek(offset)
if not data:
continue
camerainfo: dict[str, int | bytes] = {
"ModelID": self.fp.read(4)
}
makernote[ifd_tag] = handler(
ImageFileDirectory_v2(), data, False
)
self._ifds[tag] = dict(self._fixup_dict(makernote))
elif self.get(0x010F) == "Nintendo":
makernote = {}
for i in range(struct.unpack(">H", tag_data[:2])[0]):
ifd_tag, typ, count, data = struct.unpack(
">HHL4s", tag_data[i * 12 + 2 : (i + 1) * 12 + 2]
)
if ifd_tag == 0x1101:
# CameraInfo
(offset,) = struct.unpack(">L", data)
self.fp.seek(offset)
self.fp.read(4)
# Seconds since 2000
camerainfo["TimeStamp"] = i32le(self.fp.read(12))
camerainfo: dict[str, int | bytes] = {
"ModelID": self.fp.read(4)
}
self.fp.read(4)
camerainfo["InternalSerialNumber"] = self.fp.read(4)
self.fp.read(4)
# Seconds since 2000
camerainfo["TimeStamp"] = i32le(self.fp.read(12))
self.fp.read(12)
parallax = self.fp.read(4)
handler = ImageFileDirectory_v2._load_dispatch[
TiffTags.FLOAT
][1]
camerainfo["Parallax"] = handler(
ImageFileDirectory_v2(), parallax, False
)[0]
self.fp.read(4)
camerainfo["InternalSerialNumber"] = self.fp.read(4)
self.fp.read(4)
camerainfo["Category"] = self.fp.read(2)
self.fp.read(12)
parallax = self.fp.read(4)
handler = ImageFileDirectory_v2._load_dispatch[
TiffTags.FLOAT
][1]
camerainfo["Parallax"] = handler(
ImageFileDirectory_v2(), parallax, False
)[0]
makernote = {0x1101: camerainfo}
self._ifds[tag] = makernote
self.fp.read(4)
camerainfo["Category"] = self.fp.read(2)
makernote = {0x1101: camerainfo}
self._ifds[tag] = makernote
except struct.error:
pass
else:
# Interop
ifd = self._get_ifd_dict(tag_data, tag)

View File

@ -597,9 +597,7 @@ class ImageDraw:
mode = self.fontmode
if stroke_width == 0 and embedded_color:
mode = "RGBA"
coord = []
for i in range(2):
coord.append(int(xy[i]))
coord = [int(xy[i]) for i in range(2)]
start = (math.modf(xy[0])[0], math.modf(xy[1])[0])
try:
mask, offset = image_text.font.getmask2( # type: ignore[union-attr,misc]

View File

@ -215,8 +215,10 @@ class ImageFile(Image.Image):
if subifd_offsets:
if not isinstance(subifd_offsets, tuple):
subifd_offsets = (subifd_offsets,)
for subifd_offset in subifd_offsets:
ifds.append((exif._get_ifd_dict(subifd_offset), subifd_offset))
ifds = [
(exif._get_ifd_dict(subifd_offset), subifd_offset)
for subifd_offset in subifd_offsets
]
ifd1 = exif.get_ifd(ExifTags.IFD.IFD1)
if ifd1 and ifd1.get(ExifTags.Base.JpegIFOffset):
assert exif._info is not None

View File

@ -930,7 +930,7 @@ def load_path(filename: str | bytes) -> ImageFont:
for directory in sys.path:
try:
return load(os.path.join(directory, filename))
except OSError:
except OSError: # noqa: PERF203
pass
msg = f'cannot find font file "{filename}" in sys.path'
if os.path.exists(filename):

View File

@ -198,13 +198,11 @@ class ImagePalette:
try:
fp.write("# Palette\n")
fp.write(f"# Mode: {self.mode}\n")
palette_len = len(self.palette)
for i in range(256):
fp.write(f"{i}")
for j in range(i * len(self.mode), (i + 1) * len(self.mode)):
try:
fp.write(f" {self.palette[j]}")
except IndexError:
fp.write(" 0")
fp.write(f" {self.palette[j] if j < palette_len else 0}")
fp.write("\n")
finally:
if open_fp:

View File

@ -185,13 +185,9 @@ def getiptcinfo(
data = None
info: dict[tuple[int, int], bytes | list[bytes]] = {}
if isinstance(im, IptcImageFile):
# return info dictionary right away
for k, v in im.info.items():
if isinstance(k, tuple):
info[k] = v
return info
return {k: v for k, v in im.info.items() if isinstance(k, tuple)}
elif isinstance(im, JpegImagePlugin.JpegImageFile):
# extract the IPTC/NAA resource
@ -227,7 +223,4 @@ def getiptcinfo(
except (IndexError, KeyError):
pass # expected failure
for k, v in iptc_im.info.items():
if isinstance(k, tuple):
info[k] = v
return info
return {k: v for k, v in iptc_im.info.items() if isinstance(k, tuple)}

View File

@ -176,7 +176,7 @@ def _parse_jp2_header(
nc = None
dpi = None # 2-tuple of DPI info, or None
palette = None
cmyk = False
colr = None
while header.has_next_box():
tbox = header.next_box_type()
@ -199,10 +199,16 @@ def _parse_jp2_header(
mode = "RGBA"
elif tbox == b"colr":
meth, _, _, enumcs = header.read_fields(">BBBI")
if cmyk := (meth == 1 and enumcs == 12):
if nc == 4:
mode = "CMYK"
elif tbox == b"pclr" and mode in ("L", "LA"):
if meth == 1:
if enumcs in (0, 15):
colr = "1"
elif enumcs == 12:
colr = "CMYK"
if nc == 4:
mode = "CMYK"
elif enumcs == 17:
colr = "L"
elif tbox == b"pclr" and mode in ("L", "LA") and colr not in ("1", "L"):
ne, npc = header.read_fields(">HB")
assert isinstance(ne, int)
assert isinstance(npc, int)
@ -213,7 +219,7 @@ def _parse_jp2_header(
max_bitdepth = bitdepth
if max_bitdepth <= 8:
if npc == 4:
palette_mode = "CMYK" if cmyk else "RGBA"
palette_mode = "CMYK" if colr == "CMYK" else "RGBA"
else:
palette_mode = "RGB"
palette = ImagePalette.ImagePalette(palette_mode)

View File

@ -127,8 +127,8 @@ def APP(self: JpegImageFile, marker: int) -> None:
# parse the image resource block
offset = 14
photoshop = self.info.setdefault("photoshop", {})
while s[offset : offset + 4] == b"8BIM":
try:
try:
while s[offset : offset + 4] == b"8BIM":
offset += 4
# resource code
code = i16(s, offset)
@ -153,8 +153,8 @@ def APP(self: JpegImageFile, marker: int) -> None:
photoshop[code] = data
offset += size
offset += offset & 1 # align
except struct.error:
break # insufficient data
except struct.error:
pass # insufficient data
elif marker == 0xFFEE and s.startswith(b"Adobe"):
self.info["adobe"] = i16(s, 5)
@ -738,17 +738,15 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
if not (0 < len(qtables) < 5):
msg = "None or too many quantization tables"
raise ValueError(msg)
for idx, table in enumerate(qtables):
try:
try:
for idx, table in enumerate(qtables):
if len(table) != 64:
msg = "Invalid quantization table"
raise TypeError(msg)
table_array = array.array("H", table)
except TypeError as e:
msg = "Invalid quantization table"
raise ValueError(msg) from e
else:
qtables[idx] = list(table_array)
qtables[idx] = list(array.array("H", table))
except TypeError as e:
msg = "Invalid quantization table"
raise ValueError(msg) from e
return qtables
if qtables == "keep":

View File

@ -251,7 +251,7 @@ class PcfFontFile(FontFile.FontFile):
]
if encoding_offset != 0xFFFF:
encoding[i] = encoding_offset
except UnicodeDecodeError:
except UnicodeDecodeError: # noqa: PERF203
# character is not supported in selected encoding
pass

View File

@ -866,13 +866,13 @@ class PngImageFile(ImageFile.ImageFile):
self._seek(0, True)
last_frame = self.__frame
for f in range(self.__frame + 1, frame + 1):
try:
try:
for f in range(self.__frame + 1, frame + 1):
self._seek(f)
except EOFError as e:
self.seek(last_frame)
msg = "no more images in APNG file"
raise EOFError(msg) from e
except EOFError as e:
self.seek(last_frame)
msg = "no more images in APNG file"
raise EOFError(msg) from e
def _seek(self, frame: int, rewind: bool = False) -> None:
assert self.png is not None

View File

@ -17,11 +17,13 @@
#
from __future__ import annotations
import os
import warnings
from typing import IO
from . import Image, ImageFile, ImagePalette
from ._binary import i16le as i16
from ._binary import i32le as i32
from ._binary import o8
from ._binary import o16le as o16
@ -157,6 +159,20 @@ class TgaImageFile(ImageFile.ImageFile):
pass # cannot decode
def load_end(self) -> None:
if self.mode == "RGBA":
assert self.fp is not None
self.fp.seek(-26, os.SEEK_END)
footer = self.fp.read(26)
if footer.endswith(b"TRUEVISION-XFILE.\x00"):
# version 2
extension_offset = i32(footer)
if extension_offset:
self.fp.seek(extension_offset + 494)
attributes_type = self.fp.read(1)
if attributes_type == b"\x00":
# No alpha
self.im.fillband(3, 255)
if self._flip_horizontally:
self.im = self.im.transpose(Image.Transpose.FLIP_LEFT_RIGHT)

View File

@ -1287,10 +1287,13 @@ class TiffImageFile(ImageFile.ImageFile):
blocks = {}
val = self.tag_v2.get(ExifTags.Base.ImageResources)
if val:
while val.startswith(b"8BIM"):
while val.startswith(b"8BIM") and len(val) >= 12:
id = i16(val[4:6])
n = math.ceil((val[6] + 1) / 2) * 2
size = i32(val[6 + n : 10 + n])
try:
size = i32(val[6 + n : 10 + n])
except struct.error:
break
data = val[10 + n : 10 + n + size]
blocks[id] = {"data": data}

View File

@ -425,7 +425,7 @@ end:
return (PyObject *)self;
}
PyObject *
void
_encoder_dealloc(AvifEncoderObject *self) {
if (self->encoder) {
avifEncoderDestroy(self->encoder);
@ -433,7 +433,7 @@ _encoder_dealloc(AvifEncoderObject *self) {
if (self->image) {
avifImageDestroy(self->image);
}
Py_RETURN_NONE;
Py_TYPE(self)->tp_free(self);
}
PyObject *
@ -687,13 +687,13 @@ AvifDecoderNew(PyObject *self_, PyObject *args) {
return (PyObject *)self;
}
PyObject *
void
_decoder_dealloc(AvifDecoderObject *self) {
if (self->decoder) {
avifDecoderDestroy(self->decoder);
}
PyBuffer_Release(&self->buffer);
Py_RETURN_NONE;
Py_TYPE(self)->tp_free(self);
}
PyObject *
@ -708,15 +708,27 @@ _decoder_get_info(AvifDecoderObject *self) {
if (image->xmp.size) {
xmp = PyBytes_FromStringAndSize((const char *)image->xmp.data, image->xmp.size);
if (!xmp) {
return NULL;
}
}
if (image->exif.size) {
exif =
PyBytes_FromStringAndSize((const char *)image->exif.data, image->exif.size);
if (!exif) {
Py_XDECREF(xmp);
return NULL;
}
}
if (image->icc.size) {
icc = PyBytes_FromStringAndSize((const char *)image->icc.data, image->icc.size);
if (!icc) {
Py_XDECREF(xmp);
Py_XDECREF(exif);
return NULL;
}
}
ret = Py_BuildValue(
@ -799,6 +811,7 @@ _decoder_get_frame(AvifDecoderObject *self, PyObject *args) {
if (rgb.height > PY_SSIZE_T_MAX / rgb.rowBytes) {
PyErr_SetString(PyExc_MemoryError, "Integer overflow in pixel size");
avifRGBImageFreePixels(&rgb);
return NULL;
}
@ -806,6 +819,9 @@ _decoder_get_frame(AvifDecoderObject *self, PyObject *args) {
bytes = PyBytes_FromStringAndSize((char *)rgb.pixels, size);
avifRGBImageFreePixels(&rgb);
if (!bytes) {
return NULL;
}
ret = Py_BuildValue(
"SKKK",

View File

@ -254,6 +254,9 @@ void
ReleaseArrowSchemaPyCapsule(PyObject *capsule) {
struct ArrowSchema *schema =
(struct ArrowSchema *)PyCapsule_GetPointer(capsule, "arrow_schema");
if (!schema) {
return;
}
if (schema->release != NULL) {
schema->release(schema);
}
@ -276,6 +279,9 @@ void
ReleaseArrowArrayPyCapsule(PyObject *capsule) {
struct ArrowArray *array =
(struct ArrowArray *)PyCapsule_GetPointer(capsule, "arrow_array");
if (!array) {
return;
}
if (array->release != NULL) {
array->release(array);
}
@ -1219,7 +1225,9 @@ _getxy(PyObject *xy, int *x, int *y) {
PyObject *int_value = PyObject_CallMethod(value, "__int__", NULL);
if (int_value != NULL && PyLong_Check(int_value)) {
*x = PyLong_AS_LONG(int_value);
Py_DECREF(int_value);
} else {
Py_XDECREF(int_value);
goto badval;
}
}
@ -1233,7 +1241,9 @@ _getxy(PyObject *xy, int *x, int *y) {
PyObject *int_value = PyObject_CallMethod(value, "__int__", NULL);
if (int_value != NULL && PyLong_Check(int_value)) {
*y = PyLong_AS_LONG(int_value);
Py_DECREF(int_value);
} else {
Py_XDECREF(int_value);
goto badval;
}
}
@ -2472,6 +2482,9 @@ _split(ImagingObject *self) {
}
list = PyTuple_New(self->image->bands);
if (!list) {
return NULL;
}
for (i = 0; i < self->image->bands; i++) {
imaging_object = PyImagingNew(bands[i]);
if (!imaging_object) {
@ -3768,6 +3781,9 @@ _ptr_destructor(PyObject *capsule) {
static PyObject *
_getattr_ptr(ImagingObject *self, void *closure) {
PyObject *capsule = PyCapsule_New(self->image, IMAGING_MAGIC, _ptr_destructor);
if (!capsule) {
return NULL;
}
Py_INCREF(self);
PyCapsule_SetContext(capsule, self);
return capsule;
@ -4323,8 +4339,9 @@ setup_module(PyObject *m) {
#else
have_libjpegturbo = Py_False;
#endif
Py_INCREF(have_libjpegturbo);
PyModule_AddObject(m, "HAVE_LIBJPEGTURBO", have_libjpegturbo);
if (PyModule_AddObjectRef(m, "HAVE_LIBJPEGTURBO", have_libjpegturbo) < 0) {
return -1;
}
PyObject *have_mozjpeg;
#ifdef JPEG_C_PARAM_SUPPORTED
@ -4332,8 +4349,9 @@ setup_module(PyObject *m) {
#else
have_mozjpeg = Py_False;
#endif
Py_INCREF(have_mozjpeg);
PyModule_AddObject(m, "HAVE_MOZJPEG", have_mozjpeg);
if (PyModule_AddObjectRef(m, "HAVE_MOZJPEG", have_mozjpeg) < 0) {
return -1;
}
PyObject *have_libimagequant;
#ifdef HAVE_LIBIMAGEQUANT
@ -4347,8 +4365,9 @@ setup_module(PyObject *m) {
#else
have_libimagequant = Py_False;
#endif
Py_INCREF(have_libimagequant);
PyModule_AddObject(m, "HAVE_LIBIMAGEQUANT", have_libimagequant);
if (PyModule_AddObjectRef(m, "HAVE_LIBIMAGEQUANT", have_libimagequant) < 0) {
return -1;
}
#ifdef HAVE_LIBZ
/* zip encoding strategies */
@ -4376,8 +4395,9 @@ setup_module(PyObject *m) {
#else
have_zlibng = Py_False;
#endif
Py_INCREF(have_zlibng);
PyModule_AddObject(m, "HAVE_ZLIBNG", have_zlibng);
if (PyModule_AddObjectRef(m, "HAVE_ZLIBNG", have_zlibng) < 0) {
return -1;
}
#ifdef HAVE_LIBTIFF
{
@ -4394,8 +4414,9 @@ setup_module(PyObject *m) {
#else
have_xcb = Py_False;
#endif
Py_INCREF(have_xcb);
PyModule_AddObject(m, "HAVE_XCB", have_xcb);
if (PyModule_AddObjectRef(m, "HAVE_XCB", have_xcb) < 0) {
return -1;
}
PyObject *pillow_version = PyUnicode_FromString(version);
PyDict_SetItemString(

View File

@ -558,7 +558,13 @@ cms_transform_apply(CmsTransformObject *self, PyObject *args) {
}
im = (Imaging)PyCapsule_GetPointer(i0, IMAGING_MAGIC);
if (!im) {
return NULL;
}
imOut = (Imaging)PyCapsule_GetPointer(i1, IMAGING_MAGIC);
if (!imOut) {
return NULL;
}
return Py_BuildValue("i", pyCMSdoTransform(im, imOut, self->transform));
}
@ -1447,14 +1453,14 @@ setup_module(PyObject *m) {
int vn;
/* Ready object types */
PyType_Ready(&CmsProfile_Type);
PyType_Ready(&CmsTransform_Type);
if (PyType_Ready(&CmsProfile_Type) < 0 || PyType_Ready(&CmsTransform_Type) < 0) {
return -1;
}
Py_INCREF(&CmsProfile_Type);
PyModule_AddObject(m, "CmsProfile", (PyObject *)&CmsProfile_Type);
Py_INCREF(&CmsTransform_Type);
PyModule_AddObject(m, "CmsTransform", (PyObject *)&CmsTransform_Type);
if (PyModule_AddObjectRef(m, "CmsProfile", (PyObject *)&CmsProfile_Type) < 0 ||
PyModule_AddObjectRef(m, "CmsTransform", (PyObject *)&CmsTransform_Type) < 0) {
return -1;
}
d = PyModule_GetDict(m);

View File

@ -941,8 +941,18 @@ font_render(FontObject *self, PyObject *args) {
return NULL;
}
PyObject *imagePtr = PyObject_GetAttrString(image, "ptr");
if (!imagePtr) {
PyMem_Del(glyph_info);
Py_DECREF(image);
return NULL;
}
im = (Imaging)PyCapsule_GetPointer(imagePtr, IMAGING_MAGIC);
Py_XDECREF(imagePtr);
Py_DECREF(imagePtr);
if (!im) {
PyMem_Del(glyph_info);
Py_DECREF(image);
return NULL;
}
x_offset = round(x_offset - stroke_width);
y_offset = round(y_offset - stroke_width);
@ -1545,7 +1555,9 @@ setup_module(PyObject *m) {
d = PyModule_GetDict(m);
/* Ready object type */
PyType_Ready(&Font_Type);
if (PyType_Ready(&Font_Type) < 0) {
return -1;
}
if (FT_Init_FreeType(&library)) {
return 0; /* leave it uninitialized */

View File

@ -187,8 +187,17 @@ _unop(PyObject *self, PyObject *args) {
}
unop = (void *)PyCapsule_GetPointer(op, MATH_FUNC_UNOP_MAGIC);
if (!unop) {
return NULL;
}
out = (Imaging)PyCapsule_GetPointer(i0, IMAGING_MAGIC);
if (!out) {
return NULL;
}
im1 = (Imaging)PyCapsule_GetPointer(i1, IMAGING_MAGIC);
if (!im1) {
return NULL;
}
unop(out, im1);
@ -219,9 +228,21 @@ _binop(PyObject *self, PyObject *args) {
}
binop = (void *)PyCapsule_GetPointer(op, MATH_FUNC_BINOP_MAGIC);
if (!binop) {
return NULL;
}
out = (Imaging)PyCapsule_GetPointer(i0, IMAGING_MAGIC);
if (!out) {
return NULL;
}
im1 = (Imaging)PyCapsule_GetPointer(i1, IMAGING_MAGIC);
if (!im1) {
return NULL;
}
im2 = (Imaging)PyCapsule_GetPointer(i2, IMAGING_MAGIC);
if (!im2) {
return NULL;
}
binop(out, im1, im2);

View File

@ -53,7 +53,13 @@ apply(PyObject *self, PyObject *args) {
}
imgin = (Imaging)PyCapsule_GetPointer(i0, IMAGING_MAGIC);
if (!imgin) {
return NULL;
}
imgout = (Imaging)PyCapsule_GetPointer(i1, IMAGING_MAGIC);
if (!imgout) {
return NULL;
}
width = imgin->xsize;
height = imgin->ysize;
@ -143,6 +149,9 @@ match(PyObject *self, PyObject *args) {
}
imgin = (Imaging)PyCapsule_GetPointer(i0, IMAGING_MAGIC);
if (!imgin) {
return NULL;
}
if (imgin->type != IMAGING_TYPE_UINT8 || imgin->bands != 1) {
PyErr_SetString(PyExc_RuntimeError, "Unsupported image type");
@ -185,6 +194,10 @@ match(PyObject *self, PyObject *args) {
(b6 << 6) | (b7 << 7) | (b8 << 8));
if (lut[lut_idx]) {
PyObject *coordObj = Py_BuildValue("(nn)", col_idx, row_idx);
if (!coordObj) {
Py_DECREF(ret);
return NULL;
}
PyList_Append(ret, coordObj);
Py_XDECREF(coordObj);
}
@ -216,6 +229,9 @@ get_on_pixels(PyObject *self, PyObject *args) {
}
img = (Imaging)PyCapsule_GetPointer(i0, IMAGING_MAGIC);
if (!img) {
return NULL;
}
rows = img->image8;
width = img->xsize;
height = img->ysize;
@ -230,6 +246,10 @@ get_on_pixels(PyObject *self, PyObject *args) {
for (col_idx = 0; col_idx < width; col_idx++) {
if (row[col_idx]) {
PyObject *coordObj = Py_BuildValue("(nn)", col_idx, row_idx);
if (!coordObj) {
Py_DECREF(ret);
return NULL;
}
PyList_Append(ret, coordObj);
Py_XDECREF(coordObj);
}

View File

@ -219,6 +219,7 @@ _anim_encoder_dealloc(PyObject *self) {
WebPAnimEncoderObject *encp = (WebPAnimEncoderObject *)self;
WebPPictureFree(&(encp->frame));
WebPAnimEncoderDelete(encp->enc);
Py_TYPE(self)->tp_free(self);
}
PyObject *
@ -261,6 +262,9 @@ _anim_encoder_add(PyObject *self, PyObject *args) {
}
im = (Imaging)PyCapsule_GetPointer(i0, IMAGING_MAGIC);
if (!im) {
return NULL;
}
// Setup config for this frame
if (!WebPConfigInit(&config)) {
@ -441,6 +445,7 @@ _anim_decoder_dealloc(PyObject *self) {
WebPAnimDecoderObject *decp = (WebPAnimDecoderObject *)self;
WebPDataClear(&(decp->data));
WebPAnimDecoderDelete(decp->dec);
Py_TYPE(self)->tp_free(self);
}
PyObject *
@ -503,6 +508,9 @@ _anim_decoder_get_next(PyObject *self) {
bytes = PyBytes_FromStringAndSize(
(char *)buf, decp->info.canvas_width * 4 * decp->info.canvas_height
);
if (!bytes) {
return NULL;
}
ret = Py_BuildValue("Si", bytes, timestamp);
@ -605,6 +613,9 @@ WebPEncode_wrapper(PyObject *self, PyObject *args) {
}
im = (Imaging)PyCapsule_GetPointer(i0, IMAGING_MAGIC);
if (!im) {
return NULL;
}
// Setup config for this frame
if (!WebPConfigInit(&config)) {

View File

@ -905,6 +905,7 @@ PyImaging_Jpeg2KDecoderNew(PyObject *self, PyObject *args) {
} else if (strcmp(format, "jp2") == 0) {
codec_format = OPJ_CODEC_JP2;
} else {
PyErr_SetString(PyExc_ValueError, "unknown codec format");
return NULL;
}

View File

@ -480,6 +480,9 @@ PyImaging_GrabClipboardWin32(PyObject *self, PyObject *args) {
GlobalUnlock(handle);
CloseClipboard();
if (!result) {
return NULL;
}
return Py_BuildValue("zN", format_names[format], result);
}

View File

@ -727,6 +727,7 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
const RawModeID rawmode = findRawModeID(rawmode_name);
if (get_packer(encoder, mode, rawmode) < 0) {
Py_DECREF(encoder);
return NULL;
}
@ -742,6 +743,7 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
for (pos = 0; pos < tags_size; pos++) {
item = PyList_GetItemRef(tags, pos);
if (item == NULL) {
Py_DECREF(encoder);
return NULL;
}
@ -766,6 +768,7 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
if (!is_core_tag) {
PyObject *tag_type;
if (PyDict_GetItemRef(types, key, &tag_type) < 0) {
Py_DECREF(encoder);
return NULL; // Exception has been already set
}
if (tag_type) {
@ -837,6 +840,7 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
if (key_int == TIFFTAG_COLORMAP) {
int stride = 256;
if (len != 768) {
Py_DECREF(encoder);
PyErr_SetString(
PyExc_ValueError, "Requiring 768 items for Colormap"
);
@ -1343,11 +1347,10 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args) {
if (strcmp(format, "j2k") == 0) {
codec_format = OPJ_CODEC_J2K;
} else if (strcmp(format, "jpt") == 0) {
codec_format = OPJ_CODEC_JPT;
} else if (strcmp(format, "jp2") == 0) {
codec_format = OPJ_CODEC_JP2;
} else {
PyErr_SetString(PyExc_ValueError, "unknown codec format");
return NULL;
}
@ -1362,6 +1365,7 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args) {
} else if (strcmp(progression, "CPRL") == 0) {
prog_order = OPJ_CPRL;
} else {
PyErr_SetString(PyExc_ValueError, "unknown progression");
return NULL;
}
@ -1374,6 +1378,7 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args) {
} else if (strcmp(cinema_mode, "cinema4k-24") == 0) {
cine_mode = OPJ_CINEMA4K_24;
} else {
PyErr_SetString(PyExc_ValueError, "unknown cinema mode");
return NULL;
}

View File

@ -352,16 +352,16 @@ ImagingPaste(
static inline void
fill(
Imaging imOut, const void *ink_, int dx, int dy, int xsize, int ysize, int pixelsize
Imaging imOut, const UINT8 *ink, int dx, int dy, int xsize, int ysize, int pixelsize
) {
/* fill opaque region */
int x, y;
int x, y, i;
UINT8 ink8 = 0;
INT32 ink32 = 0L;
memcpy(&ink32, ink_, pixelsize);
memcpy(&ink8, ink_, sizeof(ink8));
memcpy(&ink32, ink, pixelsize);
memcpy(&ink8, ink, sizeof(ink8));
if (imOut->image8 || ink32 == 0L) {
dx *= pixelsize;
@ -371,12 +371,24 @@ fill(
}
} else {
#if defined _WIN32 && !defined _WIN64
dx *= pixelsize;
for (y = 0; y < ysize; y++) {
UINT8 *out = (UINT8 *)imOut->image[y + dy] + dx;
for (x = 0; x < xsize; x++) {
for (i = 0; i < pixelsize; i++) {
*out++ = ink[i];
}
}
}
#else
for (y = 0; y < ysize; y++) {
INT32 *out = imOut->image32[y + dy] + dx;
for (x = 0; x < xsize; x++) {
out[x] = ink32;
}
}
#endif
}
}

View File

@ -175,8 +175,15 @@ ImagingSgiRleDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t
/* Get all data from File descriptor */
c = (SGISTATE *)state->context;
_imaging_seek_pyFd(state->fd, 0L, SEEK_END);
if (_imaging_seek_pyFd(state->fd, 0L, SEEK_END) == -1) {
state->errcode = IMAGING_CODEC_UNKNOWN;
return -1;
}
c->bufsize = _imaging_tell_pyFd(state->fd);
if (c->bufsize == -1) {
state->errcode = IMAGING_CODEC_UNKNOWN;
return -1;
}
c->bufsize -= SGI_HEADER_SIZE;
c->tablen = im->bands * im->ysize;
@ -194,8 +201,8 @@ ImagingSgiRleDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t
state->errcode = IMAGING_CODEC_MEMORY;
return -1;
}
_imaging_seek_pyFd(state->fd, SGI_HEADER_SIZE, SEEK_SET);
if (_imaging_read_pyFd(state->fd, (char *)ptr, c->bufsize) != c->bufsize) {
if (_imaging_seek_pyFd(state->fd, SGI_HEADER_SIZE, SEEK_SET) == -1 ||
_imaging_read_pyFd(state->fd, (char *)ptr, c->bufsize) != c->bufsize) {
free(ptr);
state->errcode = IMAGING_CODEC_UNKNOWN;
return -1;

View File

@ -677,9 +677,15 @@ ImagingNewArrow(
Imaging im;
struct ArrowSchema *schema =
(struct ArrowSchema *)PyCapsule_GetPointer(schema_capsule, "arrow_schema");
if (!schema) {
return NULL;
}
struct ArrowArray *external_array =
(struct ArrowArray *)PyCapsule_GetPointer(array_capsule, "arrow_array");
if (!external_array) {
return NULL;
}
if (xsize < 0 || ysize < 0) {
return (Imaging)ImagingError_ValueError("bad image size");

View File

@ -12,6 +12,9 @@ _imaging_read_pyFd(PyObject *fd, char *dest, Py_ssize_t bytes) {
int bytes_result;
result = PyObject_CallMethod(fd, "read", "n", bytes);
if (result == NULL) {
goto err;
}
bytes_result = PyBytes_AsStringAndSize(result, &buffer, &length);
if (bytes_result == -1) {
@ -28,7 +31,7 @@ _imaging_read_pyFd(PyObject *fd, char *dest, Py_ssize_t bytes) {
return length;
err:
Py_DECREF(result);
Py_XDECREF(result);
return -1;
}
@ -38,9 +41,16 @@ _imaging_write_pyFd(PyObject *fd, char *src, Py_ssize_t bytes) {
PyObject *byteObj;
byteObj = PyBytes_FromStringAndSize(src, bytes);
if (!byteObj) {
return -1;
}
result = PyObject_CallMethod(fd, "write", "O", byteObj);
Py_DECREF(byteObj);
if (result == NULL) {
return -1;
}
Py_DECREF(result);
return bytes;
@ -51,6 +61,9 @@ _imaging_seek_pyFd(PyObject *fd, Py_ssize_t offset, int whence) {
PyObject *result;
result = PyObject_CallMethod(fd, "seek", "ni", offset, whence);
if (result == NULL) {
return -1;
}
Py_DECREF(result);
return 0;
@ -62,6 +75,9 @@ _imaging_tell_pyFd(PyObject *fd) {
Py_ssize_t location;
result = PyObject_CallMethod(fd, "tell", NULL);
if (result == NULL) {
return -1;
}
location = PyLong_AsSsize_t(result);
Py_DECREF(result);

View File

@ -114,14 +114,14 @@ ARCHITECTURES = {
V = {
"BROTLI": "1.2.0",
"FREETYPE": "2.14.2",
"FREETYPE": "2.14.3",
"FRIBIDI": "1.0.16",
"HARFBUZZ": "13.2.1",
"JPEGTURBO": "3.1.3",
"LCMS2": "2.18",
"LIBAVIF": "1.4.1",
"LIBIMAGEQUANT": "4.4.1",
"LIBPNG": "1.6.55",
"LIBPNG": "1.6.56",
"LIBWEBP": "1.6.0",
"OPENJPEG": "2.5.4",
"TIFF": "4.7.1",
@ -542,14 +542,11 @@ def write_script(
def get_footer(dep: dict[str, Any]) -> list[str]:
lines = []
for out in dep.get("headers", []):
lines.append(cmd_copy(out, "{inc_dir}"))
for out in dep.get("libs", []):
lines.append(cmd_copy(out, "{lib_dir}"))
for out in dep.get("bins", []):
lines.append(cmd_copy(out, "{bin_dir}"))
return lines
return (
[cmd_copy(out, "{inc_dir}") for out in dep.get("headers", [])]
+ [cmd_copy(out, "{lib_dir}") for out in dep.get("libs", [])]
+ [cmd_copy(out, "{bin_dir}") for out in dep.get("bins", [])]
)
def build_env(prefs: dict[str, str], verbose: bool) -> None: