diff --git a/Tests/helper.py b/Tests/helper.py index 924b9733403..a55daf66f8f 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -7,7 +7,6 @@ import logging import os import shutil -import subprocess import sys import tempfile from functools import lru_cache @@ -282,16 +281,6 @@ def _cached_hopper(mode: str) -> Image.Image: return im -def djpeg_available() -> bool: - if shutil.which("djpeg"): - try: - subprocess.check_call(["djpeg", "-version"]) - return True - except subprocess.CalledProcessError: # pragma: no cover - return False - return False - - def netpbm_available() -> bool: return bool(shutil.which("ppmquant") and shutil.which("ppmtogif")) diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 0e16d867a3b..b66a5e1adc1 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -25,7 +25,6 @@ assert_image_equal_tofile, assert_image_similar, assert_image_similar_tofile, - djpeg_available, hopper, is_win32, mark_if_feature_version, @@ -730,13 +729,6 @@ def test_restart_markers(self, blocks: int, rows: int, markers: int) -> None: ) assert len(re.findall(b"\xff[\xd0-\xd7]", out.getvalue())) == markers - @pytest.mark.skipif(not djpeg_available(), reason="djpeg not available") - def test_load_djpeg(self) -> None: - with Image.open(TEST_FILE) as img: - assert isinstance(img, JpegImagePlugin.JpegImageFile) - img.load_djpeg() - assert_image_similar_tofile(img, TEST_FILE, 5) - def test_no_duplicate_0x1001_tag(self) -> None: # Arrange tag_ids = {v: k for k, v in ExifTags.TAGS.items()} diff --git a/Tests/test_shell_injection.py b/Tests/test_shell_injection.py index a7e95ed83c8..19058cae0ed 100644 --- a/Tests/test_shell_injection.py +++ b/Tests/test_shell_injection.py @@ -1,13 +1,12 @@ from __future__ import annotations -import shutil from io import BytesIO import pytest -from PIL import GifImagePlugin, Image, JpegImagePlugin +from PIL import GifImagePlugin, Image -from .helper import djpeg_available, is_win32, netpbm_available +from .helper import is_win32, netpbm_available TYPE_CHECKING = False if TYPE_CHECKING: @@ -36,16 +35,6 @@ def assert_save_filename_check( with Image.open(dest_file) as im: im.load() - @pytest.mark.skipif(not djpeg_available(), reason="djpeg not available") - def test_load_djpeg_filename(self, tmp_path: Path) -> None: - for filename in test_filenames: - src_file = tmp_path / filename - shutil.copy(TEST_JPG, src_file) - - with Image.open(src_file) as im: - assert isinstance(im, JpegImagePlugin.JpegImageFile) - im.load_djpeg() - @pytest.mark.skipif(not netpbm_available(), reason="Netpbm not available") def test_save_netpbm_filename_bmp_mode(self, tmp_path: Path) -> None: with Image.open(TEST_GIF) as im: diff --git a/docs/releasenotes/13.0.0.rst b/docs/releasenotes/13.0.0.rst index 45667d9ec07..8f4b2f711aa 100644 --- a/docs/releasenotes/13.0.0.rst +++ b/docs/releasenotes/13.0.0.rst @@ -66,6 +66,32 @@ ImageCms.ImageCmsProfile.product_name and .product_info ``.product_info`` attributes have been removed. They were set to ``None`` since Pillow 2.3.0. +JpegImageFile.load_djpeg() +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``JpegImageFile.load_djpeg()`` has been removed. + +It was undocumented, and delegated JPEG decoding to the external IJG ``djpeg`` +command line utility, requiring the image to be present as a file on disk. + +Pillow decodes JPEG images with libjpeg directly, +so :py:meth:`~PIL.Image.Image.load()` can be used instead:: + + from PIL import Image + with Image.open("hopper.jpg") as im: + im.load() + +If ``djpeg`` is still desired, it can be invoked directly. +It writes PPM to standard output, which Pillow can read:: + + import subprocess + from io import BytesIO + from PIL import Image + + data = subprocess.check_output(["djpeg", "hopper.jpg"]) + with Image.open(BytesIO(data)) as im: + im.load() + Deprecations ============ diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index 5d4347818d4..2d17b9ef57c 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -36,11 +36,8 @@ import array import io import math -import os import struct -import subprocess import sys -import tempfile import warnings from . import Image, ImageFile @@ -465,37 +462,6 @@ def draft( box = (0, 0, original_size[0] / scale, original_size[1] / scale) return self.mode, box - def load_djpeg(self) -> None: - # ALTERNATIVE: handle JPEGs via the IJG command line utilities - - f, path = tempfile.mkstemp() - os.close(f) - if os.path.exists(self.filename): - subprocess.check_call(["djpeg", "-outfile", path, self.filename]) - else: - try: - os.unlink(path) - except OSError: - pass - - msg = "Invalid Filename" - raise ValueError(msg) - - try: - with Image.open(path) as _im: - _im.load() - self.im = _im.im - finally: - try: - os.unlink(path) - except OSError: - pass - - self._mode = self.im.mode - self._size = self.im.size - - self.tile = [] - def _getexif(self) -> dict[int, Any] | None: return _getexif(self) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 2e8b3b82643..c4035087913 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -138,17 +138,16 @@ def cmd_msbuild( }, "build": [ *cmds_cmake( - ("jpeg-static", "djpeg-static"), + ("jpeg-static",), "-DENABLE_SHARED:BOOL=FALSE", "-DWITH_JPEG8:BOOL=TRUE", "-DWITH_CRT_DLL:BOOL=TRUE", ), cmd_copy("jpeg-static.lib", "libjpeg.lib"), - cmd_copy("djpeg-static.exe", "djpeg.exe"), ], "headers": ["jconfig.h", r"src\j*.h"], "libs": ["libjpeg.lib"], - "bins": ["djpeg.exe"], + "bins": [], }, "zlib": { "url": f"https://github.com/zlib-ng/zlib-ng/archive/refs/tags/{V['zlib-ng']}.tar.gz",