Merge branch 'main' into pdf
|  | @ -21,9 +21,11 @@ environment: | ||||||
| install: | install: | ||||||
| - '%PYTHON%\%EXECUTABLE% --version' | - '%PYTHON%\%EXECUTABLE% --version' | ||||||
| - curl -fsSL -o pillow-depends.zip https://github.com/python-pillow/pillow-depends/archive/main.zip | - curl -fsSL -o pillow-depends.zip https://github.com/python-pillow/pillow-depends/archive/main.zip | ||||||
|  | - curl -fsSL -o pillow-test-images.zip https://github.com/python-pillow/test-images/archive/main.zip | ||||||
| - 7z x pillow-depends.zip -oc:\ | - 7z x pillow-depends.zip -oc:\ | ||||||
|  | - 7z x pillow-test-images.zip -oc:\ | ||||||
| - mv c:\pillow-depends-main c:\pillow-depends | - mv c:\pillow-depends-main c:\pillow-depends | ||||||
| - xcopy /S /Y c:\pillow-depends\test_images\* c:\pillow\tests\images | - xcopy /S /Y c:\test-images-main\* c:\pillow\tests\images | ||||||
| - 7z x ..\pillow-depends\nasm-2.15.05-win64.zip -oc:\ | - 7z x ..\pillow-depends\nasm-2.15.05-win64.zip -oc:\ | ||||||
| - ..\pillow-depends\gs1000w32.exe /S | - ..\pillow-depends\gs1000w32.exe /S | ||||||
| - path c:\nasm-2.15.05;C:\Program Files (x86)\gs\gs10.0.0\bin;%PATH% | - path c:\nasm-2.15.05;C:\Program Files (x86)\gs\gs10.0.0\bin;%PATH% | ||||||
|  |  | ||||||
|  | @ -37,7 +37,8 @@ python3 -m pip install -U pytest-timeout | ||||||
| python3 -m pip install pyroma | python3 -m pip install pyroma | ||||||
| 
 | 
 | ||||||
| if [[ $(uname) != CYGWIN* ]]; then | if [[ $(uname) != CYGWIN* ]]; then | ||||||
|     python3 -m pip install numpy |     # TODO Remove condition when NumPy supports 3.12 | ||||||
|  |     if ! [ "$GHA_PYTHON_VERSION" == "3.12-dev" ]; then python3 -m pip install numpy ; fi | ||||||
| 
 | 
 | ||||||
|     # PyQt6 doesn't support PyPy3 |     # PyQt6 doesn't support PyPy3 | ||||||
|     if [[ $GHA_PYTHON_VERSION == 3.* ]]; then |     if [[ $GHA_PYTHON_VERSION == 3.* ]]; then | ||||||
|  |  | ||||||
							
								
								
									
										2
									
								
								.github/workflows/cifuzz.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						|  | @ -3,10 +3,12 @@ name: CIFuzz | ||||||
| on: | on: | ||||||
|   push: |   push: | ||||||
|     paths: |     paths: | ||||||
|  |       - ".github/workflows/cifuzz.yml" | ||||||
|       - "**.c" |       - "**.c" | ||||||
|       - "**.h" |       - "**.h" | ||||||
|   pull_request: |   pull_request: | ||||||
|     paths: |     paths: | ||||||
|  |       - ".github/workflows/cifuzz.yml" | ||||||
|       - "**.c" |       - "**.c" | ||||||
|       - "**.h" |       - "**.h" | ||||||
|   workflow_dispatch: |   workflow_dispatch: | ||||||
|  |  | ||||||
							
								
								
									
										52
									
								
								.github/workflows/docs.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,52 @@ | ||||||
|  | name: Docs | ||||||
|  | 
 | ||||||
|  | on: | ||||||
|  |   push: | ||||||
|  |     paths: | ||||||
|  |       - ".github/workflows/docs.yml" | ||||||
|  |       - "docs/**" | ||||||
|  |   pull_request: | ||||||
|  |     paths: | ||||||
|  |       - ".github/workflows/docs.yml" | ||||||
|  |       - "docs/**" | ||||||
|  |   workflow_dispatch: | ||||||
|  | 
 | ||||||
|  | permissions: | ||||||
|  |   contents: read | ||||||
|  | 
 | ||||||
|  | concurrency: | ||||||
|  |   group: ${{ github.workflow }}-${{ github.ref }} | ||||||
|  |   cancel-in-progress: true | ||||||
|  | 
 | ||||||
|  | jobs: | ||||||
|  |   build: | ||||||
|  | 
 | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |     name: Docs | ||||||
|  | 
 | ||||||
|  |     steps: | ||||||
|  |     - uses: actions/checkout@v3 | ||||||
|  | 
 | ||||||
|  |     - name: Set up Python | ||||||
|  |       uses: actions/setup-python@v4 | ||||||
|  |       with: | ||||||
|  |         python-version: "3.x" | ||||||
|  |         cache: pip | ||||||
|  |         cache-dependency-path: ".ci/*.sh" | ||||||
|  | 
 | ||||||
|  |     - name: Build system information | ||||||
|  |       run: python3 .github/workflows/system-info.py | ||||||
|  | 
 | ||||||
|  |     - name: Install Linux dependencies | ||||||
|  |       run: | | ||||||
|  |         .ci/install.sh | ||||||
|  |       env: | ||||||
|  |         GHA_PYTHON_VERSION: "3.x" | ||||||
|  | 
 | ||||||
|  |     - name: Build | ||||||
|  |       run: | | ||||||
|  |         .ci/build.sh | ||||||
|  | 
 | ||||||
|  |     - name: Docs | ||||||
|  |       run: | | ||||||
|  |         make doccheck | ||||||
							
								
								
									
										3
									
								
								.github/workflows/macos-install.sh
									
									
									
									
										vendored
									
									
								
							
							
						
						|  | @ -13,7 +13,8 @@ python3 -m pip install -U pytest-cov | ||||||
| python3 -m pip install -U pytest-timeout | python3 -m pip install -U pytest-timeout | ||||||
| python3 -m pip install pyroma | python3 -m pip install pyroma | ||||||
| 
 | 
 | ||||||
| python3 -m pip install numpy | # TODO Remove condition when NumPy supports 3.12 | ||||||
|  | if ! [ "$GHA_PYTHON_VERSION" == "3.12-dev" ]; then python3 -m pip install numpy ; fi | ||||||
| 
 | 
 | ||||||
| # extra test images | # extra test images | ||||||
| pushd depends && ./install_extra_test_images.sh && popd | pushd depends && ./install_extra_test_images.sh && popd | ||||||
|  |  | ||||||
							
								
								
									
										13
									
								
								.github/workflows/test-cygwin.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						|  | @ -1,6 +1,15 @@ | ||||||
| name: Test Cygwin | name: Test Cygwin | ||||||
| 
 | 
 | ||||||
| on: [push, pull_request, workflow_dispatch] | on: | ||||||
|  |   push: | ||||||
|  |     paths-ignore: | ||||||
|  |       - ".github/workflows/docs.yml" | ||||||
|  |       - "docs/**" | ||||||
|  |   pull_request: | ||||||
|  |     paths-ignore: | ||||||
|  |       - ".github/workflows/docs.yml" | ||||||
|  |       - "docs/**" | ||||||
|  |   workflow_dispatch: | ||||||
| 
 | 
 | ||||||
| permissions: | permissions: | ||||||
|   contents: read |   contents: read | ||||||
|  | @ -59,7 +68,7 @@ jobs: | ||||||
|             python3${{ matrix.python-minor-version }}-sip |             python3${{ matrix.python-minor-version }}-sip | ||||||
|             python3${{ matrix.python-minor-version }}-tkinter |             python3${{ matrix.python-minor-version }}-tkinter | ||||||
|             qt5-devel-tools |             qt5-devel-tools | ||||||
|             subversion |             wget | ||||||
|             xorg-server-extra |             xorg-server-extra | ||||||
|             zlib-devel |             zlib-devel | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										12
									
								
								.github/workflows/test-docker.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						|  | @ -1,6 +1,15 @@ | ||||||
| name: Test Docker | name: Test Docker | ||||||
| 
 | 
 | ||||||
| on: [push, pull_request, workflow_dispatch] | on: | ||||||
|  |   push: | ||||||
|  |     paths-ignore: | ||||||
|  |       - ".github/workflows/docs.yml" | ||||||
|  |       - "docs/**" | ||||||
|  |   pull_request: | ||||||
|  |     paths-ignore: | ||||||
|  |       - ".github/workflows/docs.yml" | ||||||
|  |       - "docs/**" | ||||||
|  |   workflow_dispatch: | ||||||
| 
 | 
 | ||||||
| permissions: | permissions: | ||||||
|   contents: read |   contents: read | ||||||
|  | @ -87,6 +96,7 @@ jobs: | ||||||
|       with: |       with: | ||||||
|         flags: GHA_Docker |         flags: GHA_Docker | ||||||
|         name: ${{ matrix.docker }} |         name: ${{ matrix.docker }} | ||||||
|  |         gcov: true | ||||||
| 
 | 
 | ||||||
|   success: |   success: | ||||||
|     permissions: |     permissions: | ||||||
|  |  | ||||||
							
								
								
									
										14
									
								
								.github/workflows/test-mingw.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						|  | @ -1,6 +1,15 @@ | ||||||
| name: Test MinGW | name: Test MinGW | ||||||
| 
 | 
 | ||||||
| on: [push, pull_request, workflow_dispatch] | on: | ||||||
|  |   push: | ||||||
|  |     paths-ignore: | ||||||
|  |       - ".github/workflows/docs.yml" | ||||||
|  |       - "docs/**" | ||||||
|  |   pull_request: | ||||||
|  |     paths-ignore: | ||||||
|  |       - ".github/workflows/docs.yml" | ||||||
|  |       - "docs/**" | ||||||
|  |   workflow_dispatch: | ||||||
| 
 | 
 | ||||||
| permissions: | permissions: | ||||||
|   contents: read |   contents: read | ||||||
|  | @ -59,8 +68,7 @@ jobs: | ||||||
|               ${{ matrix.package }}-python3-numpy \ |               ${{ matrix.package }}-python3-numpy \ | ||||||
|               ${{ matrix.package }}-python3-olefile \ |               ${{ matrix.package }}-python3-olefile \ | ||||||
|               ${{ matrix.package }}-python3-pip \ |               ${{ matrix.package }}-python3-pip \ | ||||||
|               ${{ matrix.package }}-python3-setuptools \ |               ${{ matrix.package }}-python3-setuptools | ||||||
|               subversion |  | ||||||
| 
 | 
 | ||||||
|           if [ ${{ matrix.package }} == "mingw-w64-x86_64" ]; then |           if [ ${{ matrix.package }} == "mingw-w64-x86_64" ]; then | ||||||
|               pacman -S --noconfirm \ |               pacman -S --noconfirm \ | ||||||
|  |  | ||||||
							
								
								
									
										2
									
								
								.github/workflows/test-valgrind.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						|  | @ -5,10 +5,12 @@ name: Test Valgrind | ||||||
| on: | on: | ||||||
|   push: |   push: | ||||||
|     paths: |     paths: | ||||||
|  |       - ".github/workflows/test-valgrind.yml" | ||||||
|       - "**.c" |       - "**.c" | ||||||
|       - "**.h" |       - "**.h" | ||||||
|   pull_request: |   pull_request: | ||||||
|     paths: |     paths: | ||||||
|  |       - ".github/workflows/test-valgrind.yml" | ||||||
|       - "**.c" |       - "**.c" | ||||||
|       - "**.h" |       - "**.h" | ||||||
|   workflow_dispatch: |   workflow_dispatch: | ||||||
|  |  | ||||||
							
								
								
									
										22
									
								
								.github/workflows/test-windows.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						|  | @ -1,6 +1,15 @@ | ||||||
| name: Test Windows | name: Test Windows | ||||||
| 
 | 
 | ||||||
| on: [push, pull_request, workflow_dispatch] | on: | ||||||
|  |   push: | ||||||
|  |     paths-ignore: | ||||||
|  |       - ".github/workflows/docs.yml" | ||||||
|  |       - "docs/**" | ||||||
|  |   pull_request: | ||||||
|  |     paths-ignore: | ||||||
|  |       - ".github/workflows/docs.yml" | ||||||
|  |       - "docs/**" | ||||||
|  |   workflow_dispatch: | ||||||
| 
 | 
 | ||||||
| permissions: | permissions: | ||||||
|   contents: read |   contents: read | ||||||
|  | @ -15,7 +24,7 @@ jobs: | ||||||
|     strategy: |     strategy: | ||||||
|       fail-fast: false |       fail-fast: false | ||||||
|       matrix: |       matrix: | ||||||
|         python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] |         python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12-dev"] | ||||||
|         architecture: ["x86", "x64"] |         architecture: ["x86", "x64"] | ||||||
|         include: |         include: | ||||||
|           # PyPy 7.3.4+ only ships 64-bit binaries for Windows |           # PyPy 7.3.4+ only ships 64-bit binaries for Windows | ||||||
|  | @ -38,6 +47,12 @@ jobs: | ||||||
|         repository: python-pillow/pillow-depends |         repository: python-pillow/pillow-depends | ||||||
|         path: winbuild\depends |         path: winbuild\depends | ||||||
| 
 | 
 | ||||||
|  |     - name: Checkout extra test images | ||||||
|  |       uses: actions/checkout@v3 | ||||||
|  |       with: | ||||||
|  |         repository: python-pillow/test-images | ||||||
|  |         path: Tests\test-images | ||||||
|  | 
 | ||||||
|     # sets env: pythonLocation |     # sets env: pythonLocation | ||||||
|     - name: Set up Python |     - name: Set up Python | ||||||
|       uses: actions/setup-python@v4 |       uses: actions/setup-python@v4 | ||||||
|  | @ -62,7 +77,8 @@ jobs: | ||||||
|         winbuild\depends\gs1000w32.exe /S |         winbuild\depends\gs1000w32.exe /S | ||||||
|         echo "C:\Program Files (x86)\gs\gs10.0.0\bin" >> $env:GITHUB_PATH |         echo "C:\Program Files (x86)\gs\gs10.0.0\bin" >> $env:GITHUB_PATH | ||||||
| 
 | 
 | ||||||
|         xcopy /S /Y winbuild\depends\test_images\* Tests\images\ |         # Install extra test images | ||||||
|  |         xcopy /S /Y Tests\test-images\* Tests\images | ||||||
| 
 | 
 | ||||||
|         # make cache key depend on VS version |         # make cache key depend on VS version | ||||||
|         & "C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" ` |         & "C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" ` | ||||||
|  |  | ||||||
							
								
								
									
										19
									
								
								.github/workflows/test.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						|  | @ -1,6 +1,15 @@ | ||||||
| name: Test | name: Test | ||||||
| 
 | 
 | ||||||
| on: [push, pull_request, workflow_dispatch] | on: | ||||||
|  |   push: | ||||||
|  |     paths-ignore: | ||||||
|  |       - ".github/workflows/docs.yml" | ||||||
|  |       - "docs/**" | ||||||
|  |   pull_request: | ||||||
|  |     paths-ignore: | ||||||
|  |       - ".github/workflows/docs.yml" | ||||||
|  |       - "docs/**" | ||||||
|  |   workflow_dispatch: | ||||||
| 
 | 
 | ||||||
| permissions: | permissions: | ||||||
|   contents: read |   contents: read | ||||||
|  | @ -22,6 +31,7 @@ jobs: | ||||||
|         python-version: [ |         python-version: [ | ||||||
|           "pypy3.9", |           "pypy3.9", | ||||||
|           "pypy3.8", |           "pypy3.8", | ||||||
|  |           "3.12-dev", | ||||||
|           "3.11", |           "3.11", | ||||||
|           "3.10", |           "3.10", | ||||||
|           "3.9", |           "3.9", | ||||||
|  | @ -95,11 +105,6 @@ jobs: | ||||||
|         name: errors |         name: errors | ||||||
|         path: Tests/errors |         path: Tests/errors | ||||||
| 
 | 
 | ||||||
|     - name: Docs |  | ||||||
|       if: startsWith(matrix.os, 'ubuntu') && matrix.python-version == 3.11 |  | ||||||
|       run: | |  | ||||||
|         make doccheck |  | ||||||
| 
 |  | ||||||
|     - name: After success |     - name: After success | ||||||
|       run: | |       run: | | ||||||
|         .ci/after_success.sh |         .ci/after_success.sh | ||||||
|  | @ -107,9 +112,9 @@ jobs: | ||||||
|     - name: Upload coverage |     - name: Upload coverage | ||||||
|       uses: codecov/codecov-action@v3 |       uses: codecov/codecov-action@v3 | ||||||
|       with: |       with: | ||||||
|         file: ./coverage.xml |  | ||||||
|         flags: ${{ matrix.os == 'macos-latest' && 'GHA_macOS' || 'GHA_Ubuntu' }} |         flags: ${{ matrix.os == 'macos-latest' && 'GHA_macOS' || 'GHA_Ubuntu' }} | ||||||
|         name: ${{ matrix.os }} Python ${{ matrix.python-version }} |         name: ${{ matrix.os }} Python ${{ matrix.python-version }} | ||||||
|  |         gcov: true | ||||||
| 
 | 
 | ||||||
|   success: |   success: | ||||||
|     permissions: |     permissions: | ||||||
|  |  | ||||||
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						|  | @ -79,7 +79,7 @@ docs/_build/ | ||||||
| # JetBrains | # JetBrains | ||||||
| .idea | .idea | ||||||
| 
 | 
 | ||||||
| # Extra test images installed from pillow-depends/test_images | # Extra test images installed from python-pillow/test-images | ||||||
| Tests/images/README.md | Tests/images/README.md | ||||||
| Tests/images/crash_1.tif | Tests/images/crash_1.tif | ||||||
| Tests/images/crash_2.tif | Tests/images/crash_2.tif | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| repos: | repos: | ||||||
|   - repo: https://github.com/psf/black |   - repo: https://github.com/psf/black | ||||||
|     rev: 22.12.0 |     rev: 23.1.0 | ||||||
|     hooks: |     hooks: | ||||||
|       - id: black |       - id: black | ||||||
|         args: [--target-version=py37] |         args: [--target-version=py37] | ||||||
|  |  | ||||||
							
								
								
									
										33
									
								
								CHANGES.rst
									
									
									
									
									
								
							
							
						
						|  | @ -5,6 +5,39 @@ Changelog (Pillow) | ||||||
| 9.5.0 (unreleased) | 9.5.0 (unreleased) | ||||||
| ------------------ | ------------------ | ||||||
| 
 | 
 | ||||||
|  | - Close OleFileIO instance when closing or exiting FPX or MIC #7005 | ||||||
|  |   [radarhere] | ||||||
|  | 
 | ||||||
|  | - Added __int__ to IFDRational for Python >= 3.11 #6998 | ||||||
|  |   [radarhere] | ||||||
|  | 
 | ||||||
|  | - Added memoryview support to Dib.frombytes() #6988 | ||||||
|  |   [radarhere, nulano] | ||||||
|  | 
 | ||||||
|  | - Close file pointer copy in the libtiff encoder if still open #6986 | ||||||
|  |   [fcarron, radarhere] | ||||||
|  | 
 | ||||||
|  | - Raise an error if ImageDraw co-ordinates are incorrectly ordered #6978 | ||||||
|  |   [radarhere] | ||||||
|  | 
 | ||||||
|  | - Added "corners" argument to ImageDraw rounded_rectangle() #6954 | ||||||
|  |   [radarhere] | ||||||
|  | 
 | ||||||
|  | - Added memoryview support to frombytes() #6974 | ||||||
|  |   [radarhere] | ||||||
|  | 
 | ||||||
|  | - Allow comments in FITS images #6973 | ||||||
|  |   [radarhere] | ||||||
|  | 
 | ||||||
|  | - Support saving PDF with different X and Y resolutions #6961 | ||||||
|  |   [jvanderneutstulen, radarhere, hugovk] | ||||||
|  | 
 | ||||||
|  | - Fixed writing int as UNDEFINED tag #6950 | ||||||
|  |   [radarhere] | ||||||
|  | 
 | ||||||
|  | - Raise an error if EXIF data is too long when saving JPEG #6939 | ||||||
|  |   [radarhere] | ||||||
|  | 
 | ||||||
| - Handle more than one directory returned by pkg-config #6896 | - Handle more than one directory returned by pkg-config #6896 | ||||||
|   [sebastic, radarhere] |   [sebastic, radarhere] | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										4
									
								
								LICENSE
									
									
									
									
									
								
							
							
						
						|  | @ -13,8 +13,8 @@ By obtaining, using, and/or copying this software and/or its associated | ||||||
| documentation, you agree that you have read, understood, and will comply | documentation, you agree that you have read, understood, and will comply | ||||||
| with the following terms and conditions: | with the following terms and conditions: | ||||||
| 
 | 
 | ||||||
| Permission to use, copy, modify, and distribute this software and its | Permission to use, copy, modify and distribute this software and its | ||||||
| associated documentation for any purpose and without fee is hereby granted, | documentation for any purpose and without fee is hereby granted, | ||||||
| provided that the above copyright notice appears in all copies, and that | provided that the above copyright notice appears in all copies, and that | ||||||
| both that copyright notice and this permission notice appear in supporting | both that copyright notice and this permission notice appear in supporting | ||||||
| documentation, and that the name of Secret Labs AB or the author not be | documentation, and that the name of Secret Labs AB or the author not be | ||||||
|  |  | ||||||
|  | @ -4,7 +4,6 @@ TEST_FILE = "Tests/images/fli_overflow.fli" | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_fli_overflow(): | def test_fli_overflow(): | ||||||
| 
 |  | ||||||
|     # this should not crash with a malloc error or access violation |     # this should not crash with a malloc error or access violation | ||||||
|     with Image.open(TEST_FILE) as im: |     with Image.open(TEST_FILE) as im: | ||||||
|         im.load() |         im.load() | ||||||
|  |  | ||||||
|  | @ -23,7 +23,6 @@ def test_ignore_dos_text(): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_dos_text(): | def test_dos_text(): | ||||||
| 
 |  | ||||||
|     try: |     try: | ||||||
|         im = Image.open(TEST_FILE) |         im = Image.open(TEST_FILE) | ||||||
|         im.load() |         im.load() | ||||||
|  |  | ||||||
|  | @ -20,7 +20,7 @@ logger = logging.getLogger(__name__) | ||||||
| 
 | 
 | ||||||
| HAS_UPLOADER = False | HAS_UPLOADER = False | ||||||
| 
 | 
 | ||||||
| if os.environ.get("SHOW_ERRORS", None): | if os.environ.get("SHOW_ERRORS"): | ||||||
|     # local img.show for errors. |     # local img.show for errors. | ||||||
|     HAS_UPLOADER = True |     HAS_UPLOADER = True | ||||||
| 
 | 
 | ||||||
|  | @ -271,7 +271,7 @@ def netpbm_available(): | ||||||
| 
 | 
 | ||||||
| def magick_command(): | def magick_command(): | ||||||
|     if sys.platform == "win32": |     if sys.platform == "win32": | ||||||
|         magickhome = os.environ.get("MAGICK_HOME", "") |         magickhome = os.environ.get("MAGICK_HOME") | ||||||
|         if magickhome: |         if magickhome: | ||||||
|             imagemagick = [os.path.join(magickhome, "convert.exe")] |             imagemagick = [os.path.join(magickhome, "convert.exe")] | ||||||
|             graphicsmagick = [os.path.join(magickhome, "gm.exe"), "convert"] |             graphicsmagick = [os.path.join(magickhome, "gm.exe"), "convert"] | ||||||
|  |  | ||||||
| Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB | 
| Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/imagedraw_rounded_rectangle_corners_nnnn.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 544 B | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/imagedraw_rounded_rectangle_corners_nnny.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 685 B | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/imagedraw_rounded_rectangle_corners_nnyn.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 649 B | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/imagedraw_rounded_rectangle_corners_nnyy.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 755 B | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/imagedraw_rounded_rectangle_corners_nynn.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 643 B | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/imagedraw_rounded_rectangle_corners_nyny.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 775 B | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/imagedraw_rounded_rectangle_corners_nyyn.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 741 B | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/imagedraw_rounded_rectangle_corners_nyyy.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 844 B | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/imagedraw_rounded_rectangle_corners_ynnn.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 656 B | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/imagedraw_rounded_rectangle_corners_ynny.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 785 B | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/imagedraw_rounded_rectangle_corners_ynyn.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 752 B | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/imagedraw_rounded_rectangle_corners_ynyy.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 856 B | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/imagedraw_rounded_rectangle_corners_yynn.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 737 B | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/imagedraw_rounded_rectangle_corners_yyny.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 870 B | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/imagedraw_rounded_rectangle_corners_yyyn.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 835 B | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/imagedraw_rounded_rectangle_corners_yyyy.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 934 B | 
|  | @ -18,7 +18,6 @@ def test_bad(): | ||||||
|     """These shouldn't crash/dos, but they shouldn't return anything |     """These shouldn't crash/dos, but they shouldn't return anything | ||||||
|     either""" |     either""" | ||||||
|     for f in get_files("b"): |     for f in get_files("b"): | ||||||
| 
 |  | ||||||
|         # Assert that there is no unclosed file warning |         # Assert that there is no unclosed file warning | ||||||
|         with warnings.catch_warnings(): |         with warnings.catch_warnings(): | ||||||
|             try: |             try: | ||||||
|  |  | ||||||
|  | @ -177,13 +177,14 @@ class TestEnvVars: | ||||||
|         Image._apply_env_variables({"PILLOW_BLOCK_SIZE": "2m"}) |         Image._apply_env_variables({"PILLOW_BLOCK_SIZE": "2m"}) | ||||||
|         assert Image.core.get_block_size() == 2 * 1024 * 1024 |         assert Image.core.get_block_size() == 2 * 1024 * 1024 | ||||||
| 
 | 
 | ||||||
|     def test_warnings(self): |     @pytest.mark.parametrize( | ||||||
|         pytest.warns( |         "var", | ||||||
|             UserWarning, Image._apply_env_variables, {"PILLOW_ALIGNMENT": "15"} |         ( | ||||||
|         ) |             {"PILLOW_ALIGNMENT": "15"}, | ||||||
|         pytest.warns( |             {"PILLOW_BLOCK_SIZE": "1024"}, | ||||||
|             UserWarning, Image._apply_env_variables, {"PILLOW_BLOCK_SIZE": "1024"} |             {"PILLOW_BLOCKS_MAX": "wat"}, | ||||||
|         ) |         ), | ||||||
|         pytest.warns( |     ) | ||||||
|             UserWarning, Image._apply_env_variables, {"PILLOW_BLOCKS_MAX": "wat"} |     def test_warnings(self, var): | ||||||
|         ) |         with pytest.warns(UserWarning): | ||||||
|  |             Image._apply_env_variables(var) | ||||||
|  |  | ||||||
|  | @ -36,12 +36,10 @@ class TestDecompressionBomb: | ||||||
|         Image.MAX_IMAGE_PIXELS = 128 * 128 - 1 |         Image.MAX_IMAGE_PIXELS = 128 * 128 - 1 | ||||||
|         assert Image.MAX_IMAGE_PIXELS == 128 * 128 - 1 |         assert Image.MAX_IMAGE_PIXELS == 128 * 128 - 1 | ||||||
| 
 | 
 | ||||||
|         def open(): |         with pytest.warns(Image.DecompressionBombWarning): | ||||||
|             with Image.open(TEST_FILE): |             with Image.open(TEST_FILE): | ||||||
|                 pass |                 pass | ||||||
| 
 | 
 | ||||||
|         pytest.warns(Image.DecompressionBombWarning, open) |  | ||||||
| 
 |  | ||||||
|     def test_exception(self): |     def test_exception(self): | ||||||
|         # Set limit to trigger exception on the test file |         # Set limit to trigger exception on the test file | ||||||
|         Image.MAX_IMAGE_PIXELS = 64 * 128 - 1 |         Image.MAX_IMAGE_PIXELS = 64 * 128 - 1 | ||||||
|  | @ -87,7 +85,8 @@ class TestDecompressionCrop: | ||||||
|         # same decompression bomb warnings on them. |         # same decompression bomb warnings on them. | ||||||
|         with hopper() as src: |         with hopper() as src: | ||||||
|             box = (0, 0, src.width * 2, src.height * 2) |             box = (0, 0, src.width * 2, src.height * 2) | ||||||
|             pytest.warns(Image.DecompressionBombWarning, src.crop, box) |             with pytest.warns(Image.DecompressionBombWarning): | ||||||
|  |                 src.crop(box) | ||||||
| 
 | 
 | ||||||
|     def test_crop_decompression_checks(self): |     def test_crop_decompression_checks(self): | ||||||
|         im = Image.new("RGB", (100, 100)) |         im = Image.new("RGB", (100, 100)) | ||||||
|  | @ -95,7 +94,8 @@ class TestDecompressionCrop: | ||||||
|         for value in ((-9999, -9999, -9990, -9990), (-999, -999, -990, -990)): |         for value in ((-9999, -9999, -9990, -9990), (-999, -999, -990, -990)): | ||||||
|             assert im.crop(value).size == (9, 9) |             assert im.crop(value).size == (9, 9) | ||||||
| 
 | 
 | ||||||
|         pytest.warns(Image.DecompressionBombWarning, im.crop, (-160, -160, 99, 99)) |         with pytest.warns(Image.DecompressionBombWarning): | ||||||
|  |             im.crop((-160, -160, 99, 99)) | ||||||
| 
 | 
 | ||||||
|         with pytest.raises(Image.DecompressionBombError): |         with pytest.raises(Image.DecompressionBombError): | ||||||
|             im.crop((-99909, -99990, 99999, 99999)) |             im.crop((-99909, -99990, 99999, 99999)) | ||||||
|  |  | ||||||
|  | @ -263,13 +263,11 @@ def test_apng_chunk_errors(): | ||||||
|     with Image.open("Tests/images/apng/chunk_no_actl.png") as im: |     with Image.open("Tests/images/apng/chunk_no_actl.png") as im: | ||||||
|         assert not im.is_animated |         assert not im.is_animated | ||||||
| 
 | 
 | ||||||
|     def open(): |     with pytest.warns(UserWarning): | ||||||
|         with Image.open("Tests/images/apng/chunk_multi_actl.png") as im: |         with Image.open("Tests/images/apng/chunk_multi_actl.png") as im: | ||||||
|             im.load() |             im.load() | ||||||
|         assert not im.is_animated |         assert not im.is_animated | ||||||
| 
 | 
 | ||||||
|     pytest.warns(UserWarning, open) |  | ||||||
| 
 |  | ||||||
|     with Image.open("Tests/images/apng/chunk_actl_after_idat.png") as im: |     with Image.open("Tests/images/apng/chunk_actl_after_idat.png") as im: | ||||||
|         assert not im.is_animated |         assert not im.is_animated | ||||||
| 
 | 
 | ||||||
|  | @ -287,21 +285,17 @@ def test_apng_chunk_errors(): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_apng_syntax_errors(): | def test_apng_syntax_errors(): | ||||||
|     def open_frames_zero(): |     with pytest.warns(UserWarning): | ||||||
|         with Image.open("Tests/images/apng/syntax_num_frames_zero.png") as im: |         with Image.open("Tests/images/apng/syntax_num_frames_zero.png") as im: | ||||||
|             assert not im.is_animated |             assert not im.is_animated | ||||||
|             with pytest.raises(OSError): |             with pytest.raises(OSError): | ||||||
|                 im.load() |                 im.load() | ||||||
| 
 | 
 | ||||||
|     pytest.warns(UserWarning, open_frames_zero) |     with pytest.warns(UserWarning): | ||||||
| 
 |  | ||||||
|     def open_frames_zero_default(): |  | ||||||
|         with Image.open("Tests/images/apng/syntax_num_frames_zero_default.png") as im: |         with Image.open("Tests/images/apng/syntax_num_frames_zero_default.png") as im: | ||||||
|             assert not im.is_animated |             assert not im.is_animated | ||||||
|             im.load() |             im.load() | ||||||
| 
 | 
 | ||||||
|     pytest.warns(UserWarning, open_frames_zero_default) |  | ||||||
| 
 |  | ||||||
|     # we can handle this case gracefully |     # we can handle this case gracefully | ||||||
|     exception = None |     exception = None | ||||||
|     with Image.open("Tests/images/apng/syntax_num_frames_low.png") as im: |     with Image.open("Tests/images/apng/syntax_num_frames_low.png") as im: | ||||||
|  | @ -316,13 +310,11 @@ def test_apng_syntax_errors(): | ||||||
|             im.seek(im.n_frames - 1) |             im.seek(im.n_frames - 1) | ||||||
|             im.load() |             im.load() | ||||||
| 
 | 
 | ||||||
|     def open(): |     with pytest.warns(UserWarning): | ||||||
|         with Image.open("Tests/images/apng/syntax_num_frames_invalid.png") as im: |         with Image.open("Tests/images/apng/syntax_num_frames_invalid.png") as im: | ||||||
|             assert not im.is_animated |             assert not im.is_animated | ||||||
|             im.load() |             im.load() | ||||||
| 
 | 
 | ||||||
|     pytest.warns(UserWarning, open) |  | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
| @pytest.mark.parametrize( | @pytest.mark.parametrize( | ||||||
|     "test_file", |     "test_file", | ||||||
|  |  | ||||||
|  | @ -141,7 +141,6 @@ def test_rgba_bitfields(): | ||||||
|     # This test image has been manually hexedited |     # This test image has been manually hexedited | ||||||
|     # to change the bitfield compression in the header from XBGR to RGBA |     # to change the bitfield compression in the header from XBGR to RGBA | ||||||
|     with Image.open("Tests/images/rgb32bf-rgba.bmp") as im: |     with Image.open("Tests/images/rgb32bf-rgba.bmp") as im: | ||||||
| 
 |  | ||||||
|         # So before the comparing the image, swap the channels |         # So before the comparing the image, swap the channels | ||||||
|         b, g, r = im.split()[1:] |         b, g, r = im.split()[1:] | ||||||
|         im = Image.merge("RGB", (r, g, b)) |         im = Image.merge("RGB", (r, g, b)) | ||||||
|  |  | ||||||
|  | @ -10,7 +10,6 @@ TEST_FILE = "Tests/images/gfs.t06z.rassda.tm00.bufr_d" | ||||||
| def test_open(): | def test_open(): | ||||||
|     # Act |     # Act | ||||||
|     with Image.open(TEST_FILE) as im: |     with Image.open(TEST_FILE) as im: | ||||||
| 
 |  | ||||||
|         # Assert |         # Assert | ||||||
|         assert im.format == "BUFR" |         assert im.format == "BUFR" | ||||||
| 
 | 
 | ||||||
|  | @ -31,7 +30,6 @@ def test_invalid_file(): | ||||||
| def test_load(): | def test_load(): | ||||||
|     # Arrange |     # Arrange | ||||||
|     with Image.open(TEST_FILE) as im: |     with Image.open(TEST_FILE) as im: | ||||||
| 
 |  | ||||||
|         # Act / Assert: stub cannot load without an implemented handler |         # Act / Assert: stub cannot load without an implemented handler | ||||||
|         with pytest.raises(OSError): |         with pytest.raises(OSError): | ||||||
|             im.load() |             im.load() | ||||||
|  | @ -58,6 +56,7 @@ def test_handler(tmp_path): | ||||||
| 
 | 
 | ||||||
|         def load(self, im): |         def load(self, im): | ||||||
|             self.loaded = True |             self.loaded = True | ||||||
|  |             im.fp.close() | ||||||
|             return Image.new("RGB", (1, 1)) |             return Image.new("RGB", (1, 1)) | ||||||
| 
 | 
 | ||||||
|         def save(self, im, fp, filename): |         def save(self, im, fp, filename): | ||||||
|  |  | ||||||
|  | @ -15,7 +15,6 @@ def test_sanity(): | ||||||
| 
 | 
 | ||||||
|     # Act |     # Act | ||||||
|     with Image.open(TEST_FILE) as im: |     with Image.open(TEST_FILE) as im: | ||||||
| 
 |  | ||||||
|         # Assert |         # Assert | ||||||
|         assert im.size == (128, 128) |         assert im.size == (128, 128) | ||||||
|         assert isinstance(im, DcxImagePlugin.DcxImageFile) |         assert isinstance(im, DcxImagePlugin.DcxImageFile) | ||||||
|  | @ -29,7 +28,8 @@ def test_unclosed_file(): | ||||||
|         im = Image.open(TEST_FILE) |         im = Image.open(TEST_FILE) | ||||||
|         im.load() |         im.load() | ||||||
| 
 | 
 | ||||||
|     pytest.warns(ResourceWarning, open) |     with pytest.warns(ResourceWarning): | ||||||
|  |         open() | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_closed_file(): | def test_closed_file(): | ||||||
|  | @ -54,7 +54,6 @@ def test_invalid_file(): | ||||||
| def test_tell(): | def test_tell(): | ||||||
|     # Arrange |     # Arrange | ||||||
|     with Image.open(TEST_FILE) as im: |     with Image.open(TEST_FILE) as im: | ||||||
| 
 |  | ||||||
|         # Act |         # Act | ||||||
|         frame = im.tell() |         frame = im.tell() | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -80,7 +80,6 @@ def test_invalid_file(): | ||||||
| @pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") | @pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") | ||||||
| def test_cmyk(): | def test_cmyk(): | ||||||
|     with Image.open("Tests/images/pil_sample_cmyk.eps") as cmyk_image: |     with Image.open("Tests/images/pil_sample_cmyk.eps") as cmyk_image: | ||||||
| 
 |  | ||||||
|         assert cmyk_image.mode == "CMYK" |         assert cmyk_image.mode == "CMYK" | ||||||
|         assert cmyk_image.size == (100, 100) |         assert cmyk_image.size == (100, 100) | ||||||
|         assert cmyk_image.format == "EPS" |         assert cmyk_image.format == "EPS" | ||||||
|  |  | ||||||
|  | @ -12,7 +12,6 @@ TEST_FILE = "Tests/images/hopper.fits" | ||||||
| def test_open(): | def test_open(): | ||||||
|     # Act |     # Act | ||||||
|     with Image.open(TEST_FILE) as im: |     with Image.open(TEST_FILE) as im: | ||||||
| 
 |  | ||||||
|         # Assert |         # Assert | ||||||
|         assert im.format == "FITS" |         assert im.format == "FITS" | ||||||
|         assert im.size == (128, 128) |         assert im.size == (128, 128) | ||||||
|  | @ -45,6 +44,12 @@ def test_naxis_zero(): | ||||||
|             pass |             pass | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | def test_comment(): | ||||||
|  |     image_data = b"SIMPLE  =                    T / comment string" | ||||||
|  |     with pytest.raises(OSError): | ||||||
|  |         FitsImagePlugin.FitsImageFile(BytesIO(image_data)) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| def test_stub_deprecated(): | def test_stub_deprecated(): | ||||||
|     class Handler: |     class Handler: | ||||||
|         opened = False |         opened = False | ||||||
|  | @ -55,6 +60,7 @@ def test_stub_deprecated(): | ||||||
| 
 | 
 | ||||||
|         def load(self, im): |         def load(self, im): | ||||||
|             self.loaded = True |             self.loaded = True | ||||||
|  |             im.fp.close() | ||||||
|             return Image.new("RGB", (1, 1)) |             return Image.new("RGB", (1, 1)) | ||||||
| 
 | 
 | ||||||
|     handler = Handler() |     handler = Handler() | ||||||
|  |  | ||||||
|  | @ -36,7 +36,8 @@ def test_unclosed_file(): | ||||||
|         im = Image.open(static_test_file) |         im = Image.open(static_test_file) | ||||||
|         im.load() |         im.load() | ||||||
| 
 | 
 | ||||||
|     pytest.warns(ResourceWarning, open) |     with pytest.warns(ResourceWarning): | ||||||
|  |         open() | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_closed_file(): | def test_closed_file(): | ||||||
|  | @ -64,7 +65,6 @@ def test_context_manager(): | ||||||
| def test_tell(): | def test_tell(): | ||||||
|     # Arrange |     # Arrange | ||||||
|     with Image.open(static_test_file) as im: |     with Image.open(static_test_file) as im: | ||||||
| 
 |  | ||||||
|         # Act |         # Act | ||||||
|         frame = im.tell() |         frame = im.tell() | ||||||
| 
 | 
 | ||||||
|  | @ -110,7 +110,6 @@ def test_eoferror(): | ||||||
| 
 | 
 | ||||||
| def test_seek_tell(): | def test_seek_tell(): | ||||||
|     with Image.open(animated_test_file) as im: |     with Image.open(animated_test_file) as im: | ||||||
| 
 |  | ||||||
|         layer_number = im.tell() |         layer_number = im.tell() | ||||||
|         assert layer_number == 0 |         assert layer_number == 0 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -18,6 +18,16 @@ def test_sanity(): | ||||||
|         assert_image_equal_tofile(im, "Tests/images/input_bw_one_band.png") |         assert_image_equal_tofile(im, "Tests/images/input_bw_one_band.png") | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | def test_close(): | ||||||
|  |     with Image.open("Tests/images/input_bw_one_band.fpx") as im: | ||||||
|  |         pass | ||||||
|  |     assert im.ole.fp.closed | ||||||
|  | 
 | ||||||
|  |     im = Image.open("Tests/images/input_bw_one_band.fpx") | ||||||
|  |     im.close() | ||||||
|  |     assert im.ole.fp.closed | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| def test_invalid_file(): | def test_invalid_file(): | ||||||
|     # Test an invalid OLE file |     # Test an invalid OLE file | ||||||
|     invalid_file = "Tests/images/flower.jpg" |     invalid_file = "Tests/images/flower.jpg" | ||||||
|  |  | ||||||
|  | @ -36,7 +36,8 @@ def test_unclosed_file(): | ||||||
|         im = Image.open(TEST_GIF) |         im = Image.open(TEST_GIF) | ||||||
|         im.load() |         im.load() | ||||||
| 
 | 
 | ||||||
|     pytest.warns(ResourceWarning, open) |     with pytest.warns(ResourceWarning): | ||||||
|  |         open() | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_closed_file(): | def test_closed_file(): | ||||||
|  | @ -209,7 +210,7 @@ def test_optimize_if_palette_can_be_reduced_by_half(): | ||||||
|         im = im.resize((591, 443)) |         im = im.resize((591, 443)) | ||||||
|     im_rgb = im.convert("RGB") |     im_rgb = im.convert("RGB") | ||||||
| 
 | 
 | ||||||
|     for (optimize, colors) in ((False, 256), (True, 8)): |     for optimize, colors in ((False, 256), (True, 8)): | ||||||
|         out = BytesIO() |         out = BytesIO() | ||||||
|         im_rgb.save(out, "GIF", optimize=optimize) |         im_rgb.save(out, "GIF", optimize=optimize) | ||||||
|         with Image.open(out) as reloaded: |         with Image.open(out) as reloaded: | ||||||
|  | @ -221,7 +222,6 @@ def test_roundtrip(tmp_path): | ||||||
|     im = hopper() |     im = hopper() | ||||||
|     im.save(out) |     im.save(out) | ||||||
|     with Image.open(out) as reread: |     with Image.open(out) as reread: | ||||||
| 
 |  | ||||||
|         assert_image_similar(reread.convert("RGB"), im, 50) |         assert_image_similar(reread.convert("RGB"), im, 50) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -232,7 +232,6 @@ def test_roundtrip2(tmp_path): | ||||||
|         im2 = im.copy() |         im2 = im.copy() | ||||||
|         im2.save(out) |         im2.save(out) | ||||||
|     with Image.open(out) as reread: |     with Image.open(out) as reread: | ||||||
| 
 |  | ||||||
|         assert_image_similar(reread.convert("RGB"), hopper(), 50) |         assert_image_similar(reread.convert("RGB"), hopper(), 50) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -242,7 +241,6 @@ def test_roundtrip_save_all(tmp_path): | ||||||
|     im = hopper() |     im = hopper() | ||||||
|     im.save(out, save_all=True) |     im.save(out, save_all=True) | ||||||
|     with Image.open(out) as reread: |     with Image.open(out) as reread: | ||||||
| 
 |  | ||||||
|         assert_image_similar(reread.convert("RGB"), im, 50) |         assert_image_similar(reread.convert("RGB"), im, 50) | ||||||
| 
 | 
 | ||||||
|     # Multiframe image |     # Multiframe image | ||||||
|  | @ -284,13 +282,11 @@ def test_headers_saving_for_animated_gifs(tmp_path): | ||||||
|     important_headers = ["background", "version", "duration", "loop"] |     important_headers = ["background", "version", "duration", "loop"] | ||||||
|     # Multiframe image |     # Multiframe image | ||||||
|     with Image.open("Tests/images/dispose_bgnd.gif") as im: |     with Image.open("Tests/images/dispose_bgnd.gif") as im: | ||||||
| 
 |  | ||||||
|         info = im.info.copy() |         info = im.info.copy() | ||||||
| 
 | 
 | ||||||
|         out = str(tmp_path / "temp.gif") |         out = str(tmp_path / "temp.gif") | ||||||
|         im.save(out, save_all=True) |         im.save(out, save_all=True) | ||||||
|     with Image.open(out) as reread: |     with Image.open(out) as reread: | ||||||
| 
 |  | ||||||
|         for header in important_headers: |         for header in important_headers: | ||||||
|             assert info[header] == reread.info[header] |             assert info[header] == reread.info[header] | ||||||
| 
 | 
 | ||||||
|  | @ -308,7 +304,6 @@ def test_palette_handling(tmp_path): | ||||||
|         im2.save(f, optimize=True) |         im2.save(f, optimize=True) | ||||||
| 
 | 
 | ||||||
|     with Image.open(f) as reloaded: |     with Image.open(f) as reloaded: | ||||||
| 
 |  | ||||||
|         assert_image_similar(im, reloaded.convert("RGB"), 10) |         assert_image_similar(im, reloaded.convert("RGB"), 10) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -324,7 +319,6 @@ def test_palette_434(tmp_path): | ||||||
| 
 | 
 | ||||||
|     orig = "Tests/images/test.colors.gif" |     orig = "Tests/images/test.colors.gif" | ||||||
|     with Image.open(orig) as im: |     with Image.open(orig) as im: | ||||||
| 
 |  | ||||||
|         with roundtrip(im) as reloaded: |         with roundtrip(im) as reloaded: | ||||||
|             assert_image_similar(im, reloaded, 1) |             assert_image_similar(im, reloaded, 1) | ||||||
|         with roundtrip(im, optimize=True) as reloaded: |         with roundtrip(im, optimize=True) as reloaded: | ||||||
|  | @ -575,7 +569,6 @@ def test_save_dispose(tmp_path): | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|     with Image.open(out) as img: |     with Image.open(out) as img: | ||||||
| 
 |  | ||||||
|         for i in range(2): |         for i in range(2): | ||||||
|             img.seek(img.tell() + 1) |             img.seek(img.tell() + 1) | ||||||
|             assert img.disposal_method == i + 1 |             assert img.disposal_method == i + 1 | ||||||
|  | @ -773,7 +766,6 @@ def test_multiple_duration(tmp_path): | ||||||
|         out, save_all=True, append_images=im_list[1:], duration=duration_list |         out, save_all=True, append_images=im_list[1:], duration=duration_list | ||||||
|     ) |     ) | ||||||
|     with Image.open(out) as reread: |     with Image.open(out) as reread: | ||||||
| 
 |  | ||||||
|         for duration in duration_list: |         for duration in duration_list: | ||||||
|             assert reread.info["duration"] == duration |             assert reread.info["duration"] == duration | ||||||
|             try: |             try: | ||||||
|  | @ -786,7 +778,6 @@ def test_multiple_duration(tmp_path): | ||||||
|         out, save_all=True, append_images=im_list[1:], duration=tuple(duration_list) |         out, save_all=True, append_images=im_list[1:], duration=tuple(duration_list) | ||||||
|     ) |     ) | ||||||
|     with Image.open(out) as reread: |     with Image.open(out) as reread: | ||||||
| 
 |  | ||||||
|         for duration in duration_list: |         for duration in duration_list: | ||||||
|             assert reread.info["duration"] == duration |             assert reread.info["duration"] == duration | ||||||
|             try: |             try: | ||||||
|  | @ -844,7 +835,6 @@ def test_identical_frames(tmp_path): | ||||||
|         out, save_all=True, append_images=im_list[1:], duration=duration_list |         out, save_all=True, append_images=im_list[1:], duration=duration_list | ||||||
|     ) |     ) | ||||||
|     with Image.open(out) as reread: |     with Image.open(out) as reread: | ||||||
| 
 |  | ||||||
|         # Assert that the first three frames were combined |         # Assert that the first three frames were combined | ||||||
|         assert reread.n_frames == 2 |         assert reread.n_frames == 2 | ||||||
| 
 | 
 | ||||||
|  | @ -1098,7 +1088,8 @@ def test_rgb_transparency(tmp_path): | ||||||
|     im = Image.new("RGB", (1, 1)) |     im = Image.new("RGB", (1, 1)) | ||||||
|     im.info["transparency"] = b"" |     im.info["transparency"] = b"" | ||||||
|     ims = [Image.new("RGB", (1, 1))] |     ims = [Image.new("RGB", (1, 1))] | ||||||
|     pytest.warns(UserWarning, im.save, out, save_all=True, append_images=ims) |     with pytest.warns(UserWarning): | ||||||
|  |         im.save(out, save_all=True, append_images=ims) | ||||||
| 
 | 
 | ||||||
|     with Image.open(out) as reloaded: |     with Image.open(out) as reloaded: | ||||||
|         assert "transparency" not in reloaded.info |         assert "transparency" not in reloaded.info | ||||||
|  |  | ||||||
|  | @ -10,7 +10,6 @@ TEST_FILE = "Tests/images/WAlaska.wind.7days.grb" | ||||||
| def test_open(): | def test_open(): | ||||||
|     # Act |     # Act | ||||||
|     with Image.open(TEST_FILE) as im: |     with Image.open(TEST_FILE) as im: | ||||||
| 
 |  | ||||||
|         # Assert |         # Assert | ||||||
|         assert im.format == "GRIB" |         assert im.format == "GRIB" | ||||||
| 
 | 
 | ||||||
|  | @ -31,7 +30,6 @@ def test_invalid_file(): | ||||||
| def test_load(): | def test_load(): | ||||||
|     # Arrange |     # Arrange | ||||||
|     with Image.open(TEST_FILE) as im: |     with Image.open(TEST_FILE) as im: | ||||||
| 
 |  | ||||||
|         # Act / Assert: stub cannot load without an implemented handler |         # Act / Assert: stub cannot load without an implemented handler | ||||||
|         with pytest.raises(OSError): |         with pytest.raises(OSError): | ||||||
|             im.load() |             im.load() | ||||||
|  | @ -58,6 +56,7 @@ def test_handler(tmp_path): | ||||||
| 
 | 
 | ||||||
|         def load(self, im): |         def load(self, im): | ||||||
|             self.loaded = True |             self.loaded = True | ||||||
|  |             im.fp.close() | ||||||
|             return Image.new("RGB", (1, 1)) |             return Image.new("RGB", (1, 1)) | ||||||
| 
 | 
 | ||||||
|         def save(self, im, fp, filename): |         def save(self, im, fp, filename): | ||||||
|  |  | ||||||
|  | @ -8,7 +8,6 @@ TEST_FILE = "Tests/images/hdf5.h5" | ||||||
| def test_open(): | def test_open(): | ||||||
|     # Act |     # Act | ||||||
|     with Image.open(TEST_FILE) as im: |     with Image.open(TEST_FILE) as im: | ||||||
| 
 |  | ||||||
|         # Assert |         # Assert | ||||||
|         assert im.format == "HDF5" |         assert im.format == "HDF5" | ||||||
| 
 | 
 | ||||||
|  | @ -29,7 +28,6 @@ def test_invalid_file(): | ||||||
| def test_load(): | def test_load(): | ||||||
|     # Arrange |     # Arrange | ||||||
|     with Image.open(TEST_FILE) as im: |     with Image.open(TEST_FILE) as im: | ||||||
| 
 |  | ||||||
|         # Act / Assert: stub cannot load without an implemented handler |         # Act / Assert: stub cannot load without an implemented handler | ||||||
|         with pytest.raises(OSError): |         with pytest.raises(OSError): | ||||||
|             im.load() |             im.load() | ||||||
|  | @ -59,6 +57,7 @@ def test_handler(tmp_path): | ||||||
| 
 | 
 | ||||||
|         def load(self, im): |         def load(self, im): | ||||||
|             self.loaded = True |             self.loaded = True | ||||||
|  |             im.fp.close() | ||||||
|             return Image.new("RGB", (1, 1)) |             return Image.new("RGB", (1, 1)) | ||||||
| 
 | 
 | ||||||
|         def save(self, im, fp, filename): |         def save(self, im, fp, filename): | ||||||
|  |  | ||||||
|  | @ -16,7 +16,6 @@ def test_sanity(): | ||||||
|     # Loading this icon by default should result in the largest size |     # Loading this icon by default should result in the largest size | ||||||
|     # (512x512@2x) being loaded |     # (512x512@2x) being loaded | ||||||
|     with Image.open(TEST_FILE) as im: |     with Image.open(TEST_FILE) as im: | ||||||
| 
 |  | ||||||
|         # Assert that there is no unclosed file warning |         # Assert that there is no unclosed file warning | ||||||
|         with warnings.catch_warnings(): |         with warnings.catch_warnings(): | ||||||
|             im.load() |             im.load() | ||||||
|  |  | ||||||
|  | @ -175,7 +175,6 @@ def test_save_256x256(tmp_path): | ||||||
|         # Act |         # Act | ||||||
|         im.save(outfile) |         im.save(outfile) | ||||||
|     with Image.open(outfile) as im_saved: |     with Image.open(outfile) as im_saved: | ||||||
| 
 |  | ||||||
|         # Assert |         # Assert | ||||||
|         assert im_saved.size == (256, 256) |         assert im_saved.size == (256, 256) | ||||||
| 
 | 
 | ||||||
|  | @ -213,12 +212,10 @@ def test_save_append_images(tmp_path): | ||||||
| def test_unexpected_size(): | def test_unexpected_size(): | ||||||
|     # This image has been manually hexedited to state that it is 16x32 |     # This image has been manually hexedited to state that it is 16x32 | ||||||
|     # while the image within is still 16x16 |     # while the image within is still 16x16 | ||||||
|     def open(): |     with pytest.warns(UserWarning): | ||||||
|         with Image.open("Tests/images/hopper_unexpected.ico") as im: |         with Image.open("Tests/images/hopper_unexpected.ico") as im: | ||||||
|             assert im.size == (16, 16) |             assert im.size == (16, 16) | ||||||
| 
 | 
 | ||||||
|     pytest.warns(UserWarning, open) |  | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
| def test_draw_reloaded(tmp_path): | def test_draw_reloaded(tmp_path): | ||||||
|     with Image.open(TEST_ICO_FILE) as im: |     with Image.open(TEST_ICO_FILE) as im: | ||||||
|  |  | ||||||
|  | @ -32,7 +32,8 @@ def test_unclosed_file(): | ||||||
|         im = Image.open(TEST_IM) |         im = Image.open(TEST_IM) | ||||||
|         im.load() |         im.load() | ||||||
| 
 | 
 | ||||||
|     pytest.warns(ResourceWarning, open) |     with pytest.warns(ResourceWarning): | ||||||
|  |         open() | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_closed_file(): | def test_closed_file(): | ||||||
|  | @ -51,7 +52,6 @@ def test_context_manager(): | ||||||
| def test_tell(): | def test_tell(): | ||||||
|     # Arrange |     # Arrange | ||||||
|     with Image.open(TEST_IM) as im: |     with Image.open(TEST_IM) as im: | ||||||
| 
 |  | ||||||
|         # Act |         # Act | ||||||
|         frame = im.tell() |         frame = im.tell() | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -11,7 +11,6 @@ TEST_FILE = "Tests/images/iptc.jpg" | ||||||
| def test_getiptcinfo_jpg_none(): | def test_getiptcinfo_jpg_none(): | ||||||
|     # Arrange |     # Arrange | ||||||
|     with hopper() as im: |     with hopper() as im: | ||||||
| 
 |  | ||||||
|         # Act |         # Act | ||||||
|         iptc = IptcImagePlugin.getiptcinfo(im) |         iptc = IptcImagePlugin.getiptcinfo(im) | ||||||
| 
 | 
 | ||||||
|  | @ -22,7 +21,6 @@ def test_getiptcinfo_jpg_none(): | ||||||
| def test_getiptcinfo_jpg_found(): | def test_getiptcinfo_jpg_found(): | ||||||
|     # Arrange |     # Arrange | ||||||
|     with Image.open(TEST_FILE) as im: |     with Image.open(TEST_FILE) as im: | ||||||
| 
 |  | ||||||
|         # Act |         # Act | ||||||
|         iptc = IptcImagePlugin.getiptcinfo(im) |         iptc = IptcImagePlugin.getiptcinfo(im) | ||||||
| 
 | 
 | ||||||
|  | @ -35,7 +33,6 @@ def test_getiptcinfo_jpg_found(): | ||||||
| def test_getiptcinfo_tiff_none(): | def test_getiptcinfo_tiff_none(): | ||||||
|     # Arrange |     # Arrange | ||||||
|     with Image.open("Tests/images/hopper.tif") as im: |     with Image.open("Tests/images/hopper.tif") as im: | ||||||
| 
 |  | ||||||
|         # Act |         # Act | ||||||
|         iptc = IptcImagePlugin.getiptcinfo(im) |         iptc = IptcImagePlugin.getiptcinfo(im) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -57,7 +57,6 @@ class TestFileJpeg: | ||||||
|         return Image.frombytes(mode, size, os.urandom(size[0] * size[1] * len(mode))) |         return Image.frombytes(mode, size, os.urandom(size[0] * size[1] * len(mode))) | ||||||
| 
 | 
 | ||||||
|     def test_sanity(self): |     def test_sanity(self): | ||||||
| 
 |  | ||||||
|         # internal version number |         # internal version number | ||||||
|         assert re.search(r"\d+\.\d+$", features.version_codec("jpg")) |         assert re.search(r"\d+\.\d+$", features.version_codec("jpg")) | ||||||
| 
 | 
 | ||||||
|  | @ -271,7 +270,10 @@ class TestFileJpeg: | ||||||
|         # https://github.com/python-pillow/Pillow/issues/148 |         # https://github.com/python-pillow/Pillow/issues/148 | ||||||
|         f = str(tmp_path / "temp.jpg") |         f = str(tmp_path / "temp.jpg") | ||||||
|         im = hopper() |         im = hopper() | ||||||
|         im.save(f, "JPEG", quality=90, exif=b"1" * 65532) |         im.save(f, "JPEG", quality=90, exif=b"1" * 65533) | ||||||
|  | 
 | ||||||
|  |         with pytest.raises(ValueError): | ||||||
|  |             im.save(f, "JPEG", quality=90, exif=b"1" * 65534) | ||||||
| 
 | 
 | ||||||
|     def test_exif_typeerror(self): |     def test_exif_typeerror(self): | ||||||
|         with Image.open("Tests/images/exif_typeerror.jpg") as im: |         with Image.open("Tests/images/exif_typeerror.jpg") as im: | ||||||
|  | @ -368,7 +370,6 @@ class TestFileJpeg: | ||||||
| 
 | 
 | ||||||
|     def test_exif_gps_typeerror(self): |     def test_exif_gps_typeerror(self): | ||||||
|         with Image.open("Tests/images/exif_gps_typeerror.jpg") as im: |         with Image.open("Tests/images/exif_gps_typeerror.jpg") as im: | ||||||
| 
 |  | ||||||
|             # Should not raise a TypeError |             # Should not raise a TypeError | ||||||
|             im._getexif() |             im._getexif() | ||||||
| 
 | 
 | ||||||
|  | @ -447,7 +448,7 @@ class TestFileJpeg: | ||||||
|             ims = im.get_child_images() |             ims = im.get_child_images() | ||||||
| 
 | 
 | ||||||
|         assert len(ims) == 1 |         assert len(ims) == 1 | ||||||
|         assert_image_equal_tofile(ims[0], "Tests/images/flower_thumbnail.png") |         assert_image_similar_tofile(ims[0], "Tests/images/flower_thumbnail.png", 2.1) | ||||||
| 
 | 
 | ||||||
|     def test_mp(self): |     def test_mp(self): | ||||||
|         with Image.open("Tests/images/pil_sample_rgb.jpg") as im: |         with Image.open("Tests/images/pil_sample_rgb.jpg") as im: | ||||||
|  | @ -682,7 +683,6 @@ class TestFileJpeg: | ||||||
|         # Shouldn't raise error |         # Shouldn't raise error | ||||||
|         fn = "Tests/images/sugarshack_bad_mpo_header.jpg" |         fn = "Tests/images/sugarshack_bad_mpo_header.jpg" | ||||||
|         with pytest.warns(UserWarning, Image.open, fn) as im: |         with pytest.warns(UserWarning, Image.open, fn) as im: | ||||||
| 
 |  | ||||||
|             # Assert |             # Assert | ||||||
|             assert im.format == "JPEG" |             assert im.format == "JPEG" | ||||||
| 
 | 
 | ||||||
|  | @ -704,7 +704,6 @@ class TestFileJpeg: | ||||||
|         # Arrange |         # Arrange | ||||||
|         outfile = str(tmp_path / "temp.tif") |         outfile = str(tmp_path / "temp.tif") | ||||||
|         with Image.open("Tests/images/hopper.tif") as im: |         with Image.open("Tests/images/hopper.tif") as im: | ||||||
| 
 |  | ||||||
|             # Act |             # Act | ||||||
|             im.save(outfile, "JPEG", dpi=im.info["dpi"]) |             im.save(outfile, "JPEG", dpi=im.info["dpi"]) | ||||||
| 
 | 
 | ||||||
|  | @ -731,7 +730,6 @@ class TestFileJpeg: | ||||||
|         # This Photoshop CC 2017 image has DPI in EXIF not metadata |         # This Photoshop CC 2017 image has DPI in EXIF not metadata | ||||||
|         # EXIF XResolution is (2000000, 10000) |         # EXIF XResolution is (2000000, 10000) | ||||||
|         with Image.open("Tests/images/photoshop-200dpi.jpg") as im: |         with Image.open("Tests/images/photoshop-200dpi.jpg") as im: | ||||||
| 
 |  | ||||||
|             # Act / Assert |             # Act / Assert | ||||||
|             assert im.info.get("dpi") == (200, 200) |             assert im.info.get("dpi") == (200, 200) | ||||||
| 
 | 
 | ||||||
|  | @ -740,7 +738,6 @@ class TestFileJpeg: | ||||||
|         # This image has DPI in EXIF not metadata |         # This image has DPI in EXIF not metadata | ||||||
|         # EXIF XResolution is 72 |         # EXIF XResolution is 72 | ||||||
|         with Image.open("Tests/images/exif-72dpi-int.jpg") as im: |         with Image.open("Tests/images/exif-72dpi-int.jpg") as im: | ||||||
| 
 |  | ||||||
|             # Act / Assert |             # Act / Assert | ||||||
|             assert im.info.get("dpi") == (72, 72) |             assert im.info.get("dpi") == (72, 72) | ||||||
| 
 | 
 | ||||||
|  | @ -749,7 +746,6 @@ class TestFileJpeg: | ||||||
|         # This is photoshop-200dpi.jpg with EXIF resolution unit set to cm: |         # This is photoshop-200dpi.jpg with EXIF resolution unit set to cm: | ||||||
|         # exiftool -exif:ResolutionUnit=cm photoshop-200dpi.jpg |         # exiftool -exif:ResolutionUnit=cm photoshop-200dpi.jpg | ||||||
|         with Image.open("Tests/images/exif-200dpcm.jpg") as im: |         with Image.open("Tests/images/exif-200dpcm.jpg") as im: | ||||||
| 
 |  | ||||||
|             # Act / Assert |             # Act / Assert | ||||||
|             assert im.info.get("dpi") == (508, 508) |             assert im.info.get("dpi") == (508, 508) | ||||||
| 
 | 
 | ||||||
|  | @ -758,7 +754,6 @@ class TestFileJpeg: | ||||||
|         # This is photoshop-200dpi.jpg with EXIF resolution set to 0/0: |         # This is photoshop-200dpi.jpg with EXIF resolution set to 0/0: | ||||||
|         # exiftool -XResolution=0/0 -YResolution=0/0 photoshop-200dpi.jpg |         # exiftool -XResolution=0/0 -YResolution=0/0 photoshop-200dpi.jpg | ||||||
|         with Image.open("Tests/images/exif-dpi-zerodivision.jpg") as im: |         with Image.open("Tests/images/exif-dpi-zerodivision.jpg") as im: | ||||||
| 
 |  | ||||||
|             # Act / Assert |             # Act / Assert | ||||||
|             # This should return the default, and not raise a ZeroDivisionError |             # This should return the default, and not raise a ZeroDivisionError | ||||||
|             assert im.info.get("dpi") == (72, 72) |             assert im.info.get("dpi") == (72, 72) | ||||||
|  | @ -767,7 +762,6 @@ class TestFileJpeg: | ||||||
|         # Arrange |         # Arrange | ||||||
|         # 0x011A tag in this exif contains string '300300\x02' |         # 0x011A tag in this exif contains string '300300\x02' | ||||||
|         with Image.open("Tests/images/broken_exif_dpi.jpg") as im: |         with Image.open("Tests/images/broken_exif_dpi.jpg") as im: | ||||||
| 
 |  | ||||||
|             # Act / Assert |             # Act / Assert | ||||||
|             # This should return the default |             # This should return the default | ||||||
|             assert im.info.get("dpi") == (72, 72) |             assert im.info.get("dpi") == (72, 72) | ||||||
|  | @ -777,7 +771,6 @@ class TestFileJpeg: | ||||||
|         # This is photoshop-200dpi.jpg with resolution removed from EXIF: |         # This is photoshop-200dpi.jpg with resolution removed from EXIF: | ||||||
|         # exiftool "-*resolution*"= photoshop-200dpi.jpg |         # exiftool "-*resolution*"= photoshop-200dpi.jpg | ||||||
|         with Image.open("Tests/images/no-dpi-in-exif.jpg") as im: |         with Image.open("Tests/images/no-dpi-in-exif.jpg") as im: | ||||||
| 
 |  | ||||||
|             # Act / Assert |             # Act / Assert | ||||||
|             # "When the image resolution is unknown, 72 [dpi] is designated." |             # "When the image resolution is unknown, 72 [dpi] is designated." | ||||||
|             # https://exiv2.org/tags.html |             # https://exiv2.org/tags.html | ||||||
|  | @ -787,7 +780,6 @@ class TestFileJpeg: | ||||||
|         # This is no-dpi-in-exif with the tiff header of the exif block |         # This is no-dpi-in-exif with the tiff header of the exif block | ||||||
|         # hexedited from MM * to FF FF FF FF |         # hexedited from MM * to FF FF FF FF | ||||||
|         with Image.open("Tests/images/invalid-exif.jpg") as im: |         with Image.open("Tests/images/invalid-exif.jpg") as im: | ||||||
| 
 |  | ||||||
|             # This should return the default, and not a SyntaxError or |             # This should return the default, and not a SyntaxError or | ||||||
|             # OSError for unidentified image. |             # OSError for unidentified image. | ||||||
|             assert im.info.get("dpi") == (72, 72) |             assert im.info.get("dpi") == (72, 72) | ||||||
|  | @ -810,7 +802,6 @@ class TestFileJpeg: | ||||||
|     def test_invalid_exif_x_resolution(self): |     def test_invalid_exif_x_resolution(self): | ||||||
|         # When no x or y resolution is defined in EXIF |         # When no x or y resolution is defined in EXIF | ||||||
|         with Image.open("Tests/images/invalid-exif-without-x-resolution.jpg") as im: |         with Image.open("Tests/images/invalid-exif-without-x-resolution.jpg") as im: | ||||||
| 
 |  | ||||||
|             # This should return the default, and not a ValueError or |             # This should return the default, and not a ValueError or | ||||||
|             # OSError for an unidentified image. |             # OSError for an unidentified image. | ||||||
|             assert im.info.get("dpi") == (72, 72) |             assert im.info.get("dpi") == (72, 72) | ||||||
|  | @ -820,7 +811,6 @@ class TestFileJpeg: | ||||||
|         # This image has been manually hexedited to have an IFD offset of 10, |         # This image has been manually hexedited to have an IFD offset of 10, | ||||||
|         # in contrast to normal 8 |         # in contrast to normal 8 | ||||||
|         with Image.open("Tests/images/exif-ifd-offset.jpg") as im: |         with Image.open("Tests/images/exif-ifd-offset.jpg") as im: | ||||||
| 
 |  | ||||||
|             # Act / Assert |             # Act / Assert | ||||||
|             assert im._getexif()[306] == "2017:03:13 23:03:09" |             assert im._getexif()[306] == "2017:03:13 23:03:09" | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -270,7 +270,6 @@ def test_rgba(): | ||||||
|     # Arrange |     # Arrange | ||||||
|     with Image.open("Tests/images/rgb_trns_ycbc.j2k") as j2k: |     with Image.open("Tests/images/rgb_trns_ycbc.j2k") as j2k: | ||||||
|         with Image.open("Tests/images/rgb_trns_ycbc.jp2") as jp2: |         with Image.open("Tests/images/rgb_trns_ycbc.jp2") as jp2: | ||||||
| 
 |  | ||||||
|             # Act |             # Act | ||||||
|             j2k.load() |             j2k.load() | ||||||
|             jp2.load() |             jp2.load() | ||||||
|  |  | ||||||
|  | @ -645,7 +645,6 @@ class TestFileLibTiff(LibTiffTestCase): | ||||||
|         pilim = hopper() |         pilim = hopper() | ||||||
| 
 | 
 | ||||||
|         def save_bytesio(compression=None): |         def save_bytesio(compression=None): | ||||||
| 
 |  | ||||||
|             buffer_io = io.BytesIO() |             buffer_io = io.BytesIO() | ||||||
|             pilim.save(buffer_io, format="tiff", compression=compression) |             pilim.save(buffer_io, format="tiff", compression=compression) | ||||||
|             buffer_io.seek(0) |             buffer_io.seek(0) | ||||||
|  | @ -740,7 +739,6 @@ class TestFileLibTiff(LibTiffTestCase): | ||||||
| 
 | 
 | ||||||
|     def test_multipage_compression(self): |     def test_multipage_compression(self): | ||||||
|         with Image.open("Tests/images/compression.tif") as im: |         with Image.open("Tests/images/compression.tif") as im: | ||||||
| 
 |  | ||||||
|             im.seek(0) |             im.seek(0) | ||||||
|             assert im._compression == "tiff_ccitt" |             assert im._compression == "tiff_ccitt" | ||||||
|             assert im.size == (10, 10) |             assert im.size == (10, 10) | ||||||
|  | @ -1067,3 +1065,9 @@ class TestFileLibTiff(LibTiffTestCase): | ||||||
|         out = str(tmp_path / "temp.tif") |         out = str(tmp_path / "temp.tif") | ||||||
|         with pytest.raises(SystemError): |         with pytest.raises(SystemError): | ||||||
|             im.save(out, compression=compression) |             im.save(out, compression=compression) | ||||||
|  | 
 | ||||||
|  |     def test_save_many_compressed(self, tmp_path): | ||||||
|  |         im = hopper() | ||||||
|  |         out = str(tmp_path / "temp.tif") | ||||||
|  |         for _ in range(10000): | ||||||
|  |             im.save(out, compression="jpeg") | ||||||
|  |  | ||||||
|  | @ -51,6 +51,16 @@ def test_seek(): | ||||||
|         assert im.tell() == 0 |         assert im.tell() == 0 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | def test_close(): | ||||||
|  |     with Image.open(TEST_FILE) as im: | ||||||
|  |         pass | ||||||
|  |     assert im.ole.fp.closed | ||||||
|  | 
 | ||||||
|  |     im = Image.open(TEST_FILE) | ||||||
|  |     im.close() | ||||||
|  |     assert im.ole.fp.closed | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| def test_invalid_file(): | def test_invalid_file(): | ||||||
|     # Test an invalid OLE file |     # Test an invalid OLE file | ||||||
|     invalid_file = "Tests/images/flower.jpg" |     invalid_file = "Tests/images/flower.jpg" | ||||||
|  |  | ||||||
|  | @ -42,7 +42,8 @@ def test_unclosed_file(): | ||||||
|         im = Image.open(test_files[0]) |         im = Image.open(test_files[0]) | ||||||
|         im.load() |         im.load() | ||||||
| 
 | 
 | ||||||
|     pytest.warns(ResourceWarning, open) |     with pytest.warns(ResourceWarning): | ||||||
|  |         open() | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_closed_file(): | def test_closed_file(): | ||||||
|  |  | ||||||
|  | @ -44,7 +44,6 @@ def test_open_windows_v1(): | ||||||
|     # Arrange |     # Arrange | ||||||
|     # Act |     # Act | ||||||
|     with Image.open(TEST_FILE) as im: |     with Image.open(TEST_FILE) as im: | ||||||
| 
 |  | ||||||
|         # Assert |         # Assert | ||||||
|         assert_image_equal(im, hopper("1")) |         assert_image_equal(im, hopper("1")) | ||||||
|         assert isinstance(im, MspImagePlugin.MspImageFile) |         assert isinstance(im, MspImagePlugin.MspImageFile) | ||||||
|  | @ -59,7 +58,6 @@ def _assert_file_image_equal(source_path, target_path): | ||||||
|     not os.path.exists(EXTRA_DIR), reason="Extra image files not installed" |     not os.path.exists(EXTRA_DIR), reason="Extra image files not installed" | ||||||
| ) | ) | ||||||
| def test_open_windows_v2(): | def test_open_windows_v2(): | ||||||
| 
 |  | ||||||
|     files = ( |     files = ( | ||||||
|         os.path.join(EXTRA_DIR, f) |         os.path.join(EXTRA_DIR, f) | ||||||
|         for f in os.listdir(EXTRA_DIR) |         for f in os.listdir(EXTRA_DIR) | ||||||
|  |  | ||||||
|  | @ -85,6 +85,34 @@ def test_resolution(tmp_path): | ||||||
|     assert size == (61.44, 61.44) |     assert size == (61.44, 61.44) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @pytest.mark.parametrize( | ||||||
|  |     "params", | ||||||
|  |     ( | ||||||
|  |         {"dpi": (75, 150)}, | ||||||
|  |         {"dpi": (75, 150), "resolution": 200}, | ||||||
|  |     ), | ||||||
|  | ) | ||||||
|  | def test_dpi(params, tmp_path): | ||||||
|  |     im = hopper() | ||||||
|  | 
 | ||||||
|  |     outfile = str(tmp_path / "temp.pdf") | ||||||
|  |     im.save(outfile, **params) | ||||||
|  | 
 | ||||||
|  |     with open(outfile, "rb") as fp: | ||||||
|  |         contents = fp.read() | ||||||
|  | 
 | ||||||
|  |     size = tuple( | ||||||
|  |         float(d) | ||||||
|  |         for d in contents.split(b"stream\nq ")[1].split(b" 0 0 cm")[0].split(b" 0 0 ") | ||||||
|  |     ) | ||||||
|  |     assert size == (122.88, 61.44) | ||||||
|  | 
 | ||||||
|  |     size = tuple( | ||||||
|  |         float(d) for d in contents.split(b"/MediaBox [ 0 0 ")[1].split(b"]")[0].split() | ||||||
|  |     ) | ||||||
|  |     assert size == (122.88, 61.44) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| @mark_if_feature_version( | @mark_if_feature_version( | ||||||
|     pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" |     pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" | ||||||
| ) | ) | ||||||
|  | @ -94,7 +122,6 @@ def test_save_all(tmp_path): | ||||||
| 
 | 
 | ||||||
|     # Multiframe image |     # Multiframe image | ||||||
|     with Image.open("Tests/images/dispose_bgnd.gif") as im: |     with Image.open("Tests/images/dispose_bgnd.gif") as im: | ||||||
| 
 |  | ||||||
|         outfile = str(tmp_path / "temp.pdf") |         outfile = str(tmp_path / "temp.pdf") | ||||||
|         im.save(outfile, save_all=True) |         im.save(outfile, save_all=True) | ||||||
| 
 | 
 | ||||||
|  | @ -128,7 +155,6 @@ def test_save_all(tmp_path): | ||||||
| def test_multiframe_normal_save(tmp_path): | def test_multiframe_normal_save(tmp_path): | ||||||
|     # Test saving a multiframe image without save_all |     # Test saving a multiframe image without save_all | ||||||
|     with Image.open("Tests/images/dispose_bgnd.gif") as im: |     with Image.open("Tests/images/dispose_bgnd.gif") as im: | ||||||
| 
 |  | ||||||
|         outfile = str(tmp_path / "temp.pdf") |         outfile = str(tmp_path / "temp.pdf") | ||||||
|         im.save(outfile) |         im.save(outfile) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -78,7 +78,6 @@ class TestFilePng: | ||||||
|         return chunks |         return chunks | ||||||
| 
 | 
 | ||||||
|     def test_sanity(self, tmp_path): |     def test_sanity(self, tmp_path): | ||||||
| 
 |  | ||||||
|         # internal version number |         # internal version number | ||||||
|         assert re.search(r"\d+\.\d+\.\d+(\.\d+)?$", features.version_codec("zlib")) |         assert re.search(r"\d+\.\d+\.\d+(\.\d+)?$", features.version_codec("zlib")) | ||||||
| 
 | 
 | ||||||
|  | @ -156,7 +155,6 @@ class TestFilePng: | ||||||
|         assert im.info == {"spam": "egg"} |         assert im.info == {"spam": "egg"} | ||||||
| 
 | 
 | ||||||
|     def test_bad_itxt(self): |     def test_bad_itxt(self): | ||||||
| 
 |  | ||||||
|         im = load(HEAD + chunk(b"iTXt") + TAIL) |         im = load(HEAD + chunk(b"iTXt") + TAIL) | ||||||
|         assert im.info == {} |         assert im.info == {} | ||||||
| 
 | 
 | ||||||
|  | @ -201,7 +199,6 @@ class TestFilePng: | ||||||
|         assert im.info["spam"].tkey == "Spam" |         assert im.info["spam"].tkey == "Spam" | ||||||
| 
 | 
 | ||||||
|     def test_interlace(self): |     def test_interlace(self): | ||||||
| 
 |  | ||||||
|         test_file = "Tests/images/pil123p.png" |         test_file = "Tests/images/pil123p.png" | ||||||
|         with Image.open(test_file) as im: |         with Image.open(test_file) as im: | ||||||
|             assert_image(im, "P", (162, 150)) |             assert_image(im, "P", (162, 150)) | ||||||
|  | @ -495,7 +492,6 @@ class TestFilePng: | ||||||
|         # Check reading images with null tRNS value, issue #1239 |         # Check reading images with null tRNS value, issue #1239 | ||||||
|         test_file = "Tests/images/tRNS_null_1x1.png" |         test_file = "Tests/images/tRNS_null_1x1.png" | ||||||
|         with Image.open(test_file) as im: |         with Image.open(test_file) as im: | ||||||
| 
 |  | ||||||
|             assert im.info["transparency"] == 0 |             assert im.info["transparency"] == 0 | ||||||
| 
 | 
 | ||||||
|     def test_save_icc_profile(self): |     def test_save_icc_profile(self): | ||||||
|  |  | ||||||
|  | @ -27,7 +27,8 @@ def test_unclosed_file(): | ||||||
|         im = Image.open(test_file) |         im = Image.open(test_file) | ||||||
|         im.load() |         im.load() | ||||||
| 
 | 
 | ||||||
|     pytest.warns(ResourceWarning, open) |     with pytest.warns(ResourceWarning): | ||||||
|  |         open() | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_closed_file(): | def test_closed_file(): | ||||||
|  | @ -77,7 +78,6 @@ def test_eoferror(): | ||||||
| 
 | 
 | ||||||
| def test_seek_tell(): | def test_seek_tell(): | ||||||
|     with Image.open(test_file) as im: |     with Image.open(test_file) as im: | ||||||
| 
 |  | ||||||
|         layer_number = im.tell() |         layer_number = im.tell() | ||||||
|         assert layer_number == 1 |         assert layer_number == 1 | ||||||
| 
 | 
 | ||||||
|  | @ -95,7 +95,6 @@ def test_seek_tell(): | ||||||
| 
 | 
 | ||||||
| def test_seek_eoferror(): | def test_seek_eoferror(): | ||||||
|     with Image.open(test_file) as im: |     with Image.open(test_file) as im: | ||||||
| 
 |  | ||||||
|         with pytest.raises(EOFError): |         with pytest.raises(EOFError): | ||||||
|             im.seek(-1) |             im.seek(-1) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -25,7 +25,8 @@ def test_unclosed_file(): | ||||||
|         im = Image.open(TEST_FILE) |         im = Image.open(TEST_FILE) | ||||||
|         im.load() |         im.load() | ||||||
| 
 | 
 | ||||||
|     pytest.warns(ResourceWarning, open) |     with pytest.warns(ResourceWarning): | ||||||
|  |         open() | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_closed_file(): | def test_closed_file(): | ||||||
|  | @ -79,7 +80,6 @@ def test_is_spider_image(): | ||||||
| def test_tell(): | def test_tell(): | ||||||
|     # Arrange |     # Arrange | ||||||
|     with Image.open(TEST_FILE) as im: |     with Image.open(TEST_FILE) as im: | ||||||
| 
 |  | ||||||
|         # Act |         # Act | ||||||
|         index = im.tell() |         index = im.tell() | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -16,7 +16,6 @@ def test_sanity(): | ||||||
| 
 | 
 | ||||||
|     # Act |     # Act | ||||||
|     with Image.open(test_file) as im: |     with Image.open(test_file) as im: | ||||||
| 
 |  | ||||||
|         # Assert |         # Assert | ||||||
|         assert im.size == (128, 128) |         assert im.size == (128, 128) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -29,11 +29,9 @@ def test_sanity(codec, test_path, format): | ||||||
| 
 | 
 | ||||||
| @pytest.mark.skipif(is_pypy(), reason="Requires CPython") | @pytest.mark.skipif(is_pypy(), reason="Requires CPython") | ||||||
| def test_unclosed_file(): | def test_unclosed_file(): | ||||||
|     def open(): |     with pytest.warns(ResourceWarning): | ||||||
|         TarIO.TarIO(TEST_TAR_FILE, "hopper.jpg") |         TarIO.TarIO(TEST_TAR_FILE, "hopper.jpg") | ||||||
| 
 | 
 | ||||||
|     pytest.warns(ResourceWarning, open) |  | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
| def test_close(): | def test_close(): | ||||||
|     with warnings.catch_warnings(): |     with warnings.catch_warnings(): | ||||||
|  |  | ||||||
|  | @ -78,7 +78,6 @@ def test_id_field(): | ||||||
| 
 | 
 | ||||||
|     # Act |     # Act | ||||||
|     with Image.open(test_file) as im: |     with Image.open(test_file) as im: | ||||||
| 
 |  | ||||||
|         # Assert |         # Assert | ||||||
|         assert im.size == (100, 100) |         assert im.size == (100, 100) | ||||||
| 
 | 
 | ||||||
|  | @ -89,7 +88,6 @@ def test_id_field_rle(): | ||||||
| 
 | 
 | ||||||
|     # Act |     # Act | ||||||
|     with Image.open(test_file) as im: |     with Image.open(test_file) as im: | ||||||
| 
 |  | ||||||
|         # Assert |         # Assert | ||||||
|         assert im.size == (199, 199) |         assert im.size == (199, 199) | ||||||
| 
 | 
 | ||||||
|  | @ -165,13 +163,14 @@ def test_save_id_section(tmp_path): | ||||||
| 
 | 
 | ||||||
|     # Save with custom id section greater than 255 characters |     # Save with custom id section greater than 255 characters | ||||||
|     id_section = b"Test content" * 25 |     id_section = b"Test content" * 25 | ||||||
|     pytest.warns(UserWarning, lambda: im.save(out, id_section=id_section)) |     with pytest.warns(UserWarning): | ||||||
|  |         im.save(out, id_section=id_section) | ||||||
|  | 
 | ||||||
|     with Image.open(out) as test_im: |     with Image.open(out) as test_im: | ||||||
|         assert test_im.info["id_section"] == id_section[:255] |         assert test_im.info["id_section"] == id_section[:255] | ||||||
| 
 | 
 | ||||||
|     test_file = "Tests/images/tga_id_field.tga" |     test_file = "Tests/images/tga_id_field.tga" | ||||||
|     with Image.open(test_file) as im: |     with Image.open(test_file) as im: | ||||||
| 
 |  | ||||||
|         # Save with no id section |         # Save with no id section | ||||||
|         im.save(out, id_section="") |         im.save(out, id_section="") | ||||||
|     with Image.open(out) as test_im: |     with Image.open(out) as test_im: | ||||||
|  |  | ||||||
|  | @ -25,7 +25,6 @@ except ImportError: | ||||||
| 
 | 
 | ||||||
| class TestFileTiff: | class TestFileTiff: | ||||||
|     def test_sanity(self, tmp_path): |     def test_sanity(self, tmp_path): | ||||||
| 
 |  | ||||||
|         filename = str(tmp_path / "temp.tif") |         filename = str(tmp_path / "temp.tif") | ||||||
| 
 | 
 | ||||||
|         hopper("RGB").save(filename) |         hopper("RGB").save(filename) | ||||||
|  | @ -62,7 +61,8 @@ class TestFileTiff: | ||||||
|             im = Image.open("Tests/images/multipage.tiff") |             im = Image.open("Tests/images/multipage.tiff") | ||||||
|             im.load() |             im.load() | ||||||
| 
 | 
 | ||||||
|         pytest.warns(ResourceWarning, open) |         with pytest.warns(ResourceWarning): | ||||||
|  |             open() | ||||||
| 
 | 
 | ||||||
|     def test_closed_file(self): |     def test_closed_file(self): | ||||||
|         with warnings.catch_warnings(): |         with warnings.catch_warnings(): | ||||||
|  | @ -157,7 +157,6 @@ class TestFileTiff: | ||||||
|     def test_xyres_tiff(self): |     def test_xyres_tiff(self): | ||||||
|         filename = "Tests/images/pil168.tif" |         filename = "Tests/images/pil168.tif" | ||||||
|         with Image.open(filename) as im: |         with Image.open(filename) as im: | ||||||
| 
 |  | ||||||
|             # legacy api |             # legacy api | ||||||
|             assert isinstance(im.tag[X_RESOLUTION][0], tuple) |             assert isinstance(im.tag[X_RESOLUTION][0], tuple) | ||||||
|             assert isinstance(im.tag[Y_RESOLUTION][0], tuple) |             assert isinstance(im.tag[Y_RESOLUTION][0], tuple) | ||||||
|  | @ -171,7 +170,6 @@ class TestFileTiff: | ||||||
|     def test_xyres_fallback_tiff(self): |     def test_xyres_fallback_tiff(self): | ||||||
|         filename = "Tests/images/compression.tif" |         filename = "Tests/images/compression.tif" | ||||||
|         with Image.open(filename) as im: |         with Image.open(filename) as im: | ||||||
| 
 |  | ||||||
|             # v2 api |             # v2 api | ||||||
|             assert isinstance(im.tag_v2[X_RESOLUTION], TiffImagePlugin.IFDRational) |             assert isinstance(im.tag_v2[X_RESOLUTION], TiffImagePlugin.IFDRational) | ||||||
|             assert isinstance(im.tag_v2[Y_RESOLUTION], TiffImagePlugin.IFDRational) |             assert isinstance(im.tag_v2[Y_RESOLUTION], TiffImagePlugin.IFDRational) | ||||||
|  | @ -186,7 +184,6 @@ class TestFileTiff: | ||||||
|     def test_int_resolution(self): |     def test_int_resolution(self): | ||||||
|         filename = "Tests/images/pil168.tif" |         filename = "Tests/images/pil168.tif" | ||||||
|         with Image.open(filename) as im: |         with Image.open(filename) as im: | ||||||
| 
 |  | ||||||
|             # Try to read a file where X,Y_RESOLUTION are ints |             # Try to read a file where X,Y_RESOLUTION are ints | ||||||
|             im.tag_v2[X_RESOLUTION] = 71 |             im.tag_v2[X_RESOLUTION] = 71 | ||||||
|             im.tag_v2[Y_RESOLUTION] = 71 |             im.tag_v2[Y_RESOLUTION] = 71 | ||||||
|  | @ -235,7 +232,8 @@ class TestFileTiff: | ||||||
|     def test_bad_exif(self): |     def test_bad_exif(self): | ||||||
|         with Image.open("Tests/images/hopper_bad_exif.jpg") as i: |         with Image.open("Tests/images/hopper_bad_exif.jpg") as i: | ||||||
|             # Should not raise struct.error. |             # Should not raise struct.error. | ||||||
|             pytest.warns(UserWarning, i._getexif) |             with pytest.warns(UserWarning): | ||||||
|  |                 i._getexif() | ||||||
| 
 | 
 | ||||||
|     def test_save_rgba(self, tmp_path): |     def test_save_rgba(self, tmp_path): | ||||||
|         im = hopper("RGBA") |         im = hopper("RGBA") | ||||||
|  | @ -381,7 +379,6 @@ class TestFileTiff: | ||||||
|     def test___str__(self): |     def test___str__(self): | ||||||
|         filename = "Tests/images/pil136.tiff" |         filename = "Tests/images/pil136.tiff" | ||||||
|         with Image.open(filename) as im: |         with Image.open(filename) as im: | ||||||
| 
 |  | ||||||
|             # Act |             # Act | ||||||
|             ret = str(im.ifd) |             ret = str(im.ifd) | ||||||
| 
 | 
 | ||||||
|  | @ -392,7 +389,6 @@ class TestFileTiff: | ||||||
|         # Arrange |         # Arrange | ||||||
|         filename = "Tests/images/pil136.tiff" |         filename = "Tests/images/pil136.tiff" | ||||||
|         with Image.open(filename) as im: |         with Image.open(filename) as im: | ||||||
| 
 |  | ||||||
|             # v2 interface |             # v2 interface | ||||||
|             v2_tags = { |             v2_tags = { | ||||||
|                 256: 55, |                 256: 55, | ||||||
|  | @ -630,7 +626,6 @@ class TestFileTiff: | ||||||
|         filename = str(tmp_path / "temp.tif") |         filename = str(tmp_path / "temp.tif") | ||||||
|         hopper("RGB").save(filename, **kwargs) |         hopper("RGB").save(filename, **kwargs) | ||||||
|         with Image.open(filename) as im: |         with Image.open(filename) as im: | ||||||
| 
 |  | ||||||
|             # legacy interface |             # legacy interface | ||||||
|             assert im.tag[X_RESOLUTION][0][0] == 72 |             assert im.tag[X_RESOLUTION][0][0] == 72 | ||||||
|             assert im.tag[Y_RESOLUTION][0][0] == 36 |             assert im.tag[Y_RESOLUTION][0][0] == 36 | ||||||
|  |  | ||||||
|  | @ -54,7 +54,6 @@ def test_rt_metadata(tmp_path): | ||||||
|     img.save(f, tiffinfo=info) |     img.save(f, tiffinfo=info) | ||||||
| 
 | 
 | ||||||
|     with Image.open(f) as loaded: |     with Image.open(f) as loaded: | ||||||
| 
 |  | ||||||
|         assert loaded.tag[ImageJMetaDataByteCounts] == (len(bin_data),) |         assert loaded.tag[ImageJMetaDataByteCounts] == (len(bin_data),) | ||||||
|         assert loaded.tag_v2[ImageJMetaDataByteCounts] == (len(bin_data),) |         assert loaded.tag_v2[ImageJMetaDataByteCounts] == (len(bin_data),) | ||||||
| 
 | 
 | ||||||
|  | @ -74,14 +73,12 @@ def test_rt_metadata(tmp_path): | ||||||
|     info[ImageJMetaDataByteCounts] = (8, len(bin_data) - 8) |     info[ImageJMetaDataByteCounts] = (8, len(bin_data) - 8) | ||||||
|     img.save(f, tiffinfo=info) |     img.save(f, tiffinfo=info) | ||||||
|     with Image.open(f) as loaded: |     with Image.open(f) as loaded: | ||||||
| 
 |  | ||||||
|         assert loaded.tag[ImageJMetaDataByteCounts] == (8, len(bin_data) - 8) |         assert loaded.tag[ImageJMetaDataByteCounts] == (8, len(bin_data) - 8) | ||||||
|         assert loaded.tag_v2[ImageJMetaDataByteCounts] == (8, len(bin_data) - 8) |         assert loaded.tag_v2[ImageJMetaDataByteCounts] == (8, len(bin_data) - 8) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_read_metadata(): | def test_read_metadata(): | ||||||
|     with Image.open("Tests/images/hopper_g4.tif") as img: |     with Image.open("Tests/images/hopper_g4.tif") as img: | ||||||
| 
 |  | ||||||
|         assert { |         assert { | ||||||
|             "YResolution": IFDRational(4294967295, 113653537), |             "YResolution": IFDRational(4294967295, 113653537), | ||||||
|             "PlanarConfiguration": 1, |             "PlanarConfiguration": 1, | ||||||
|  | @ -219,6 +216,22 @@ def test_writing_other_types_to_bytes(value, tmp_path): | ||||||
|         assert reloaded.tag_v2[700] == b"\x01" |         assert reloaded.tag_v2[700] == b"\x01" | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | def test_writing_other_types_to_undefined(tmp_path): | ||||||
|  |     im = hopper() | ||||||
|  |     info = TiffImagePlugin.ImageFileDirectory_v2() | ||||||
|  | 
 | ||||||
|  |     tag = TiffTags.TAGS_V2[33723] | ||||||
|  |     assert tag.type == TiffTags.UNDEFINED | ||||||
|  | 
 | ||||||
|  |     info[33723] = 1 | ||||||
|  | 
 | ||||||
|  |     out = str(tmp_path / "temp.tiff") | ||||||
|  |     im.save(out, tiffinfo=info) | ||||||
|  | 
 | ||||||
|  |     with Image.open(out) as reloaded: | ||||||
|  |         assert reloaded.tag_v2[33723] == b"1" | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| def test_undefined_zero(tmp_path): | def test_undefined_zero(tmp_path): | ||||||
|     # Check that the tag has not been changed since this test was created |     # Check that the tag has not been changed since this test was created | ||||||
|     tag = TiffTags.TAGS_V2[45059] |     tag = TiffTags.TAGS_V2[45059] | ||||||
|  | @ -239,7 +252,8 @@ def test_empty_metadata(): | ||||||
|     head = f.read(8) |     head = f.read(8) | ||||||
|     info = TiffImagePlugin.ImageFileDirectory(head) |     info = TiffImagePlugin.ImageFileDirectory(head) | ||||||
|     # Should not raise struct.error. |     # Should not raise struct.error. | ||||||
|     pytest.warns(UserWarning, info.load, f) |     with pytest.warns(UserWarning): | ||||||
|  |         info.load(f) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_iccprofile(tmp_path): | def test_iccprofile(tmp_path): | ||||||
|  | @ -405,11 +419,12 @@ def test_too_many_entries(): | ||||||
|     ifd = TiffImagePlugin.ImageFileDirectory_v2() |     ifd = TiffImagePlugin.ImageFileDirectory_v2() | ||||||
| 
 | 
 | ||||||
|     #    277: ("SamplesPerPixel", SHORT, 1), |     #    277: ("SamplesPerPixel", SHORT, 1), | ||||||
|     ifd._tagdata[277] = struct.pack("hh", 4, 4) |     ifd._tagdata[277] = struct.pack("<hh", 4, 4) | ||||||
|     ifd.tagtype[277] = TiffTags.SHORT |     ifd.tagtype[277] = TiffTags.SHORT | ||||||
| 
 | 
 | ||||||
|     # Should not raise ValueError. |     # Should not raise ValueError. | ||||||
|     pytest.warns(UserWarning, lambda: ifd[277]) |     with pytest.warns(UserWarning): | ||||||
|  |         assert ifd[277] == 4 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_tag_group_data(): | def test_tag_group_data(): | ||||||
|  |  | ||||||
|  | @ -29,7 +29,10 @@ class TestUnsupportedWebp: | ||||||
|             WebPImagePlugin.SUPPORTED = False |             WebPImagePlugin.SUPPORTED = False | ||||||
| 
 | 
 | ||||||
|         file_path = "Tests/images/hopper.webp" |         file_path = "Tests/images/hopper.webp" | ||||||
|         pytest.warns(UserWarning, lambda: pytest.raises(OSError, Image.open, file_path)) |         with pytest.warns(UserWarning): | ||||||
|  |             with pytest.raises(OSError): | ||||||
|  |                 with Image.open(file_path): | ||||||
|  |                     pass | ||||||
| 
 | 
 | ||||||
|         if HAVE_WEBP: |         if HAVE_WEBP: | ||||||
|             WebPImagePlugin.SUPPORTED = True |             WebPImagePlugin.SUPPORTED = True | ||||||
|  |  | ||||||
|  | @ -18,10 +18,8 @@ except ImportError: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_read_exif_metadata(): | def test_read_exif_metadata(): | ||||||
| 
 |  | ||||||
|     file_path = "Tests/images/flower.webp" |     file_path = "Tests/images/flower.webp" | ||||||
|     with Image.open(file_path) as image: |     with Image.open(file_path) as image: | ||||||
| 
 |  | ||||||
|         assert image.format == "WEBP" |         assert image.format == "WEBP" | ||||||
|         exif_data = image.info.get("exif", None) |         exif_data = image.info.get("exif", None) | ||||||
|         assert exif_data |         assert exif_data | ||||||
|  | @ -64,10 +62,8 @@ def test_write_exif_metadata(): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_read_icc_profile(): | def test_read_icc_profile(): | ||||||
| 
 |  | ||||||
|     file_path = "Tests/images/flower2.webp" |     file_path = "Tests/images/flower2.webp" | ||||||
|     with Image.open(file_path) as image: |     with Image.open(file_path) as image: | ||||||
| 
 |  | ||||||
|         assert image.format == "WEBP" |         assert image.format == "WEBP" | ||||||
|         assert image.info.get("icc_profile", None) |         assert image.info.get("icc_profile", None) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -6,7 +6,6 @@ from .helper import assert_image_similar_tofile, hopper | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_load_raw(): | def test_load_raw(): | ||||||
| 
 |  | ||||||
|     # Test basic EMF open and rendering |     # Test basic EMF open and rendering | ||||||
|     with Image.open("Tests/images/drawing.emf") as im: |     with Image.open("Tests/images/drawing.emf") as im: | ||||||
|         if hasattr(Image.core, "drawwmf"): |         if hasattr(Image.core, "drawwmf"): | ||||||
|  |  | ||||||
|  | @ -44,7 +44,6 @@ def test_open(): | ||||||
| 
 | 
 | ||||||
|     # Act |     # Act | ||||||
|     with Image.open(filename) as im: |     with Image.open(filename) as im: | ||||||
| 
 |  | ||||||
|         # Assert |         # Assert | ||||||
|         assert im.mode == "1" |         assert im.mode == "1" | ||||||
|         assert im.size == (128, 128) |         assert im.size == (128, 128) | ||||||
|  | @ -57,7 +56,6 @@ def test_open_filename_with_underscore(): | ||||||
| 
 | 
 | ||||||
|     # Act |     # Act | ||||||
|     with Image.open(filename) as im: |     with Image.open(filename) as im: | ||||||
| 
 |  | ||||||
|         # Assert |         # Assert | ||||||
|         assert im.mode == "1" |         assert im.mode == "1" | ||||||
|         assert im.size == (128, 128) |         assert im.size == (128, 128) | ||||||
|  |  | ||||||
|  | @ -10,7 +10,6 @@ TEST_FILE = "Tests/images/hopper.p7" | ||||||
| def test_open(): | def test_open(): | ||||||
|     # Act |     # Act | ||||||
|     with Image.open(TEST_FILE) as im: |     with Image.open(TEST_FILE) as im: | ||||||
| 
 |  | ||||||
|         # Assert |         # Assert | ||||||
|         assert im.format == "XVThumb" |         assert im.format == "XVThumb" | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -69,7 +69,6 @@ class TestImage: | ||||||
|         assert issubclass(UnidentifiedImageError, OSError) |         assert issubclass(UnidentifiedImageError, OSError) | ||||||
| 
 | 
 | ||||||
|     def test_sanity(self): |     def test_sanity(self): | ||||||
| 
 |  | ||||||
|         im = Image.new("L", (100, 100)) |         im = Image.new("L", (100, 100)) | ||||||
|         assert repr(im)[:45] == "<PIL.Image.Image image mode=L size=100x100 at" |         assert repr(im)[:45] == "<PIL.Image.Image image mode=L size=100x100 at" | ||||||
|         assert im.mode == "L" |         assert im.mode == "L" | ||||||
|  | @ -1007,7 +1006,6 @@ def mock_encode(*args): | ||||||
| 
 | 
 | ||||||
| class TestRegistry: | class TestRegistry: | ||||||
|     def test_encode_registry(self): |     def test_encode_registry(self): | ||||||
| 
 |  | ||||||
|         Image.register_encoder("MOCK", mock_encode) |         Image.register_encoder("MOCK", mock_encode) | ||||||
|         assert "MOCK" in Image.ENCODERS |         assert "MOCK" in Image.ENCODERS | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -132,22 +132,26 @@ class TestImageGetPixel(AccessTest): | ||||||
|             return 1 |             return 1 | ||||||
|         return tuple(range(1, bands + 1)) |         return tuple(range(1, bands + 1)) | ||||||
| 
 | 
 | ||||||
|     def check(self, mode, c=None): |     def check(self, mode, expected_color=None): | ||||||
|         if not c: |         if not expected_color: | ||||||
|             c = self.color(mode) |             expected_color = self.color(mode) | ||||||
| 
 | 
 | ||||||
|         # check putpixel |         # check putpixel | ||||||
|         im = Image.new(mode, (1, 1), None) |         im = Image.new(mode, (1, 1), None) | ||||||
|         im.putpixel((0, 0), c) |         im.putpixel((0, 0), expected_color) | ||||||
|         assert ( |         actual_color = im.getpixel((0, 0)) | ||||||
|             im.getpixel((0, 0)) == c |         assert actual_color == expected_color, ( | ||||||
|         ), f"put/getpixel roundtrip failed for mode {mode}, color {c}" |             f"put/getpixel roundtrip failed for mode {mode}, " | ||||||
|  |             f"expected {expected_color} got {actual_color}" | ||||||
|  |         ) | ||||||
| 
 | 
 | ||||||
|         # check putpixel negative index |         # check putpixel negative index | ||||||
|         im.putpixel((-1, -1), c) |         im.putpixel((-1, -1), expected_color) | ||||||
|         assert ( |         actual_color = im.getpixel((-1, -1)) | ||||||
|             im.getpixel((-1, -1)) == c |         assert actual_color == expected_color, ( | ||||||
|         ), f"put/getpixel roundtrip negative index failed for mode {mode}, color {c}" |             f"put/getpixel roundtrip negative index failed for mode {mode}, " | ||||||
|  |             f"expected {expected_color} got {actual_color}" | ||||||
|  |         ) | ||||||
| 
 | 
 | ||||||
|         # Check 0 |         # Check 0 | ||||||
|         im = Image.new(mode, (0, 0), None) |         im = Image.new(mode, (0, 0), None) | ||||||
|  | @ -155,27 +159,32 @@ class TestImageGetPixel(AccessTest): | ||||||
| 
 | 
 | ||||||
|         error = ValueError if self._need_cffi_access else IndexError |         error = ValueError if self._need_cffi_access else IndexError | ||||||
|         with pytest.raises(error): |         with pytest.raises(error): | ||||||
|             im.putpixel((0, 0), c) |             im.putpixel((0, 0), expected_color) | ||||||
|         with pytest.raises(error): |         with pytest.raises(error): | ||||||
|             im.getpixel((0, 0)) |             im.getpixel((0, 0)) | ||||||
|         # Check 0 negative index |         # Check 0 negative index | ||||||
|         with pytest.raises(error): |         with pytest.raises(error): | ||||||
|             im.putpixel((-1, -1), c) |             im.putpixel((-1, -1), expected_color) | ||||||
|         with pytest.raises(error): |         with pytest.raises(error): | ||||||
|             im.getpixel((-1, -1)) |             im.getpixel((-1, -1)) | ||||||
| 
 | 
 | ||||||
|         # check initial color |         # check initial color | ||||||
|         im = Image.new(mode, (1, 1), c) |         im = Image.new(mode, (1, 1), expected_color) | ||||||
|         assert ( |         actual_color = im.getpixel((0, 0)) | ||||||
|             im.getpixel((0, 0)) == c |         assert actual_color == expected_color, ( | ||||||
|         ), f"initial color failed for mode {mode}, color {c} " |             f"initial color failed for mode {mode}, " | ||||||
|  |             f"expected {expected_color} got {actual_color}" | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|         # check initial color negative index |         # check initial color negative index | ||||||
|         assert ( |         actual_color = im.getpixel((-1, -1)) | ||||||
|             im.getpixel((-1, -1)) == c |         assert actual_color == expected_color, ( | ||||||
|         ), f"initial color failed with negative index for mode {mode}, color {c} " |             f"initial color failed with negative index for mode {mode}, " | ||||||
|  |             f"expected {expected_color} got {actual_color}" | ||||||
|  |         ) | ||||||
| 
 | 
 | ||||||
|         # Check 0 |         # Check 0 | ||||||
|         im = Image.new(mode, (0, 0), c) |         im = Image.new(mode, (0, 0), expected_color) | ||||||
|         with pytest.raises(error): |         with pytest.raises(error): | ||||||
|             im.getpixel((0, 0)) |             im.getpixel((0, 0)) | ||||||
|         # Check 0 negative index |         # Check 0 negative index | ||||||
|  | @ -205,13 +214,13 @@ class TestImageGetPixel(AccessTest): | ||||||
|         self.check(mode) |         self.check(mode) | ||||||
| 
 | 
 | ||||||
|     @pytest.mark.parametrize("mode", ("I;16", "I;16B")) |     @pytest.mark.parametrize("mode", ("I;16", "I;16B")) | ||||||
|     def test_signedness(self, mode): |     @pytest.mark.parametrize( | ||||||
|  |         "expected_color", (2**15 - 1, 2**15, 2**15 + 1, 2**16 - 1) | ||||||
|  |     ) | ||||||
|  |     def test_signedness(self, mode, expected_color): | ||||||
|         # see https://github.com/python-pillow/Pillow/issues/452 |         # see https://github.com/python-pillow/Pillow/issues/452 | ||||||
|         # pixelaccess is using signed int* instead of uint* |         # pixelaccess is using signed int* instead of uint* | ||||||
|         self.check(mode, 2**15 - 1) |         self.check(mode, expected_color) | ||||||
|         self.check(mode, 2**15) |  | ||||||
|         self.check(mode, 2**15 + 1) |  | ||||||
|         self.check(mode, 2**16 - 1) |  | ||||||
| 
 | 
 | ||||||
|     @pytest.mark.parametrize("mode", ("P", "PA")) |     @pytest.mark.parametrize("mode", ("P", "PA")) | ||||||
|     @pytest.mark.parametrize("color", ((255, 0, 0), (255, 0, 0, 255))) |     @pytest.mark.parametrize("color", ((255, 0, 0), (255, 0, 0, 255))) | ||||||
|  |  | ||||||
|  | @ -45,7 +45,6 @@ def test_unsupported_conversion(): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_default(): | def test_default(): | ||||||
| 
 |  | ||||||
|     im = hopper("P") |     im = hopper("P") | ||||||
|     assert im.mode == "P" |     assert im.mode == "P" | ||||||
|     converted_im = im.convert() |     converted_im = im.convert() | ||||||
|  | @ -255,17 +254,6 @@ def test_p2pa_palette(): | ||||||
|     assert im_pa.getpalette() == im.getpalette() |     assert im_pa.getpalette() == im.getpalette() | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @pytest.mark.parametrize("mode", ("RGB", "RGBA", "RGBX")) |  | ||||||
| def test_rgb_lab(mode): |  | ||||||
|     im = Image.new(mode, (1, 1)) |  | ||||||
|     converted_im = im.convert("LAB") |  | ||||||
|     assert converted_im.getpixel((0, 0)) == (0, 128, 128) |  | ||||||
| 
 |  | ||||||
|     im = Image.new("LAB", (1, 1), (255, 0, 0)) |  | ||||||
|     converted_im = im.convert(mode) |  | ||||||
|     assert converted_im.getpixel((0, 0))[:3] == (0, 255, 255) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def test_matrix_illegal_conversion(): | def test_matrix_illegal_conversion(): | ||||||
|     # Arrange |     # Arrange | ||||||
|     im = hopper("CMYK") |     im = hopper("CMYK") | ||||||
|  |  | ||||||
|  | @ -86,7 +86,6 @@ def test_crop_crash(): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_crop_zero(): | def test_crop_zero(): | ||||||
| 
 |  | ||||||
|     im = Image.new("RGB", (0, 0), "white") |     im = Image.new("RGB", (0, 0), "white") | ||||||
| 
 | 
 | ||||||
|     cropped = im.crop((0, 0, 0, 0)) |     cropped = im.crop((0, 0, 0, 0)) | ||||||
|  |  | ||||||
|  | @ -1,10 +1,17 @@ | ||||||
|  | import pytest | ||||||
|  | 
 | ||||||
| from PIL import Image | from PIL import Image | ||||||
| 
 | 
 | ||||||
| from .helper import assert_image_equal, hopper | from .helper import assert_image_equal, hopper | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_sanity(): | @pytest.mark.parametrize("data_type", ("bytes", "memoryview")) | ||||||
|  | def test_sanity(data_type): | ||||||
|     im1 = hopper() |     im1 = hopper() | ||||||
|     im2 = Image.frombytes(im1.mode, im1.size, im1.tobytes()) | 
 | ||||||
|  |     data = im1.tobytes() | ||||||
|  |     if data_type == "memoryview": | ||||||
|  |         data = memoryview(data) | ||||||
|  |     im2 = Image.frombytes(im1.mode, im1.size, data) | ||||||
| 
 | 
 | ||||||
|     assert_image_equal(im1, im2) |     assert_image_equal(im1, im2) | ||||||
|  |  | ||||||
|  | @ -4,7 +4,6 @@ from .helper import hopper | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_sanity(): | def test_sanity(): | ||||||
| 
 |  | ||||||
|     bbox = hopper().getbbox() |     bbox = hopper().getbbox() | ||||||
|     assert isinstance(bbox, tuple) |     assert isinstance(bbox, tuple) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -6,7 +6,6 @@ from .helper import hopper | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_sanity(): | def test_sanity(): | ||||||
| 
 |  | ||||||
|     with hopper() as im: |     with hopper() as im: | ||||||
|         im.mode |         im.mode | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -52,7 +52,7 @@ def test_resample(): | ||||||
|     # >>> im.save('Tests/images/hopper_45.png') |     # >>> im.save('Tests/images/hopper_45.png') | ||||||
| 
 | 
 | ||||||
|     with Image.open("Tests/images/hopper_45.png") as target: |     with Image.open("Tests/images/hopper_45.png") as target: | ||||||
|         for (resample, epsilon) in ( |         for resample, epsilon in ( | ||||||
|             (Image.Resampling.NEAREST, 10), |             (Image.Resampling.NEAREST, 10), | ||||||
|             (Image.Resampling.BILINEAR, 5), |             (Image.Resampling.BILINEAR, 5), | ||||||
|             (Image.Resampling.BICUBIC, 0), |             (Image.Resampling.BICUBIC, 0), | ||||||
|  |  | ||||||
|  | @ -4,7 +4,6 @@ from .helper import assert_image_equal, fromstring, hopper | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_sanity(): | def test_sanity(): | ||||||
| 
 |  | ||||||
|     with pytest.raises(ValueError): |     with pytest.raises(ValueError): | ||||||
|         hopper().tobitmap() |         hopper().tobitmap() | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -50,7 +50,6 @@ def test_add(): | ||||||
|     # Arrange |     # Arrange | ||||||
|     with Image.open("Tests/images/imagedraw_ellipse_RGB.png") as im1: |     with Image.open("Tests/images/imagedraw_ellipse_RGB.png") as im1: | ||||||
|         with Image.open("Tests/images/imagedraw_floodfill_RGB.png") as im2: |         with Image.open("Tests/images/imagedraw_floodfill_RGB.png") as im2: | ||||||
| 
 |  | ||||||
|             # Act |             # Act | ||||||
|             new = ImageChops.add(im1, im2) |             new = ImageChops.add(im1, im2) | ||||||
| 
 | 
 | ||||||
|  | @ -63,7 +62,6 @@ def test_add_scale_offset(): | ||||||
|     # Arrange |     # Arrange | ||||||
|     with Image.open("Tests/images/imagedraw_ellipse_RGB.png") as im1: |     with Image.open("Tests/images/imagedraw_ellipse_RGB.png") as im1: | ||||||
|         with Image.open("Tests/images/imagedraw_floodfill_RGB.png") as im2: |         with Image.open("Tests/images/imagedraw_floodfill_RGB.png") as im2: | ||||||
| 
 |  | ||||||
|             # Act |             # Act | ||||||
|             new = ImageChops.add(im1, im2, scale=2.5, offset=100) |             new = ImageChops.add(im1, im2, scale=2.5, offset=100) | ||||||
| 
 | 
 | ||||||
|  | @ -87,7 +85,6 @@ def test_add_modulo(): | ||||||
|     # Arrange |     # Arrange | ||||||
|     with Image.open("Tests/images/imagedraw_ellipse_RGB.png") as im1: |     with Image.open("Tests/images/imagedraw_ellipse_RGB.png") as im1: | ||||||
|         with Image.open("Tests/images/imagedraw_floodfill_RGB.png") as im2: |         with Image.open("Tests/images/imagedraw_floodfill_RGB.png") as im2: | ||||||
| 
 |  | ||||||
|             # Act |             # Act | ||||||
|             new = ImageChops.add_modulo(im1, im2) |             new = ImageChops.add_modulo(im1, im2) | ||||||
| 
 | 
 | ||||||
|  | @ -111,7 +108,6 @@ def test_blend(): | ||||||
|     # Arrange |     # Arrange | ||||||
|     with Image.open("Tests/images/imagedraw_ellipse_RGB.png") as im1: |     with Image.open("Tests/images/imagedraw_ellipse_RGB.png") as im1: | ||||||
|         with Image.open("Tests/images/imagedraw_floodfill_RGB.png") as im2: |         with Image.open("Tests/images/imagedraw_floodfill_RGB.png") as im2: | ||||||
| 
 |  | ||||||
|             # Act |             # Act | ||||||
|             new = ImageChops.blend(im1, im2, 0.5) |             new = ImageChops.blend(im1, im2, 0.5) | ||||||
| 
 | 
 | ||||||
|  | @ -137,7 +133,6 @@ def test_darker_image(): | ||||||
|     # Arrange |     # Arrange | ||||||
|     with Image.open("Tests/images/imagedraw_chord_RGB.png") as im1: |     with Image.open("Tests/images/imagedraw_chord_RGB.png") as im1: | ||||||
|         with Image.open("Tests/images/imagedraw_outline_chord_RGB.png") as im2: |         with Image.open("Tests/images/imagedraw_outline_chord_RGB.png") as im2: | ||||||
| 
 |  | ||||||
|             # Act |             # Act | ||||||
|             new = ImageChops.darker(im1, im2) |             new = ImageChops.darker(im1, im2) | ||||||
| 
 | 
 | ||||||
|  | @ -149,7 +144,6 @@ def test_darker_pixel(): | ||||||
|     # Arrange |     # Arrange | ||||||
|     im1 = hopper() |     im1 = hopper() | ||||||
|     with Image.open("Tests/images/imagedraw_chord_RGB.png") as im2: |     with Image.open("Tests/images/imagedraw_chord_RGB.png") as im2: | ||||||
| 
 |  | ||||||
|         # Act |         # Act | ||||||
|         new = ImageChops.darker(im1, im2) |         new = ImageChops.darker(im1, im2) | ||||||
| 
 | 
 | ||||||
|  | @ -161,7 +155,6 @@ def test_difference(): | ||||||
|     # Arrange |     # Arrange | ||||||
|     with Image.open("Tests/images/imagedraw_arc_end_le_start.png") as im1: |     with Image.open("Tests/images/imagedraw_arc_end_le_start.png") as im1: | ||||||
|         with Image.open("Tests/images/imagedraw_arc_no_loops.png") as im2: |         with Image.open("Tests/images/imagedraw_arc_no_loops.png") as im2: | ||||||
| 
 |  | ||||||
|             # Act |             # Act | ||||||
|             new = ImageChops.difference(im1, im2) |             new = ImageChops.difference(im1, im2) | ||||||
| 
 | 
 | ||||||
|  | @ -173,7 +166,6 @@ def test_difference_pixel(): | ||||||
|     # Arrange |     # Arrange | ||||||
|     im1 = hopper() |     im1 = hopper() | ||||||
|     with Image.open("Tests/images/imagedraw_polygon_kite_RGB.png") as im2: |     with Image.open("Tests/images/imagedraw_polygon_kite_RGB.png") as im2: | ||||||
| 
 |  | ||||||
|         # Act |         # Act | ||||||
|         new = ImageChops.difference(im1, im2) |         new = ImageChops.difference(im1, im2) | ||||||
| 
 | 
 | ||||||
|  | @ -195,7 +187,6 @@ def test_duplicate(): | ||||||
| def test_invert(): | def test_invert(): | ||||||
|     # Arrange |     # Arrange | ||||||
|     with Image.open("Tests/images/imagedraw_floodfill_RGB.png") as im: |     with Image.open("Tests/images/imagedraw_floodfill_RGB.png") as im: | ||||||
| 
 |  | ||||||
|         # Act |         # Act | ||||||
|         new = ImageChops.invert(im) |         new = ImageChops.invert(im) | ||||||
| 
 | 
 | ||||||
|  | @ -209,7 +200,6 @@ def test_lighter_image(): | ||||||
|     # Arrange |     # Arrange | ||||||
|     with Image.open("Tests/images/imagedraw_chord_RGB.png") as im1: |     with Image.open("Tests/images/imagedraw_chord_RGB.png") as im1: | ||||||
|         with Image.open("Tests/images/imagedraw_outline_chord_RGB.png") as im2: |         with Image.open("Tests/images/imagedraw_outline_chord_RGB.png") as im2: | ||||||
| 
 |  | ||||||
|             # Act |             # Act | ||||||
|             new = ImageChops.lighter(im1, im2) |             new = ImageChops.lighter(im1, im2) | ||||||
| 
 | 
 | ||||||
|  | @ -221,7 +211,6 @@ def test_lighter_pixel(): | ||||||
|     # Arrange |     # Arrange | ||||||
|     im1 = hopper() |     im1 = hopper() | ||||||
|     with Image.open("Tests/images/imagedraw_chord_RGB.png") as im2: |     with Image.open("Tests/images/imagedraw_chord_RGB.png") as im2: | ||||||
| 
 |  | ||||||
|         # Act |         # Act | ||||||
|         new = ImageChops.lighter(im1, im2) |         new = ImageChops.lighter(im1, im2) | ||||||
| 
 | 
 | ||||||
|  | @ -275,7 +264,6 @@ def test_offset(): | ||||||
|     xoffset = 45 |     xoffset = 45 | ||||||
|     yoffset = 20 |     yoffset = 20 | ||||||
|     with Image.open("Tests/images/imagedraw_ellipse_RGB.png") as im: |     with Image.open("Tests/images/imagedraw_ellipse_RGB.png") as im: | ||||||
| 
 |  | ||||||
|         # Act |         # Act | ||||||
|         new = ImageChops.offset(im, xoffset, yoffset) |         new = ImageChops.offset(im, xoffset, yoffset) | ||||||
| 
 | 
 | ||||||
|  | @ -292,7 +280,6 @@ def test_screen(): | ||||||
|     # Arrange |     # Arrange | ||||||
|     with Image.open("Tests/images/imagedraw_ellipse_RGB.png") as im1: |     with Image.open("Tests/images/imagedraw_ellipse_RGB.png") as im1: | ||||||
|         with Image.open("Tests/images/imagedraw_floodfill_RGB.png") as im2: |         with Image.open("Tests/images/imagedraw_floodfill_RGB.png") as im2: | ||||||
| 
 |  | ||||||
|             # Act |             # Act | ||||||
|             new = ImageChops.screen(im1, im2) |             new = ImageChops.screen(im1, im2) | ||||||
| 
 | 
 | ||||||
|  | @ -305,7 +292,6 @@ def test_subtract(): | ||||||
|     # Arrange |     # Arrange | ||||||
|     with Image.open("Tests/images/imagedraw_chord_RGB.png") as im1: |     with Image.open("Tests/images/imagedraw_chord_RGB.png") as im1: | ||||||
|         with Image.open("Tests/images/imagedraw_outline_chord_RGB.png") as im2: |         with Image.open("Tests/images/imagedraw_outline_chord_RGB.png") as im2: | ||||||
| 
 |  | ||||||
|             # Act |             # Act | ||||||
|             new = ImageChops.subtract(im1, im2) |             new = ImageChops.subtract(im1, im2) | ||||||
| 
 | 
 | ||||||
|  | @ -319,7 +305,6 @@ def test_subtract_scale_offset(): | ||||||
|     # Arrange |     # Arrange | ||||||
|     with Image.open("Tests/images/imagedraw_chord_RGB.png") as im1: |     with Image.open("Tests/images/imagedraw_chord_RGB.png") as im1: | ||||||
|         with Image.open("Tests/images/imagedraw_outline_chord_RGB.png") as im2: |         with Image.open("Tests/images/imagedraw_outline_chord_RGB.png") as im2: | ||||||
| 
 |  | ||||||
|             # Act |             # Act | ||||||
|             new = ImageChops.subtract(im1, im2, scale=2.5, offset=100) |             new = ImageChops.subtract(im1, im2, scale=2.5, offset=100) | ||||||
| 
 | 
 | ||||||
|  | @ -332,7 +317,6 @@ def test_subtract_clip(): | ||||||
|     # Arrange |     # Arrange | ||||||
|     im1 = hopper() |     im1 = hopper() | ||||||
|     with Image.open("Tests/images/imagedraw_chord_RGB.png") as im2: |     with Image.open("Tests/images/imagedraw_chord_RGB.png") as im2: | ||||||
| 
 |  | ||||||
|         # Act |         # Act | ||||||
|         new = ImageChops.subtract(im1, im2) |         new = ImageChops.subtract(im1, im2) | ||||||
| 
 | 
 | ||||||
|  | @ -344,7 +328,6 @@ def test_subtract_modulo(): | ||||||
|     # Arrange |     # Arrange | ||||||
|     with Image.open("Tests/images/imagedraw_chord_RGB.png") as im1: |     with Image.open("Tests/images/imagedraw_chord_RGB.png") as im1: | ||||||
|         with Image.open("Tests/images/imagedraw_outline_chord_RGB.png") as im2: |         with Image.open("Tests/images/imagedraw_outline_chord_RGB.png") as im2: | ||||||
| 
 |  | ||||||
|             # Act |             # Act | ||||||
|             new = ImageChops.subtract_modulo(im1, im2) |             new = ImageChops.subtract_modulo(im1, im2) | ||||||
| 
 | 
 | ||||||
|  | @ -358,7 +341,6 @@ def test_subtract_modulo_no_clip(): | ||||||
|     # Arrange |     # Arrange | ||||||
|     im1 = hopper() |     im1 = hopper() | ||||||
|     with Image.open("Tests/images/imagedraw_chord_RGB.png") as im2: |     with Image.open("Tests/images/imagedraw_chord_RGB.png") as im2: | ||||||
| 
 |  | ||||||
|         # Act |         # Act | ||||||
|         new = ImageChops.subtract_modulo(im1, im2) |         new = ImageChops.subtract_modulo(im1, im2) | ||||||
| 
 | 
 | ||||||
|  | @ -370,7 +352,6 @@ def test_soft_light(): | ||||||
|     # Arrange |     # Arrange | ||||||
|     with Image.open("Tests/images/hopper.png") as im1: |     with Image.open("Tests/images/hopper.png") as im1: | ||||||
|         with Image.open("Tests/images/hopper-XYZ.png") as im2: |         with Image.open("Tests/images/hopper-XYZ.png") as im2: | ||||||
| 
 |  | ||||||
|             # Act |             # Act | ||||||
|             new = ImageChops.soft_light(im1, im2) |             new = ImageChops.soft_light(im1, im2) | ||||||
| 
 | 
 | ||||||
|  | @ -383,7 +364,6 @@ def test_hard_light(): | ||||||
|     # Arrange |     # Arrange | ||||||
|     with Image.open("Tests/images/hopper.png") as im1: |     with Image.open("Tests/images/hopper.png") as im1: | ||||||
|         with Image.open("Tests/images/hopper-XYZ.png") as im2: |         with Image.open("Tests/images/hopper-XYZ.png") as im2: | ||||||
| 
 |  | ||||||
|             # Act |             # Act | ||||||
|             new = ImageChops.hard_light(im1, im2) |             new = ImageChops.hard_light(im1, im2) | ||||||
| 
 | 
 | ||||||
|  | @ -396,7 +376,6 @@ def test_overlay(): | ||||||
|     # Arrange |     # Arrange | ||||||
|     with Image.open("Tests/images/hopper.png") as im1: |     with Image.open("Tests/images/hopper.png") as im1: | ||||||
|         with Image.open("Tests/images/hopper-XYZ.png") as im2: |         with Image.open("Tests/images/hopper-XYZ.png") as im2: | ||||||
| 
 |  | ||||||
|             # Act |             # Act | ||||||
|             new = ImageChops.overlay(im1, im2) |             new = ImageChops.overlay(im1, im2) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -625,3 +625,14 @@ def test_constants_deprecation(): | ||||||
|         for name in enum.__members__: |         for name in enum.__members__: | ||||||
|             with pytest.warns(DeprecationWarning): |             with pytest.warns(DeprecationWarning): | ||||||
|                 assert getattr(ImageCms, prefix + name) == enum[name] |                 assert getattr(ImageCms, prefix + name) == enum[name] | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @pytest.mark.parametrize("mode", ("RGB", "RGBA", "RGBX")) | ||||||
|  | def test_rgb_lab(mode): | ||||||
|  |     im = Image.new(mode, (1, 1)) | ||||||
|  |     converted_im = im.convert("LAB") | ||||||
|  |     assert converted_im.getpixel((0, 0)) == (0, 128, 128) | ||||||
|  | 
 | ||||||
|  |     im = Image.new("LAB", (1, 1), (255, 0, 0)) | ||||||
|  |     converted_im = im.convert(mode) | ||||||
|  |     assert converted_im.getpixel((0, 0))[:3] == (0, 255, 255) | ||||||
|  |  | ||||||
|  | @ -52,7 +52,6 @@ def test_sanity(): | ||||||
| 
 | 
 | ||||||
| def test_valueerror(): | def test_valueerror(): | ||||||
|     with Image.open("Tests/images/chi.gif") as im: |     with Image.open("Tests/images/chi.gif") as im: | ||||||
| 
 |  | ||||||
|         draw = ImageDraw.Draw(im) |         draw = ImageDraw.Draw(im) | ||||||
|         draw.line((0, 0), fill=(0, 0, 0)) |         draw.line((0, 0), fill=(0, 0, 0)) | ||||||
| 
 | 
 | ||||||
|  | @ -356,7 +355,13 @@ def ellipse_various_sizes_helper(filled): | ||||||
|     for w in ellipse_sizes: |     for w in ellipse_sizes: | ||||||
|         y = 1 |         y = 1 | ||||||
|         for h in ellipse_sizes: |         for h in ellipse_sizes: | ||||||
|             border = [x, y, x + w - 1, y + h - 1] |             x1 = x + w | ||||||
|  |             if w: | ||||||
|  |                 x1 -= 1 | ||||||
|  |             y1 = y + h | ||||||
|  |             if h: | ||||||
|  |                 y1 -= 1 | ||||||
|  |             border = [x, y, x1, y1] | ||||||
|             if filled: |             if filled: | ||||||
|                 draw.ellipse(border, fill="white") |                 draw.ellipse(border, fill="white") | ||||||
|             else: |             else: | ||||||
|  | @ -736,6 +741,36 @@ def test_rounded_rectangle(xy): | ||||||
|     assert_image_equal_tofile(im, "Tests/images/imagedraw_rounded_rectangle.png") |     assert_image_equal_tofile(im, "Tests/images/imagedraw_rounded_rectangle.png") | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @pytest.mark.parametrize("top_left", (True, False)) | ||||||
|  | @pytest.mark.parametrize("top_right", (True, False)) | ||||||
|  | @pytest.mark.parametrize("bottom_right", (True, False)) | ||||||
|  | @pytest.mark.parametrize("bottom_left", (True, False)) | ||||||
|  | def test_rounded_rectangle_corners(top_left, top_right, bottom_right, bottom_left): | ||||||
|  |     corners = (top_left, top_right, bottom_right, bottom_left) | ||||||
|  | 
 | ||||||
|  |     # Arrange | ||||||
|  |     im = Image.new("RGB", (200, 200)) | ||||||
|  |     draw = ImageDraw.Draw(im) | ||||||
|  | 
 | ||||||
|  |     # Act | ||||||
|  |     draw.rounded_rectangle( | ||||||
|  |         (10, 20, 190, 180), 30, fill="red", outline="green", width=5, corners=corners | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     # Assert | ||||||
|  |     suffix = "".join( | ||||||
|  |         ( | ||||||
|  |             ("y" if top_left else "n"), | ||||||
|  |             ("y" if top_right else "n"), | ||||||
|  |             ("y" if bottom_right else "n"), | ||||||
|  |             ("y" if bottom_left else "n"), | ||||||
|  |         ) | ||||||
|  |     ) | ||||||
|  |     assert_image_equal_tofile( | ||||||
|  |         im, "Tests/images/imagedraw_rounded_rectangle_corners_" + suffix + ".png" | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| @pytest.mark.parametrize( | @pytest.mark.parametrize( | ||||||
|     "xy, radius, type", |     "xy, radius, type", | ||||||
|     [ |     [ | ||||||
|  | @ -903,9 +938,6 @@ def test_square(): | ||||||
|     img, draw = create_base_image_draw((10, 10)) |     img, draw = create_base_image_draw((10, 10)) | ||||||
|     draw.rectangle((2, 2, 7, 7), BLACK) |     draw.rectangle((2, 2, 7, 7), BLACK) | ||||||
|     assert_image_equal_tofile(img, expected, "square as normal rectangle failed") |     assert_image_equal_tofile(img, expected, "square as normal rectangle failed") | ||||||
|     img, draw = create_base_image_draw((10, 10)) |  | ||||||
|     draw.rectangle((7, 7, 2, 2), BLACK) |  | ||||||
|     assert_image_equal_tofile(img, expected, "square as inverted rectangle failed") |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_triangle_right(): | def test_triangle_right(): | ||||||
|  | @ -1470,3 +1502,21 @@ def test_polygon2(): | ||||||
|     draw.polygon([(18, 30), (19, 31), (18, 30), (85, 30), (60, 72)], "red") |     draw.polygon([(18, 30), (19, 31), (18, 30), (85, 30), (60, 72)], "red") | ||||||
|     expected = "Tests/images/imagedraw_outline_polygon_RGB.png" |     expected = "Tests/images/imagedraw_outline_polygon_RGB.png" | ||||||
|     assert_image_similar_tofile(im, expected, 1) |     assert_image_similar_tofile(im, expected, 1) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @pytest.mark.parametrize("xy", ((1, 1, 0, 1), (1, 1, 1, 0))) | ||||||
|  | def test_incorrectly_ordered_coordinates(xy): | ||||||
|  |     im = Image.new("RGB", (W, H)) | ||||||
|  |     draw = ImageDraw.Draw(im) | ||||||
|  |     with pytest.raises(ValueError): | ||||||
|  |         draw.arc(xy, 10, 260) | ||||||
|  |     with pytest.raises(ValueError): | ||||||
|  |         draw.chord(xy, 10, 260) | ||||||
|  |     with pytest.raises(ValueError): | ||||||
|  |         draw.ellipse(xy) | ||||||
|  |     with pytest.raises(ValueError): | ||||||
|  |         draw.pieslice(xy, 10, 260) | ||||||
|  |     with pytest.raises(ValueError): | ||||||
|  |         draw.rectangle(xy) | ||||||
|  |     with pytest.raises(ValueError): | ||||||
|  |         draw.rounded_rectangle(xy) | ||||||
|  |  | ||||||
|  | @ -30,7 +30,6 @@ SAFEBLOCK = ImageFile.SAFEBLOCK | ||||||
| class TestImageFile: | class TestImageFile: | ||||||
|     def test_parser(self): |     def test_parser(self): | ||||||
|         def roundtrip(format): |         def roundtrip(format): | ||||||
| 
 |  | ||||||
|             im = hopper("L").resize((1000, 1000), Image.Resampling.NEAREST) |             im = hopper("L").resize((1000, 1000), Image.Resampling.NEAREST) | ||||||
|             if format in ("MSP", "XBM"): |             if format in ("MSP", "XBM"): | ||||||
|                 im = im.convert("1") |                 im = im.convert("1") | ||||||
|  |  | ||||||
|  | @ -351,7 +351,8 @@ def test_rotated_transposed_font(font, orientation): | ||||||
|     assert bbox_b[3] == 20 + bbox_a[2] - bbox_a[0] |     assert bbox_b[3] == 20 + bbox_a[2] - bbox_a[0] | ||||||
| 
 | 
 | ||||||
|     # text length is undefined for vertical text |     # text length is undefined for vertical text | ||||||
|     pytest.raises(ValueError, draw.textlength, word) |     with pytest.raises(ValueError): | ||||||
|  |         draw.textlength(word) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @pytest.mark.parametrize( | @pytest.mark.parametrize( | ||||||
|  | @ -872,25 +873,23 @@ def test_anchor_invalid(font): | ||||||
|     d.font = font |     d.font = font | ||||||
| 
 | 
 | ||||||
|     for anchor in ["", "l", "a", "lax", "sa", "xa", "lx"]: |     for anchor in ["", "l", "a", "lax", "sa", "xa", "lx"]: | ||||||
|         pytest.raises(ValueError, lambda: font.getmask2("hello", anchor=anchor)) |         with pytest.raises(ValueError): | ||||||
|         pytest.raises(ValueError, lambda: font.getbbox("hello", anchor=anchor)) |             font.getmask2("hello", anchor=anchor) | ||||||
|         pytest.raises(ValueError, lambda: d.text((0, 0), "hello", anchor=anchor)) |         with pytest.raises(ValueError): | ||||||
|         pytest.raises(ValueError, lambda: d.textbbox((0, 0), "hello", anchor=anchor)) |             font.getbbox("hello", anchor=anchor) | ||||||
|         pytest.raises( |         with pytest.raises(ValueError): | ||||||
|             ValueError, lambda: d.multiline_text((0, 0), "foo\nbar", anchor=anchor) |             d.text((0, 0), "hello", anchor=anchor) | ||||||
|         ) |         with pytest.raises(ValueError): | ||||||
|         pytest.raises( |             d.textbbox((0, 0), "hello", anchor=anchor) | ||||||
|             ValueError, |         with pytest.raises(ValueError): | ||||||
|             lambda: d.multiline_textbbox((0, 0), "foo\nbar", anchor=anchor), |             d.multiline_text((0, 0), "foo\nbar", anchor=anchor) | ||||||
|         ) |         with pytest.raises(ValueError): | ||||||
|  |             d.multiline_textbbox((0, 0), "foo\nbar", anchor=anchor) | ||||||
|     for anchor in ["lt", "lb"]: |     for anchor in ["lt", "lb"]: | ||||||
|         pytest.raises( |         with pytest.raises(ValueError): | ||||||
|             ValueError, lambda: d.multiline_text((0, 0), "foo\nbar", anchor=anchor) |             d.multiline_text((0, 0), "foo\nbar", anchor=anchor) | ||||||
|         ) |         with pytest.raises(ValueError): | ||||||
|         pytest.raises( |             d.multiline_textbbox((0, 0), "foo\nbar", anchor=anchor) | ||||||
|             ValueError, |  | ||||||
|             lambda: d.multiline_textbbox((0, 0), "foo\nbar", anchor=anchor), |  | ||||||
|         ) |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @pytest.mark.parametrize("bpp", (1, 2, 4, 8)) | @pytest.mark.parametrize("bpp", (1, 2, 4, 8)) | ||||||
|  |  | ||||||
|  | @ -360,37 +360,20 @@ def test_anchor_invalid_ttb(): | ||||||
|     d.font = font |     d.font = font | ||||||
| 
 | 
 | ||||||
|     for anchor in ["", "l", "a", "lax", "xa", "la", "ls", "ld", "lx"]: |     for anchor in ["", "l", "a", "lax", "xa", "la", "ls", "ld", "lx"]: | ||||||
|         pytest.raises( |         with pytest.raises(ValueError): | ||||||
|             ValueError, lambda: font.getmask2("hello", anchor=anchor, direction="ttb") |             font.getmask2("hello", anchor=anchor, direction="ttb") | ||||||
|         ) |         with pytest.raises(ValueError): | ||||||
|         pytest.raises( |             font.getbbox("hello", anchor=anchor, direction="ttb") | ||||||
|             ValueError, lambda: font.getbbox("hello", anchor=anchor, direction="ttb") |         with pytest.raises(ValueError): | ||||||
|         ) |             d.text((0, 0), "hello", anchor=anchor, direction="ttb") | ||||||
|         pytest.raises( |         with pytest.raises(ValueError): | ||||||
|             ValueError, lambda: d.text((0, 0), "hello", anchor=anchor, direction="ttb") |             d.textbbox((0, 0), "hello", anchor=anchor, direction="ttb") | ||||||
|         ) |         with pytest.raises(ValueError): | ||||||
|         pytest.raises( |             d.multiline_text((0, 0), "foo\nbar", anchor=anchor, direction="ttb") | ||||||
|             ValueError, |         with pytest.raises(ValueError): | ||||||
|             lambda: d.textbbox((0, 0), "hello", anchor=anchor, direction="ttb"), |             d.multiline_textbbox((0, 0), "foo\nbar", anchor=anchor, direction="ttb") | ||||||
|         ) |  | ||||||
|         pytest.raises( |  | ||||||
|             ValueError, |  | ||||||
|             lambda: d.multiline_text( |  | ||||||
|                 (0, 0), "foo\nbar", anchor=anchor, direction="ttb" |  | ||||||
|             ), |  | ||||||
|         ) |  | ||||||
|         pytest.raises( |  | ||||||
|             ValueError, |  | ||||||
|             lambda: d.multiline_textbbox( |  | ||||||
|                 (0, 0), "foo\nbar", anchor=anchor, direction="ttb" |  | ||||||
|             ), |  | ||||||
|         ) |  | ||||||
|     # ttb multiline text does not support anchors at all |     # ttb multiline text does not support anchors at all | ||||||
|     pytest.raises( |     with pytest.raises(ValueError): | ||||||
|         ValueError, |         d.multiline_text((0, 0), "foo\nbar", anchor="mm", direction="ttb") | ||||||
|         lambda: d.multiline_text((0, 0), "foo\nbar", anchor="mm", direction="ttb"), |     with pytest.raises(ValueError): | ||||||
|     ) |         d.multiline_textbbox((0, 0), "foo\nbar", anchor="mm", direction="ttb") | ||||||
|     pytest.raises( |  | ||||||
|         ValueError, |  | ||||||
|         lambda: d.multiline_textbbox((0, 0), "foo\nbar", anchor="mm", direction="ttb"), |  | ||||||
|     ) |  | ||||||
|  |  | ||||||
|  | @ -21,7 +21,6 @@ deformer = Deformer() | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_sanity(): | def test_sanity(): | ||||||
| 
 |  | ||||||
|     ImageOps.autocontrast(hopper("L")) |     ImageOps.autocontrast(hopper("L")) | ||||||
|     ImageOps.autocontrast(hopper("RGB")) |     ImageOps.autocontrast(hopper("RGB")) | ||||||
| 
 | 
 | ||||||
|  | @ -419,7 +418,6 @@ def test_autocontrast_cutoff(): | ||||||
| def test_autocontrast_mask_toy_input(): | def test_autocontrast_mask_toy_input(): | ||||||
|     # Test the mask argument of autocontrast |     # Test the mask argument of autocontrast | ||||||
|     with Image.open("Tests/images/bw_gradient.png") as img: |     with Image.open("Tests/images/bw_gradient.png") as img: | ||||||
| 
 |  | ||||||
|         rect_mask = Image.new("L", img.size, 0) |         rect_mask = Image.new("L", img.size, 0) | ||||||
|         draw = ImageDraw.Draw(rect_mask) |         draw = ImageDraw.Draw(rect_mask) | ||||||
|         x0 = img.size[0] // 4 |         x0 = img.size[0] // 4 | ||||||
|  | @ -439,7 +437,6 @@ def test_autocontrast_mask_toy_input(): | ||||||
| def test_autocontrast_mask_real_input(): | def test_autocontrast_mask_real_input(): | ||||||
|     # Test the autocontrast with a rectangular mask |     # Test the autocontrast with a rectangular mask | ||||||
|     with Image.open("Tests/images/iptc.jpg") as img: |     with Image.open("Tests/images/iptc.jpg") as img: | ||||||
| 
 |  | ||||||
|         rect_mask = Image.new("L", img.size, 0) |         rect_mask = Image.new("L", img.size, 0) | ||||||
|         draw = ImageDraw.Draw(rect_mask) |         draw = ImageDraw.Draw(rect_mask) | ||||||
|         x0, y0 = img.size[0] // 2, img.size[1] // 2 |         x0, y0 = img.size[0] // 2, img.size[1] // 2 | ||||||
|  |  | ||||||
|  | @ -6,7 +6,6 @@ from .helper import assert_image_equal, assert_image_equal_tofile | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_sanity(): | def test_sanity(): | ||||||
| 
 |  | ||||||
|     palette = ImagePalette.ImagePalette("RGB", list(range(256)) * 3) |     palette = ImagePalette.ImagePalette("RGB", list(range(256)) * 3) | ||||||
|     assert len(palette.colors) == 256 |     assert len(palette.colors) == 256 | ||||||
| 
 | 
 | ||||||
|  | @ -23,7 +22,6 @@ def test_reload(): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_getcolor(): | def test_getcolor(): | ||||||
| 
 |  | ||||||
|     palette = ImagePalette.ImagePalette() |     palette = ImagePalette.ImagePalette() | ||||||
|     assert len(palette.palette) == 0 |     assert len(palette.palette) == 0 | ||||||
|     assert len(palette.colors) == 0 |     assert len(palette.colors) == 0 | ||||||
|  | @ -84,7 +82,6 @@ def test_getcolor_not_special(index, palette): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_file(tmp_path): | def test_file(tmp_path): | ||||||
| 
 |  | ||||||
|     palette = ImagePalette.ImagePalette("RGB", list(range(256)) * 3) |     palette = ImagePalette.ImagePalette("RGB", list(range(256)) * 3) | ||||||
| 
 | 
 | ||||||
|     f = str(tmp_path / "temp.lut") |     f = str(tmp_path / "temp.lut") | ||||||
|  |  | ||||||
|  | @ -8,7 +8,6 @@ from PIL import Image, ImagePath | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_path(): | def test_path(): | ||||||
| 
 |  | ||||||
|     p = ImagePath.Path(list(range(10))) |     p = ImagePath.Path(list(range(10))) | ||||||
| 
 | 
 | ||||||
|     # sequence interface |     # sequence interface | ||||||
|  |  | ||||||
|  | @ -6,7 +6,6 @@ from .helper import assert_image_equal, hopper, skip_unless_feature | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_sanity(tmp_path): | def test_sanity(tmp_path): | ||||||
| 
 |  | ||||||
|     test_file = str(tmp_path / "temp.im") |     test_file = str(tmp_path / "temp.im") | ||||||
| 
 | 
 | ||||||
|     im = hopper("RGB") |     im = hopper("RGB") | ||||||
|  |  | ||||||
|  | @ -55,8 +55,8 @@ def test_show_without_viewers(): | ||||||
|     viewers = ImageShow._viewers |     viewers = ImageShow._viewers | ||||||
|     ImageShow._viewers = [] |     ImageShow._viewers = [] | ||||||
| 
 | 
 | ||||||
|     im = hopper() |     with hopper() as im: | ||||||
|     assert not ImageShow.show(im) |         assert not ImageShow.show(im) | ||||||
| 
 | 
 | ||||||
|     ImageShow._viewers = viewers |     ImageShow._viewers = viewers | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -6,7 +6,6 @@ from .helper import hopper | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_sanity(): | def test_sanity(): | ||||||
| 
 |  | ||||||
|     im = hopper() |     im = hopper() | ||||||
| 
 | 
 | ||||||
|     st = ImageStat.Stat(im) |     st = ImageStat.Stat(im) | ||||||
|  | @ -31,7 +30,6 @@ def test_sanity(): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_hopper(): | def test_hopper(): | ||||||
| 
 |  | ||||||
|     im = hopper() |     im = hopper() | ||||||
| 
 | 
 | ||||||
|     st = ImageStat.Stat(im) |     st = ImageStat.Stat(im) | ||||||
|  | @ -45,7 +43,6 @@ def test_hopper(): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_constant(): | def test_constant(): | ||||||
| 
 |  | ||||||
|     im = Image.new("L", (128, 128), 128) |     im = Image.new("L", (128, 128), 128) | ||||||
| 
 | 
 | ||||||
|     st = ImageStat.Stat(im) |     st = ImageStat.Stat(im) | ||||||
|  |  | ||||||
|  | @ -100,8 +100,11 @@ class TestImageWinDib: | ||||||
|         # Act |         # Act | ||||||
|         # Make one the same as the using tobytes()/frombytes() |         # Make one the same as the using tobytes()/frombytes() | ||||||
|         test_buffer = dib1.tobytes() |         test_buffer = dib1.tobytes() | ||||||
|         dib2.frombytes(test_buffer) |         for datatype in ("bytes", "memoryview"): | ||||||
|  |             if datatype == "memoryview": | ||||||
|  |                 test_buffer = memoryview(test_buffer) | ||||||
|  |             dib2.frombytes(test_buffer) | ||||||
| 
 | 
 | ||||||
|         # Assert |             # Assert | ||||||
|         # Confirm they're the same |             # Confirm they're the same | ||||||
|         assert dib1.tobytes() == dib2.tobytes() |             assert dib1.tobytes() == dib2.tobytes() | ||||||
|  |  | ||||||
|  | @ -4,7 +4,6 @@ from PIL import Image | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_setmode(): | def test_setmode(): | ||||||
| 
 |  | ||||||
|     im = Image.new("L", (1, 1), 255) |     im = Image.new("L", (1, 1), 255) | ||||||
|     im.im.setmode("1") |     im.im.setmode("1") | ||||||
|     assert im.im.getpixel((0, 0)) == 255 |     assert im.im.getpixel((0, 0)) == 255 | ||||||
|  |  | ||||||
|  | @ -42,7 +42,6 @@ def test_basic(tmp_path, mode): | ||||||
|     im_in.save(filename) |     im_in.save(filename) | ||||||
| 
 | 
 | ||||||
|     with Image.open(filename) as im_out: |     with Image.open(filename) as im_out: | ||||||
| 
 |  | ||||||
|         verify(im_in) |         verify(im_in) | ||||||
|         verify(im_out) |         verify(im_out) | ||||||
| 
 | 
 | ||||||
|  | @ -87,7 +86,6 @@ def test_tobytes(): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_convert(): | def test_convert(): | ||||||
| 
 |  | ||||||
|     im = original.copy() |     im = original.copy() | ||||||
| 
 | 
 | ||||||
|     verify(im.convert("I;16")) |     verify(im.convert("I;16")) | ||||||
|  |  | ||||||
|  | @ -235,7 +235,6 @@ def test_no_resource_warning_for_numpy_array(): | ||||||
| 
 | 
 | ||||||
|     test_file = "Tests/images/hopper.png" |     test_file = "Tests/images/hopper.png" | ||||||
|     with Image.open(test_file) as im: |     with Image.open(test_file) as im: | ||||||
| 
 |  | ||||||
|         # Act/Assert |         # Act/Assert | ||||||
|         with warnings.catch_warnings(): |         with warnings.catch_warnings(): | ||||||
|             array(im) |             array(im) | ||||||
|  |  | ||||||
|  | @ -89,7 +89,6 @@ def test_pickle_la_mode_with_palette(tmp_path): | ||||||
| def test_pickle_tell(): | def test_pickle_tell(): | ||||||
|     # Arrange |     # Arrange | ||||||
|     with Image.open("Tests/images/hopper.webp") as image: |     with Image.open("Tests/images/hopper.webp") as image: | ||||||
| 
 |  | ||||||
|         # Act: roundtrip |         # Act: roundtrip | ||||||
|         unpickled_image = pickle.loads(pickle.dumps(image)) |         unpickled_image = pickle.loads(pickle.dumps(image)) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||