diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml index 7e771f1b7..3f78c98b6 100644 --- a/.github/workflows/cifuzz.yml +++ b/.github/workflows/cifuzz.yml @@ -24,6 +24,9 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +env: + FORCE_COLOR: 1 + jobs: Fuzzing: runs-on: ubuntu-latest diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml index a8ddef22c..12633284f 100644 --- a/.github/workflows/release-drafter.yml +++ b/.github/workflows/release-drafter.yml @@ -14,6 +14,9 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +env: + FORCE_COLOR: 1 + jobs: update_release_draft: permissions: diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 1b0c3c654..9d1902838 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -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' diff --git a/.github/workflows/test-docker.yml b/.github/workflows/test-docker.yml index 8f24bef3d..08226738e 100644 --- a/.github/workflows/test-docker.yml +++ b/.github/workflows/test-docker.yml @@ -26,6 +26,9 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +env: + FORCE_COLOR: 1 + jobs: build: diff --git a/.github/workflows/test-mingw.yml b/.github/workflows/test-mingw.yml index e247414c8..808373a65 100644 --- a/.github/workflows/test-mingw.yml +++ b/.github/workflows/test-mingw.yml @@ -28,6 +28,7 @@ concurrency: env: COVERAGE_CORE: sysmon + FORCE_COLOR: 1 jobs: build: diff --git a/.github/workflows/test-valgrind-memory.yml b/.github/workflows/test-valgrind-memory.yml index bd244aa5a..87eace643 100644 --- a/.github/workflows/test-valgrind-memory.yml +++ b/.github/workflows/test-valgrind-memory.yml @@ -26,6 +26,9 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +env: + FORCE_COLOR: 1 + jobs: build: diff --git a/.github/workflows/test-valgrind.yml b/.github/workflows/test-valgrind.yml index 81cfb8456..f14dab616 100644 --- a/.github/workflows/test-valgrind.yml +++ b/.github/workflows/test-valgrind.yml @@ -24,6 +24,9 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +env: + FORCE_COLOR: 1 + jobs: build: diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 3bc70e337..45392a689 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -28,6 +28,7 @@ concurrency: env: COVERAGE_CORE: sysmon + FORCE_COLOR: 1 jobs: build: diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index 85ce69e07..107eeae9b 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -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 diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index ed49f15c0..af2f9b3e8 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -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 diff --git a/Tests/test_bmp_reference.py b/Tests/test_bmp_reference.py index 8fbd73748..ea0853100 100644 --- a/Tests/test_bmp_reference.py +++ b/Tests/test_bmp_reference.py @@ -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"), diff --git a/Tests/test_file_container.py b/Tests/test_file_container.py index 597ab5083..c73f2a40c 100644 --- a/Tests/test_file_container.py +++ b/Tests/test_file_container.py @@ -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: diff --git a/Tests/test_file_jpeg2k.py b/Tests/test_file_jpeg2k.py index df686df32..0e60b59f5 100644 --- a/Tests/test_file_jpeg2k.py +++ b/Tests/test_file_jpeg2k.py @@ -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: diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index b453e3aa5..6f20900e4 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -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: diff --git a/Tests/test_file_mpo.py b/Tests/test_file_mpo.py index 4db62bd6d..7f35693f5 100644 --- a/Tests/test_file_mpo.py +++ b/Tests/test_file_mpo.py @@ -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() diff --git a/Tests/test_file_tga.py b/Tests/test_file_tga.py index 277515fd4..7ec562342 100644 --- a/Tests/test_file_tga.py +++ b/Tests/test_file_tga.py @@ -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" diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index c6c8467d6..e442471d1 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -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" diff --git a/depends/download-and-extract.sh b/depends/download-and-extract.sh index 04bfbc755..520104753 100755 --- a/depends/download-and-extract.sh +++ b/depends/download-and-extract.sh @@ -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 diff --git a/docs/releasenotes/12.2.0.rst b/docs/releasenotes/12.2.0.rst new file mode 100644 index 000000000..66526592a --- /dev/null +++ b/docs/releasenotes/12.2.0.rst @@ -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() + ... + + +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. diff --git a/docs/releasenotes/index.rst b/docs/releasenotes/index.rst index 690be2072..076872979 100644 --- a/docs/releasenotes/index.rst +++ b/docs/releasenotes/index.rst @@ -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 diff --git a/pyproject.toml b/pyproject.toml index 6d9910ca1..7eb9a3fbd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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 diff --git a/setup.py b/setup.py index 3d975950b..496c8cb1f 100644 --- a/setup.py +++ b/setup.py @@ -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"]), diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index 390b3b374..b8db5d832 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -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): diff --git a/src/PIL/IcnsImagePlugin.py b/src/PIL/IcnsImagePlugin.py index 023835fb7..cb7a74c2e 100644 --- a/src/PIL/IcnsImagePlugin.py +++ b/src/PIL/IcnsImagePlugin.py @@ -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)) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index f154cda2b..6062857da 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -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(" 4: - (offset,) = struct.unpack(" 4: + (offset,) = 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) diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index eb108ac41..506bb3b43 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -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] diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index 50e0075a2..df2a82b73 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -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 diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index ea7f4dc54..ec7c7cb08 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -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): diff --git a/src/PIL/ImagePalette.py b/src/PIL/ImagePalette.py index 99ad2771b..2abbd46ea 100644 --- a/src/PIL/ImagePalette.py +++ b/src/PIL/ImagePalette.py @@ -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: diff --git a/src/PIL/IptcImagePlugin.py b/src/PIL/IptcImagePlugin.py index 6fc824e4c..9c8be8b4e 100644 --- a/src/PIL/IptcImagePlugin.py +++ b/src/PIL/IptcImagePlugin.py @@ -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)} diff --git a/src/PIL/Jpeg2KImagePlugin.py b/src/PIL/Jpeg2KImagePlugin.py index e5d735c73..cb3773530 100644 --- a/src/PIL/Jpeg2KImagePlugin.py +++ b/src/PIL/Jpeg2KImagePlugin.py @@ -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) diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index 2f11cbfe3..46320eb3b 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -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": diff --git a/src/PIL/PcfFontFile.py b/src/PIL/PcfFontFile.py index a00e9b919..b923293b0 100644 --- a/src/PIL/PcfFontFile.py +++ b/src/PIL/PcfFontFile.py @@ -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 diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index 4e082a293..76a15bd0d 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -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 diff --git a/src/PIL/TgaImagePlugin.py b/src/PIL/TgaImagePlugin.py index 90d5b5cf4..b2989a4b7 100644 --- a/src/PIL/TgaImagePlugin.py +++ b/src/PIL/TgaImagePlugin.py @@ -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) diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index de2ce066e..3eec94dca 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -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} diff --git a/src/_avif.c b/src/_avif.c index 5e8b9fe8e..1fe0cb986 100644 --- a/src/_avif.c +++ b/src/_avif.c @@ -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", diff --git a/src/_imaging.c b/src/_imaging.c index af8360368..55d29d1bf 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -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( diff --git a/src/_imagingcms.c b/src/_imagingcms.c index ad3b27896..469be05f4 100644 --- a/src/_imagingcms.c +++ b/src/_imagingcms.c @@ -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); diff --git a/src/_imagingft.c b/src/_imagingft.c index 3be1bcb9a..5d91eaad6 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -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 */ diff --git a/src/_imagingmath.c b/src/_imagingmath.c index 48c395900..c04361468 100644 --- a/src/_imagingmath.c +++ b/src/_imagingmath.c @@ -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); diff --git a/src/_imagingmorph.c b/src/_imagingmorph.c index 5995f9d42..b6f307c84 100644 --- a/src/_imagingmorph.c +++ b/src/_imagingmorph.c @@ -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); } diff --git a/src/_webp.c b/src/_webp.c index d065e329c..115141273 100644 --- a/src/_webp.c +++ b/src/_webp.c @@ -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)) { diff --git a/src/decode.c b/src/decode.c index c5c9cf56f..cda4ce702 100644 --- a/src/decode.c +++ b/src/decode.c @@ -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; } diff --git a/src/display.c b/src/display.c index 5b5853a3c..944c60b70 100644 --- a/src/display.c +++ b/src/display.c @@ -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); } diff --git a/src/encode.c b/src/encode.c index f2bb464fa..1fc31404d 100644 --- a/src/encode.c +++ b/src/encode.c @@ -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; } diff --git a/src/libImaging/Paste.c b/src/libImaging/Paste.c index 25941ab3d..f01bce933 100644 --- a/src/libImaging/Paste.c +++ b/src/libImaging/Paste.c @@ -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 } } diff --git a/src/libImaging/SgiRleDecode.c b/src/libImaging/SgiRleDecode.c index a562f582c..2f5268b80 100644 --- a/src/libImaging/SgiRleDecode.c +++ b/src/libImaging/SgiRleDecode.c @@ -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; diff --git a/src/libImaging/Storage.c b/src/libImaging/Storage.c index c09062c92..c8175612e 100644 --- a/src/libImaging/Storage.c +++ b/src/libImaging/Storage.c @@ -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"); diff --git a/src/libImaging/codec_fd.c b/src/libImaging/codec_fd.c index 526168110..c5614e603 100644 --- a/src/libImaging/codec_fd.c +++ b/src/libImaging/codec_fd.c @@ -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); diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index be72ae93b..466cca176 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -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: