mirror of
				https://github.com/python-pillow/Pillow.git
				synced 2025-11-04 09:57:43 +03:00 
			
		
		
		
	Merge branch 'main' into imagegrab
This commit is contained in:
		
						commit
						ca9f4f8304
					
				| 
						 | 
					@ -1,99 +0,0 @@
 | 
				
			||||||
skip_commits:
 | 
					 | 
				
			||||||
  files:
 | 
					 | 
				
			||||||
    - ".github/**/*"
 | 
					 | 
				
			||||||
    - ".gitmodules"
 | 
					 | 
				
			||||||
    - "docs/**/*"
 | 
					 | 
				
			||||||
    - "wheels/**/*"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
version: '{build}'
 | 
					 | 
				
			||||||
clone_folder: c:\pillow
 | 
					 | 
				
			||||||
init:
 | 
					 | 
				
			||||||
- ECHO %PYTHON%
 | 
					 | 
				
			||||||
#- ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
 | 
					 | 
				
			||||||
# Uncomment previous line to get RDP access during the build.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
environment:
 | 
					 | 
				
			||||||
  COVERAGE_CORE: sysmon
 | 
					 | 
				
			||||||
  EXECUTABLE: python.exe
 | 
					 | 
				
			||||||
  TEST_OPTIONS:
 | 
					 | 
				
			||||||
  DEPLOY: YES
 | 
					 | 
				
			||||||
  matrix:
 | 
					 | 
				
			||||||
  - PYTHON: C:/Python313
 | 
					 | 
				
			||||||
    ARCHITECTURE: x86
 | 
					 | 
				
			||||||
    APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022
 | 
					 | 
				
			||||||
  - PYTHON: C:/Python39-x64
 | 
					 | 
				
			||||||
    ARCHITECTURE: AMD64
 | 
					 | 
				
			||||||
    APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
install:
 | 
					 | 
				
			||||||
- '%PYTHON%\%EXECUTABLE% --version'
 | 
					 | 
				
			||||||
- '%PYTHON%\%EXECUTABLE% -m pip install --upgrade pip'
 | 
					 | 
				
			||||||
- curl -fsSL -o pillow-test-images.zip https://github.com/python-pillow/test-images/archive/main.zip
 | 
					 | 
				
			||||||
- 7z x pillow-test-images.zip -oc:\
 | 
					 | 
				
			||||||
- xcopy /S /Y c:\test-images-main\* c:\pillow\tests\images
 | 
					 | 
				
			||||||
- curl -fsSL -o nasm-win64.zip https://raw.githubusercontent.com/python-pillow/pillow-depends/main/nasm-2.16.03-win64.zip
 | 
					 | 
				
			||||||
- 7z x nasm-win64.zip -oc:\
 | 
					 | 
				
			||||||
- choco install ghostscript --version=10.4.0
 | 
					 | 
				
			||||||
- path c:\nasm-2.16.03;C:\Program Files\gs\gs10.04.0\bin;%PATH%
 | 
					 | 
				
			||||||
- cd c:\pillow\winbuild\
 | 
					 | 
				
			||||||
- ps: |
 | 
					 | 
				
			||||||
        c:\python39\python.exe c:\pillow\winbuild\build_prepare.py -v --depends=C:\pillow-depends\
 | 
					 | 
				
			||||||
        c:\pillow\winbuild\build\build_dep_all.cmd
 | 
					 | 
				
			||||||
        $host.SetShouldExit(0)
 | 
					 | 
				
			||||||
- path C:\pillow\winbuild\build\bin;%PATH%
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
build_script:
 | 
					 | 
				
			||||||
- cd c:\pillow
 | 
					 | 
				
			||||||
- winbuild\build\build_env.cmd
 | 
					 | 
				
			||||||
- '%PYTHON%\%EXECUTABLE% -m pip install -v -C raqm=vendor -C fribidi=vendor .'
 | 
					 | 
				
			||||||
- '%PYTHON%\%EXECUTABLE% selftest.py --installed'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
test_script:
 | 
					 | 
				
			||||||
- cd c:\pillow
 | 
					 | 
				
			||||||
- '%PYTHON%\%EXECUTABLE% -m pip install pytest pytest-cov pytest-timeout defusedxml ipython numpy olefile pyroma'
 | 
					 | 
				
			||||||
- c:\"Program Files (x86)"\"Windows Kits"\10\Debuggers\x86\gflags.exe /p /enable %PYTHON%\%EXECUTABLE%
 | 
					 | 
				
			||||||
- path %PYTHON%;%PATH%
 | 
					 | 
				
			||||||
- .ci\test.cmd
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
after_test:
 | 
					 | 
				
			||||||
- curl -Os https://uploader.codecov.io/latest/windows/codecov.exe
 | 
					 | 
				
			||||||
- .\codecov.exe --file coverage.xml --name %PYTHON% --flags AppVeyor
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
matrix:
 | 
					 | 
				
			||||||
  fast_finish: true
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
cache:
 | 
					 | 
				
			||||||
- '%LOCALAPPDATA%\pip\Cache'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
artifacts:
 | 
					 | 
				
			||||||
- path: pillow\*.egg
 | 
					 | 
				
			||||||
  name: egg
 | 
					 | 
				
			||||||
- path: pillow\*.whl
 | 
					 | 
				
			||||||
  name: wheel
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
before_deploy:
 | 
					 | 
				
			||||||
  - cd c:\pillow
 | 
					 | 
				
			||||||
  - '%PYTHON%\%EXECUTABLE% -m pip wheel -v -C raqm=vendor -C fribidi=vendor .'
 | 
					 | 
				
			||||||
  - ps: Get-ChildItem .\*.whl | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
deploy:
 | 
					 | 
				
			||||||
  provider: S3
 | 
					 | 
				
			||||||
  region: us-west-2
 | 
					 | 
				
			||||||
  access_key_id: AKIAIRAXC62ZNTVQJMOQ
 | 
					 | 
				
			||||||
  secret_access_key:
 | 
					 | 
				
			||||||
    secure: Hwb6klTqtBeMgxAjRoDltiiqpuH8xbwD4UooDzBSiCWXjuFj1lyl4kHgHwTCCGqi
 | 
					 | 
				
			||||||
  bucket: pillow-nightly
 | 
					 | 
				
			||||||
  folder: win/$(APPVEYOR_BUILD_NUMBER)/
 | 
					 | 
				
			||||||
  artifact: /.*egg|wheel/
 | 
					 | 
				
			||||||
  on:
 | 
					 | 
				
			||||||
    APPVEYOR_REPO_NAME: python-pillow/Pillow
 | 
					 | 
				
			||||||
    branch: main
 | 
					 | 
				
			||||||
    deploy: YES
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Uncomment the following lines to get RDP access after the build/test and block for
 | 
					 | 
				
			||||||
# up to the timeout limit (~1hr)
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
#on_finish:
 | 
					 | 
				
			||||||
#- ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
 | 
					 | 
				
			||||||
| 
						 | 
					@ -2,8 +2,4 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# gather the coverage data
 | 
					# gather the coverage data
 | 
				
			||||||
python3 -m pip install coverage
 | 
					python3 -m pip install coverage
 | 
				
			||||||
if [[ $MATRIX_DOCKER ]]; then
 | 
					 | 
				
			||||||
  python3 -m coverage xml --ignore-errors
 | 
					 | 
				
			||||||
else
 | 
					 | 
				
			||||||
python3 -m coverage xml
 | 
					python3 -m coverage xml
 | 
				
			||||||
fi
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,8 +3,5 @@
 | 
				
			||||||
set -e
 | 
					set -e
 | 
				
			||||||
 | 
					
 | 
				
			||||||
python3 -m coverage erase
 | 
					python3 -m coverage erase
 | 
				
			||||||
if [ $(uname) == "Darwin" ]; then
 | 
					 | 
				
			||||||
    export CPPFLAGS="-I/usr/local/miniconda/include";
 | 
					 | 
				
			||||||
fi
 | 
					 | 
				
			||||||
make clean
 | 
					make clean
 | 
				
			||||||
make install-coverage
 | 
					make install-coverage
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,12 +2,12 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
aptget_update()
 | 
					aptget_update()
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    if [ ! -z $1 ]; then
 | 
					    if [ -n "$1" ]; then
 | 
				
			||||||
        echo ""
 | 
					        echo ""
 | 
				
			||||||
        echo "Retrying apt-get update..."
 | 
					        echo "Retrying apt-get update..."
 | 
				
			||||||
        echo ""
 | 
					        echo ""
 | 
				
			||||||
    fi
 | 
					    fi
 | 
				
			||||||
    output=`sudo apt-get update 2>&1`
 | 
					    output=$(sudo apt-get update 2>&1)
 | 
				
			||||||
    echo "$output"
 | 
					    echo "$output"
 | 
				
			||||||
    if [[ $output == *[WE]:\ * ]]; then
 | 
					    if [[ $output == *[WE]:\ * ]]; then
 | 
				
			||||||
        return 1
 | 
					        return 1
 | 
				
			||||||
| 
						 | 
					@ -20,8 +20,8 @@ fi
 | 
				
			||||||
set -e
 | 
					set -e
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if [[ $(uname) != CYGWIN* ]]; then
 | 
					if [[ $(uname) != CYGWIN* ]]; then
 | 
				
			||||||
    sudo apt-get -qq install libfreetype6-dev liblcms2-dev python3-tk\
 | 
					    sudo apt-get -qq install libfreetype6-dev liblcms2-dev libtiff-dev python3-tk\
 | 
				
			||||||
                             ghostscript libjpeg-turbo-progs libopenjp2-7-dev\
 | 
					                             ghostscript libjpeg-turbo8-dev libopenjp2-7-dev\
 | 
				
			||||||
                             cmake meson imagemagick libharfbuzz-dev libfribidi-dev\
 | 
					                             cmake meson imagemagick libharfbuzz-dev libfribidi-dev\
 | 
				
			||||||
                             sway wl-clipboard libopenblas-dev
 | 
					                             sway wl-clipboard libopenblas-dev
 | 
				
			||||||
fi
 | 
					fi
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1 +1 @@
 | 
				
			||||||
cibuildwheel==2.21.3
 | 
					cibuildwheel==2.23.2
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,4 @@
 | 
				
			||||||
mypy==1.13.0
 | 
					mypy==1.15.0
 | 
				
			||||||
IceSpringPySideStubs-PyQt6
 | 
					IceSpringPySideStubs-PyQt6
 | 
				
			||||||
IceSpringPySideStubs-PySide6
 | 
					IceSpringPySideStubs-PySide6
 | 
				
			||||||
ipython
 | 
					ipython
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										5
									
								
								.github/CONTRIBUTING.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								.github/CONTRIBUTING.md
									
									
									
									
										vendored
									
									
								
							| 
						 | 
					@ -9,7 +9,7 @@ Please send a pull request to the `main` branch. Please include [documentation](
 | 
				
			||||||
- Fork the Pillow repository.
 | 
					- Fork the Pillow repository.
 | 
				
			||||||
- Create a branch from `main`.
 | 
					- Create a branch from `main`.
 | 
				
			||||||
- Develop bug fixes, features, tests, etc.
 | 
					- Develop bug fixes, features, tests, etc.
 | 
				
			||||||
- Run the test suite. You can enable GitHub Actions (https://github.com/MY-USERNAME/Pillow/actions) and [AppVeyor](https://ci.appveyor.com/projects/new) on your repo to catch test failures prior to the pull request, and [Codecov](https://codecov.io/gh) to see if the changed code is covered by tests.
 | 
					- Run the test suite. You can enable GitHub Actions (https://github.com/MY-USERNAME/Pillow/actions) on your repo to catch test failures prior to the pull request, and [Codecov](https://codecov.io/gh) to see if the changed code is covered by tests.
 | 
				
			||||||
- Create a pull request to pull the changes from your branch to the Pillow `main`.
 | 
					- Create a pull request to pull the changes from your branch to the Pillow `main`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Guidelines
 | 
					### Guidelines
 | 
				
			||||||
| 
						 | 
					@ -17,9 +17,8 @@ Please send a pull request to the `main` branch. Please include [documentation](
 | 
				
			||||||
- Separate code commits from reformatting commits.
 | 
					- Separate code commits from reformatting commits.
 | 
				
			||||||
- Provide tests for any newly added code.
 | 
					- Provide tests for any newly added code.
 | 
				
			||||||
- Follow PEP 8.
 | 
					- Follow PEP 8.
 | 
				
			||||||
- When committing only documentation changes please include `[ci skip]` in the commit message to avoid running tests on AppVeyor.
 | 
					- When committing only documentation changes please include `[ci skip]` in the commit message to avoid running extra tests.
 | 
				
			||||||
- Include [release notes](https://github.com/python-pillow/Pillow/tree/main/docs/releasenotes) as needed or appropriate with your bug fixes, feature additions and tests.
 | 
					- Include [release notes](https://github.com/python-pillow/Pillow/tree/main/docs/releasenotes) as needed or appropriate with your bug fixes, feature additions and tests.
 | 
				
			||||||
- Do not add to the [changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst) for proposed changes, as that is updated after changes are merged.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Reporting Issues
 | 
					## Reporting Issues
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										1
									
								
								.github/mergify.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/mergify.yml
									
									
									
									
										vendored
									
									
								
							| 
						 | 
					@ -9,7 +9,6 @@ pull_request_rules:
 | 
				
			||||||
      - status-success=Windows Test Successful
 | 
					      - status-success=Windows Test Successful
 | 
				
			||||||
      - status-success=MinGW
 | 
					      - status-success=MinGW
 | 
				
			||||||
      - status-success=Cygwin Test Successful
 | 
					      - status-success=Cygwin Test Successful
 | 
				
			||||||
      - status-success=continuous-integration/appveyor/pr
 | 
					 | 
				
			||||||
    actions:
 | 
					    actions:
 | 
				
			||||||
      merge:
 | 
					      merge:
 | 
				
			||||||
        method: merge
 | 
					        method: merge
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										11
									
								
								.github/release-drafter.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										11
									
								
								.github/release-drafter.yml
									
									
									
									
										vendored
									
									
								
							| 
						 | 
					@ -3,18 +3,19 @@ tag-template: "$NEXT_MINOR_VERSION"
 | 
				
			||||||
change-template: '- $TITLE #$NUMBER [@$AUTHOR]'
 | 
					change-template: '- $TITLE #$NUMBER [@$AUTHOR]'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
categories:
 | 
					categories:
 | 
				
			||||||
  - title: "Dependencies"
 | 
					  - title: "Removals"
 | 
				
			||||||
    label: "Dependency"
 | 
					    label: "Removal"
 | 
				
			||||||
  - title: "Deprecations"
 | 
					  - title: "Deprecations"
 | 
				
			||||||
    label: "Deprecation"
 | 
					    label: "Deprecation"
 | 
				
			||||||
  - title: "Documentation"
 | 
					  - title: "Documentation"
 | 
				
			||||||
    label: "Documentation"
 | 
					    label: "Documentation"
 | 
				
			||||||
  - title: "Removals"
 | 
					  - title: "Dependencies"
 | 
				
			||||||
    label: "Removal"
 | 
					    label: "Dependency"
 | 
				
			||||||
  - title: "Testing"
 | 
					  - title: "Testing"
 | 
				
			||||||
    label: "Testing"
 | 
					    label: "Testing"
 | 
				
			||||||
  - title: "Type hints"
 | 
					  - title: "Type hints"
 | 
				
			||||||
    label: "Type hints"
 | 
					    label: "Type hints"
 | 
				
			||||||
 | 
					  - title: "Other changes"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
exclude-labels:
 | 
					exclude-labels:
 | 
				
			||||||
  - "changelog: skip"
 | 
					  - "changelog: skip"
 | 
				
			||||||
| 
						 | 
					@ -23,6 +24,4 @@ template: |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  https://pillow.readthedocs.io/en/stable/releasenotes/$NEXT_MINOR_VERSION.html
 | 
					  https://pillow.readthedocs.io/en/stable/releasenotes/$NEXT_MINOR_VERSION.html
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  ## Changes
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  $CHANGES
 | 
					  $CHANGES
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										2
									
								
								.github/renovate.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/renovate.json
									
									
									
									
										vendored
									
									
								
							| 
						 | 
					@ -16,6 +16,6 @@
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    "schedule": [
 | 
					    "schedule": [
 | 
				
			||||||
        "on the 3rd day of the month"
 | 
					        "* * 3 * *"
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										8
									
								
								.github/workflows/macos-install.sh
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.github/workflows/macos-install.sh
									
									
									
									
										vendored
									
									
								
							| 
						 | 
					@ -8,17 +8,13 @@ fi
 | 
				
			||||||
brew install \
 | 
					brew install \
 | 
				
			||||||
    freetype \
 | 
					    freetype \
 | 
				
			||||||
    ghostscript \
 | 
					    ghostscript \
 | 
				
			||||||
 | 
					    jpeg-turbo \
 | 
				
			||||||
    libimagequant \
 | 
					    libimagequant \
 | 
				
			||||||
    libjpeg \
 | 
					    libraqm \
 | 
				
			||||||
    libtiff \
 | 
					    libtiff \
 | 
				
			||||||
    little-cms2 \
 | 
					    little-cms2 \
 | 
				
			||||||
    openjpeg \
 | 
					    openjpeg \
 | 
				
			||||||
    webp
 | 
					    webp
 | 
				
			||||||
if [[ "$ImageOS" == "macos13" ]]; then
 | 
					 | 
				
			||||||
    brew install --ignore-dependencies libraqm
 | 
					 | 
				
			||||||
else
 | 
					 | 
				
			||||||
    brew install libraqm
 | 
					 | 
				
			||||||
fi
 | 
					 | 
				
			||||||
export PKG_CONFIG_PATH="/usr/local/opt/openblas/lib/pkgconfig"
 | 
					export PKG_CONFIG_PATH="/usr/local/opt/openblas/lib/pkgconfig"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
python3 -m pip install coverage
 | 
					python3 -m pip install coverage
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										7
									
								
								.github/workflows/test-cygwin.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								.github/workflows/test-cygwin.yml
									
									
									
									
										vendored
									
									
								
							| 
						 | 
					@ -52,7 +52,7 @@ jobs:
 | 
				
			||||||
          persist-credentials: false
 | 
					          persist-credentials: false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Install Cygwin
 | 
					      - name: Install Cygwin
 | 
				
			||||||
        uses: cygwin/cygwin-install-action@v4
 | 
					        uses: cygwin/cygwin-install-action@v5
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          packages: >
 | 
					          packages: >
 | 
				
			||||||
            gcc-g++
 | 
					            gcc-g++
 | 
				
			||||||
| 
						 | 
					@ -133,11 +133,12 @@ jobs:
 | 
				
			||||||
      - name: After success
 | 
					      - name: After success
 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
          bash.exe .ci/after_success.sh
 | 
					          bash.exe .ci/after_success.sh
 | 
				
			||||||
 | 
					          rm C:\cygwin\bin\bash.EXE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Upload coverage
 | 
					      - name: Upload coverage
 | 
				
			||||||
        uses: codecov/codecov-action@v4
 | 
					        uses: codecov/codecov-action@v5
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          file: ./coverage.xml
 | 
					          files: ./coverage.xml
 | 
				
			||||||
          flags: GHA_Cygwin
 | 
					          flags: GHA_Cygwin
 | 
				
			||||||
          name: Cygwin Python 3.${{ matrix.python-minor-version }}
 | 
					          name: Cygwin Python 3.${{ matrix.python-minor-version }}
 | 
				
			||||||
          token: ${{ secrets.CODECOV_ORG_TOKEN }}
 | 
					          token: ${{ secrets.CODECOV_ORG_TOKEN }}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										23
									
								
								.github/workflows/test-docker.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										23
									
								
								.github/workflows/test-docker.yml
									
									
									
									
										vendored
									
									
								
							| 
						 | 
					@ -29,13 +29,13 @@ concurrency:
 | 
				
			||||||
jobs:
 | 
					jobs:
 | 
				
			||||||
  build:
 | 
					  build:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    runs-on: ubuntu-latest
 | 
					    runs-on: ${{ matrix.os }}
 | 
				
			||||||
    strategy:
 | 
					    strategy:
 | 
				
			||||||
      fail-fast: false
 | 
					      fail-fast: false
 | 
				
			||||||
      matrix:
 | 
					      matrix:
 | 
				
			||||||
 | 
					        os: ["ubuntu-latest"]
 | 
				
			||||||
        docker: [
 | 
					        docker: [
 | 
				
			||||||
          # Run slower jobs first to give them a headstart and reduce waiting time
 | 
					          # Run slower jobs first to give them a headstart and reduce waiting time
 | 
				
			||||||
          ubuntu-22.04-jammy-arm64v8,
 | 
					 | 
				
			||||||
          ubuntu-24.04-noble-ppc64le,
 | 
					          ubuntu-24.04-noble-ppc64le,
 | 
				
			||||||
          ubuntu-24.04-noble-s390x,
 | 
					          ubuntu-24.04-noble-s390x,
 | 
				
			||||||
          # Then run the remainder
 | 
					          # Then run the remainder
 | 
				
			||||||
| 
						 | 
					@ -44,6 +44,7 @@ jobs:
 | 
				
			||||||
          amazon-2023-amd64,
 | 
					          amazon-2023-amd64,
 | 
				
			||||||
          arch,
 | 
					          arch,
 | 
				
			||||||
          centos-stream-9-amd64,
 | 
					          centos-stream-9-amd64,
 | 
				
			||||||
 | 
					          centos-stream-10-amd64,
 | 
				
			||||||
          debian-12-bookworm-x86,
 | 
					          debian-12-bookworm-x86,
 | 
				
			||||||
          debian-12-bookworm-amd64,
 | 
					          debian-12-bookworm-amd64,
 | 
				
			||||||
          fedora-40-amd64,
 | 
					          fedora-40-amd64,
 | 
				
			||||||
| 
						 | 
					@ -54,12 +55,13 @@ jobs:
 | 
				
			||||||
        ]
 | 
					        ]
 | 
				
			||||||
        dockerTag: [main]
 | 
					        dockerTag: [main]
 | 
				
			||||||
        include:
 | 
					        include:
 | 
				
			||||||
          - docker: "ubuntu-22.04-jammy-arm64v8"
 | 
					 | 
				
			||||||
            qemu-arch: "aarch64"
 | 
					 | 
				
			||||||
          - docker: "ubuntu-24.04-noble-ppc64le"
 | 
					          - docker: "ubuntu-24.04-noble-ppc64le"
 | 
				
			||||||
            qemu-arch: "ppc64le"
 | 
					            qemu-arch: "ppc64le"
 | 
				
			||||||
          - docker: "ubuntu-24.04-noble-s390x"
 | 
					          - docker: "ubuntu-24.04-noble-s390x"
 | 
				
			||||||
            qemu-arch: "s390x"
 | 
					            qemu-arch: "s390x"
 | 
				
			||||||
 | 
					          - docker: "ubuntu-24.04-noble-arm64v8"
 | 
				
			||||||
 | 
					            os: "ubuntu-24.04-arm"
 | 
				
			||||||
 | 
					            dockerTag: main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    name: ${{ matrix.docker }}
 | 
					    name: ${{ matrix.docker }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -73,8 +75,9 @@ jobs:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    - name: Set up QEMU
 | 
					    - name: Set up QEMU
 | 
				
			||||||
      if: "matrix.qemu-arch"
 | 
					      if: "matrix.qemu-arch"
 | 
				
			||||||
      run: |
 | 
					      uses: docker/setup-qemu-action@v3
 | 
				
			||||||
        docker run --rm --privileged aptman/qus -s -- -p ${{ matrix.qemu-arch }}
 | 
					      with:
 | 
				
			||||||
 | 
					        platforms: ${{ matrix.qemu-arch }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    - name: Docker pull
 | 
					    - name: Docker pull
 | 
				
			||||||
      run: |
 | 
					      run: |
 | 
				
			||||||
| 
						 | 
					@ -89,18 +92,18 @@ jobs:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    - name: After success
 | 
					    - name: After success
 | 
				
			||||||
      run: |
 | 
					      run: |
 | 
				
			||||||
        PATH="$PATH:~/.local/bin"
 | 
					 | 
				
			||||||
        docker start pillow_container
 | 
					        docker start pillow_container
 | 
				
			||||||
 | 
					        sudo docker cp pillow_container:/Pillow /Pillow
 | 
				
			||||||
 | 
					        sudo chown -R runner /Pillow
 | 
				
			||||||
        pil_path=`docker exec pillow_container /vpy3/bin/python -c 'import os, PIL;print(os.path.realpath(os.path.dirname(PIL.__file__)))'`
 | 
					        pil_path=`docker exec pillow_container /vpy3/bin/python -c 'import os, PIL;print(os.path.realpath(os.path.dirname(PIL.__file__)))'`
 | 
				
			||||||
        docker stop pillow_container
 | 
					        docker stop pillow_container
 | 
				
			||||||
        sudo mkdir -p $pil_path
 | 
					        sudo mkdir -p $pil_path
 | 
				
			||||||
        sudo cp src/PIL/*.py $pil_path
 | 
					        sudo cp src/PIL/*.py $pil_path
 | 
				
			||||||
 | 
					        cd /Pillow
 | 
				
			||||||
        .ci/after_success.sh
 | 
					        .ci/after_success.sh
 | 
				
			||||||
      env:
 | 
					 | 
				
			||||||
        MATRIX_DOCKER: ${{ matrix.docker }}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    - name: Upload coverage
 | 
					    - name: Upload coverage
 | 
				
			||||||
      uses: codecov/codecov-action@v4
 | 
					      uses: codecov/codecov-action@v5
 | 
				
			||||||
      with:
 | 
					      with:
 | 
				
			||||||
        flags: GHA_Docker
 | 
					        flags: GHA_Docker
 | 
				
			||||||
        name: ${{ matrix.docker }}
 | 
					        name: ${{ matrix.docker }}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										18
									
								
								.github/workflows/test-mingw.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										18
									
								
								.github/workflows/test-mingw.yml
									
									
									
									
										vendored
									
									
								
							| 
						 | 
					@ -66,18 +66,18 @@ jobs:
 | 
				
			||||||
              mingw-w64-x86_64-libtiff \
 | 
					              mingw-w64-x86_64-libtiff \
 | 
				
			||||||
              mingw-w64-x86_64-libwebp \
 | 
					              mingw-w64-x86_64-libwebp \
 | 
				
			||||||
              mingw-w64-x86_64-openjpeg2 \
 | 
					              mingw-w64-x86_64-openjpeg2 \
 | 
				
			||||||
              mingw-w64-x86_64-python3-numpy \
 | 
					              mingw-w64-x86_64-python-numpy \
 | 
				
			||||||
              mingw-w64-x86_64-python3-olefile \
 | 
					              mingw-w64-x86_64-python-olefile \
 | 
				
			||||||
              mingw-w64-x86_64-python3-setuptools \
 | 
					              mingw-w64-x86_64-python-pip \
 | 
				
			||||||
 | 
					              mingw-w64-x86_64-python-pytest \
 | 
				
			||||||
 | 
					              mingw-w64-x86_64-python-pytest-cov \
 | 
				
			||||||
 | 
					              mingw-w64-x86_64-python-pytest-timeout \
 | 
				
			||||||
              mingw-w64-x86_64-python-pyqt6
 | 
					              mingw-w64-x86_64-python-pyqt6
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          python3 -m ensurepip
 | 
					 | 
				
			||||||
          python3 -m pip install pyroma pytest pytest-cov pytest-timeout
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          pushd depends && ./install_extra_test_images.sh && popd
 | 
					          pushd depends && ./install_extra_test_images.sh && popd
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Build Pillow
 | 
					      - name: Build Pillow
 | 
				
			||||||
        run: SETUPTOOLS_USE_DISTUTILS="stdlib" CFLAGS="-coverage" python3 -m pip install .
 | 
					        run: CFLAGS="-coverage" python3 -m pip install .
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Test Pillow
 | 
					      - name: Test Pillow
 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
| 
						 | 
					@ -85,9 +85,9 @@ jobs:
 | 
				
			||||||
          .ci/test.sh
 | 
					          .ci/test.sh
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Upload coverage
 | 
					      - name: Upload coverage
 | 
				
			||||||
        uses: codecov/codecov-action@v4
 | 
					        uses: codecov/codecov-action@v5
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          file: ./coverage.xml
 | 
					          files: ./coverage.xml
 | 
				
			||||||
          flags: GHA_Windows
 | 
					          flags: GHA_Windows
 | 
				
			||||||
          name: "MSYS2 MinGW"
 | 
					          name: "MSYS2 MinGW"
 | 
				
			||||||
          token: ${{ secrets.CODECOV_ORG_TOKEN }}
 | 
					          token: ${{ secrets.CODECOV_ORG_TOKEN }}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										42
									
								
								.github/workflows/test-windows.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										42
									
								
								.github/workflows/test-windows.yml
									
									
									
									
										vendored
									
									
								
							| 
						 | 
					@ -31,15 +31,20 @@ env:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
jobs:
 | 
					jobs:
 | 
				
			||||||
  build:
 | 
					  build:
 | 
				
			||||||
    runs-on: windows-latest
 | 
					    runs-on: ${{ matrix.os }}
 | 
				
			||||||
    strategy:
 | 
					    strategy:
 | 
				
			||||||
      fail-fast: false
 | 
					      fail-fast: false
 | 
				
			||||||
      matrix:
 | 
					      matrix:
 | 
				
			||||||
        python-version: ["pypy3.10", "3.9", "3.10", "3.11", "3.12", "3.13"]
 | 
					        python-version: ["pypy3.11", "pypy3.10", "3.10", "3.11", "3.12", "3.13", "3.14"]
 | 
				
			||||||
 | 
					        architecture: ["x64"]
 | 
				
			||||||
 | 
					        os: ["windows-latest"]
 | 
				
			||||||
 | 
					        include:
 | 
				
			||||||
 | 
					            # Test the oldest Python on 32-bit
 | 
				
			||||||
 | 
					            - { python-version: "3.9", architecture: "x86", os: "windows-2019" }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    timeout-minutes: 30
 | 
					    timeout-minutes: 30
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    name: Python ${{ matrix.python-version }}
 | 
					    name: Python ${{ matrix.python-version }} (${{ matrix.architecture }})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
    - name: Checkout Pillow
 | 
					    - name: Checkout Pillow
 | 
				
			||||||
| 
						 | 
					@ -67,28 +72,21 @@ jobs:
 | 
				
			||||||
      with:
 | 
					      with:
 | 
				
			||||||
        python-version: ${{ matrix.python-version }}
 | 
					        python-version: ${{ matrix.python-version }}
 | 
				
			||||||
        allow-prereleases: true
 | 
					        allow-prereleases: true
 | 
				
			||||||
 | 
					        architecture: ${{ matrix.architecture }}
 | 
				
			||||||
        cache: pip
 | 
					        cache: pip
 | 
				
			||||||
        cache-dependency-path: ".github/workflows/test-windows.yml"
 | 
					        cache-dependency-path: ".github/workflows/test-windows.yml"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    - name: Print build system information
 | 
					    - name: Print build system information
 | 
				
			||||||
      run: python3 .github/workflows/system-info.py
 | 
					      run: python3 .github/workflows/system-info.py
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    - name: Install Python dependencies
 | 
					    - name: Upgrade pip
 | 
				
			||||||
      run: >
 | 
					      run: |
 | 
				
			||||||
        python3 -m pip install
 | 
					        python3 -m pip install --upgrade pip
 | 
				
			||||||
        coverage>=7.4.2
 | 
					 | 
				
			||||||
        defusedxml
 | 
					 | 
				
			||||||
        olefile
 | 
					 | 
				
			||||||
        pyroma
 | 
					 | 
				
			||||||
        pytest
 | 
					 | 
				
			||||||
        pytest-cov
 | 
					 | 
				
			||||||
        pytest-timeout
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    - name: Install CPython dependencies
 | 
					    - name: Install CPython dependencies
 | 
				
			||||||
      if: "!contains(matrix.python-version, 'pypy')"
 | 
					      if: "!contains(matrix.python-version, 'pypy') && matrix.architecture != 'x86'"
 | 
				
			||||||
      run: >
 | 
					      run: |
 | 
				
			||||||
        python3 -m pip install
 | 
					        python3 -m pip install PyQt6
 | 
				
			||||||
        PyQt6
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    - name: Install dependencies
 | 
					    - name: Install dependencies
 | 
				
			||||||
      id: install
 | 
					      id: install
 | 
				
			||||||
| 
						 | 
					@ -96,8 +94,8 @@ jobs:
 | 
				
			||||||
        choco install nasm --no-progress
 | 
					        choco install nasm --no-progress
 | 
				
			||||||
        echo "C:\Program Files\NASM" >> $env:GITHUB_PATH
 | 
					        echo "C:\Program Files\NASM" >> $env:GITHUB_PATH
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        choco install ghostscript --version=10.4.0 --no-progress
 | 
					        choco install ghostscript --version=10.5.0 --no-progress
 | 
				
			||||||
        echo "C:\Program Files\gs\gs10.04.0\bin" >> $env:GITHUB_PATH
 | 
					        echo "C:\Program Files\gs\gs10.05.0\bin" >> $env:GITHUB_PATH
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Install extra test images
 | 
					        # Install extra test images
 | 
				
			||||||
        xcopy /S /Y Tests\test-images\* Tests\images
 | 
					        xcopy /S /Y Tests\test-images\* Tests\images
 | 
				
			||||||
| 
						 | 
					@ -188,7 +186,7 @@ jobs:
 | 
				
			||||||
    - name: Build Pillow
 | 
					    - name: Build Pillow
 | 
				
			||||||
      run: |
 | 
					      run: |
 | 
				
			||||||
        $FLAGS="-C raqm=vendor -C fribidi=vendor"
 | 
					        $FLAGS="-C raqm=vendor -C fribidi=vendor"
 | 
				
			||||||
        cmd /c "winbuild\build\build_env.cmd && $env:pythonLocation\python.exe -m pip install -v $FLAGS ."
 | 
					        cmd /c "winbuild\build\build_env.cmd && $env:pythonLocation\python.exe -m pip install -v $FLAGS .[tests]"
 | 
				
			||||||
        & $env:pythonLocation\python.exe selftest.py --installed
 | 
					        & $env:pythonLocation\python.exe selftest.py --installed
 | 
				
			||||||
      shell: pwsh
 | 
					      shell: pwsh
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -223,9 +221,9 @@ jobs:
 | 
				
			||||||
      shell: pwsh
 | 
					      shell: pwsh
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    - name: Upload coverage
 | 
					    - name: Upload coverage
 | 
				
			||||||
      uses: codecov/codecov-action@v4
 | 
					      uses: codecov/codecov-action@v5
 | 
				
			||||||
      with:
 | 
					      with:
 | 
				
			||||||
        file: ./coverage.xml
 | 
					        files: ./coverage.xml
 | 
				
			||||||
        flags: GHA_Windows
 | 
					        flags: GHA_Windows
 | 
				
			||||||
        name: ${{ runner.os }} Python ${{ matrix.python-version }}
 | 
					        name: ${{ runner.os }} Python ${{ matrix.python-version }}
 | 
				
			||||||
        token: ${{ secrets.CODECOV_ORG_TOKEN }}
 | 
					        token: ${{ secrets.CODECOV_ORG_TOKEN }}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										19
									
								
								.github/workflows/test.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										19
									
								
								.github/workflows/test.yml
									
									
									
									
										vendored
									
									
								
							| 
						 | 
					@ -41,7 +41,10 @@ jobs:
 | 
				
			||||||
          "ubuntu-latest",
 | 
					          "ubuntu-latest",
 | 
				
			||||||
        ]
 | 
					        ]
 | 
				
			||||||
        python-version: [
 | 
					        python-version: [
 | 
				
			||||||
 | 
					          "pypy3.11",
 | 
				
			||||||
          "pypy3.10",
 | 
					          "pypy3.10",
 | 
				
			||||||
 | 
					          "3.14",
 | 
				
			||||||
 | 
					          "3.13t",
 | 
				
			||||||
          "3.13",
 | 
					          "3.13",
 | 
				
			||||||
          "3.12",
 | 
					          "3.12",
 | 
				
			||||||
          "3.11",
 | 
					          "3.11",
 | 
				
			||||||
| 
						 | 
					@ -52,14 +55,14 @@ jobs:
 | 
				
			||||||
        - { python-version: "3.11", PYTHONOPTIMIZE: 1, REVERSE: "--reverse" }
 | 
					        - { python-version: "3.11", PYTHONOPTIMIZE: 1, REVERSE: "--reverse" }
 | 
				
			||||||
        - { python-version: "3.10", PYTHONOPTIMIZE: 2 }
 | 
					        - { python-version: "3.10", PYTHONOPTIMIZE: 2 }
 | 
				
			||||||
        # Free-threaded
 | 
					        # Free-threaded
 | 
				
			||||||
        - { os: "ubuntu-latest", python-version: "3.13-dev", disable-gil: true }
 | 
					        - { python-version: "3.13t", disable-gil: true }
 | 
				
			||||||
        # M1 only available for 3.10+
 | 
					        # M1 only available for 3.10+
 | 
				
			||||||
        - { os: "macos-13", python-version: "3.9" }
 | 
					        - { os: "macos-13", python-version: "3.9" }
 | 
				
			||||||
        exclude:
 | 
					        exclude:
 | 
				
			||||||
        - { os: "macos-latest", python-version: "3.9" }
 | 
					        - { os: "macos-latest", python-version: "3.9" }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    runs-on: ${{ matrix.os }}
 | 
					    runs-on: ${{ matrix.os }}
 | 
				
			||||||
    name: ${{ matrix.os }} Python ${{ matrix.python-version }} ${{ matrix.disable-gil && 'free-threaded' || '' }}
 | 
					    name: ${{ matrix.os }} Python ${{ matrix.python-version }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
    - uses: actions/checkout@v4
 | 
					    - uses: actions/checkout@v4
 | 
				
			||||||
| 
						 | 
					@ -68,7 +71,6 @@ jobs:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    - name: Set up Python ${{ matrix.python-version }}
 | 
					    - name: Set up Python ${{ matrix.python-version }}
 | 
				
			||||||
      uses: actions/setup-python@v5
 | 
					      uses: actions/setup-python@v5
 | 
				
			||||||
      if: "${{ !matrix.disable-gil }}"
 | 
					 | 
				
			||||||
      with:
 | 
					      with:
 | 
				
			||||||
        python-version: ${{ matrix.python-version }}
 | 
					        python-version: ${{ matrix.python-version }}
 | 
				
			||||||
        allow-prereleases: true
 | 
					        allow-prereleases: true
 | 
				
			||||||
| 
						 | 
					@ -77,13 +79,6 @@ jobs:
 | 
				
			||||||
          ".ci/*.sh"
 | 
					          ".ci/*.sh"
 | 
				
			||||||
          "pyproject.toml"
 | 
					          "pyproject.toml"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    - name: Set up Python ${{ matrix.python-version }} (free-threaded)
 | 
					 | 
				
			||||||
      uses: deadsnakes/action@v3.2.0
 | 
					 | 
				
			||||||
      if: "${{ matrix.disable-gil }}"
 | 
					 | 
				
			||||||
      with:
 | 
					 | 
				
			||||||
        python-version: ${{ matrix.python-version }}
 | 
					 | 
				
			||||||
        nogil: ${{ matrix.disable-gil }}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    - name: Set PYTHON_GIL
 | 
					    - name: Set PYTHON_GIL
 | 
				
			||||||
      if: "${{ matrix.disable-gil }}"
 | 
					      if: "${{ matrix.disable-gil }}"
 | 
				
			||||||
      run: |
 | 
					      run: |
 | 
				
			||||||
| 
						 | 
					@ -116,7 +111,7 @@ jobs:
 | 
				
			||||||
        GHA_PYTHON_VERSION: ${{ matrix.python-version }}
 | 
					        GHA_PYTHON_VERSION: ${{ matrix.python-version }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    - name: Register gcc problem matcher
 | 
					    - name: Register gcc problem matcher
 | 
				
			||||||
      if: "matrix.os == 'ubuntu-latest' && matrix.python-version == '3.12'"
 | 
					      if: "matrix.os == 'ubuntu-latest' && matrix.python-version == '3.13'"
 | 
				
			||||||
      run: echo "::add-matcher::.github/problem-matchers/gcc.json"
 | 
					      run: echo "::add-matcher::.github/problem-matchers/gcc.json"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    - name: Build
 | 
					    - name: Build
 | 
				
			||||||
| 
						 | 
					@ -156,7 +151,7 @@ jobs:
 | 
				
			||||||
        .ci/after_success.sh
 | 
					        .ci/after_success.sh
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    - name: Upload coverage
 | 
					    - name: Upload coverage
 | 
				
			||||||
      uses: codecov/codecov-action@v4
 | 
					      uses: codecov/codecov-action@v5
 | 
				
			||||||
      with:
 | 
					      with:
 | 
				
			||||||
        flags: ${{ matrix.os == 'ubuntu-latest' && 'GHA_Ubuntu' || 'GHA_macOS' }}
 | 
					        flags: ${{ matrix.os == 'ubuntu-latest' && 'GHA_Ubuntu' || 'GHA_macOS' }}
 | 
				
			||||||
        name: ${{ matrix.os }} Python ${{ matrix.python-version }}
 | 
					        name: ${{ matrix.os }} Python ${{ matrix.python-version }}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										196
									
								
								.github/workflows/wheels-dependencies.sh
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										196
									
								
								.github/workflows/wheels-dependencies.sh
									
									
									
									
										vendored
									
									
								
							| 
						 | 
					@ -1,11 +1,33 @@
 | 
				
			||||||
#!/bin/bash
 | 
					#!/bin/bash
 | 
				
			||||||
# Define custom utilities
 | 
					
 | 
				
			||||||
# Test for macOS with [ -n "$IS_MACOS" ]
 | 
					# Setup that needs to be done before multibuild utils are invoked
 | 
				
			||||||
if [ -z "$IS_MACOS" ]; then
 | 
					PROJECTDIR=$(pwd)
 | 
				
			||||||
    export MB_ML_LIBC=${AUDITWHEEL_POLICY::9}
 | 
					if [[ "$(uname -s)" == "Darwin" ]]; then
 | 
				
			||||||
    export MB_ML_VER=${AUDITWHEEL_POLICY:9}
 | 
					    # Safety check - macOS builds require that CIBW_ARCHS is set, and that it
 | 
				
			||||||
 | 
					    # only contains a single value (even though cibuildwheel allows multiple
 | 
				
			||||||
 | 
					    # values in CIBW_ARCHS).
 | 
				
			||||||
 | 
					    if [[ -z "$CIBW_ARCHS" ]]; then
 | 
				
			||||||
 | 
					        echo "ERROR: Pillow macOS builds require CIBW_ARCHS be defined."
 | 
				
			||||||
 | 
					        exit 1
 | 
				
			||||||
    fi
 | 
					    fi
 | 
				
			||||||
export PLAT=$CIBW_ARCHS
 | 
					    if [[ "$CIBW_ARCHS" == *" "* ]]; then
 | 
				
			||||||
 | 
					        echo "ERROR: Pillow macOS builds only support a single architecture in CIBW_ARCHS."
 | 
				
			||||||
 | 
					        exit 1
 | 
				
			||||||
 | 
					    fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Build macOS dependencies in `build/darwin`
 | 
				
			||||||
 | 
					    # Install them into `build/deps/darwin`
 | 
				
			||||||
 | 
					    WORKDIR=$(pwd)/build/darwin
 | 
				
			||||||
 | 
					    BUILD_PREFIX=$(pwd)/build/deps/darwin
 | 
				
			||||||
 | 
					else
 | 
				
			||||||
 | 
					    # Build prefix will default to /usr/local
 | 
				
			||||||
 | 
					    WORKDIR=$(pwd)/build
 | 
				
			||||||
 | 
					    MB_ML_LIBC=${AUDITWHEEL_POLICY::9}
 | 
				
			||||||
 | 
					    MB_ML_VER=${AUDITWHEEL_POLICY:9}
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
 | 
					PLAT=$CIBW_ARCHS
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Define custom utilities
 | 
				
			||||||
source wheels/multibuild/common_utils.sh
 | 
					source wheels/multibuild/common_utils.sh
 | 
				
			||||||
source wheels/multibuild/library_builders.sh
 | 
					source wheels/multibuild/library_builders.sh
 | 
				
			||||||
if [ -z "$IS_MACOS" ]; then
 | 
					if [ -z "$IS_MACOS" ]; then
 | 
				
			||||||
| 
						 | 
					@ -15,90 +37,115 @@ fi
 | 
				
			||||||
ARCHIVE_SDIR=pillow-depends-main
 | 
					ARCHIVE_SDIR=pillow-depends-main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Package versions for fresh source builds
 | 
					# Package versions for fresh source builds
 | 
				
			||||||
FREETYPE_VERSION=2.13.2
 | 
					FREETYPE_VERSION=2.13.3
 | 
				
			||||||
HARFBUZZ_VERSION=10.0.1
 | 
					HARFBUZZ_VERSION=11.0.0
 | 
				
			||||||
LIBPNG_VERSION=1.6.44
 | 
					LIBPNG_VERSION=1.6.47
 | 
				
			||||||
JPEGTURBO_VERSION=3.0.4
 | 
					JPEGTURBO_VERSION=3.1.0
 | 
				
			||||||
OPENJPEG_VERSION=2.5.2
 | 
					OPENJPEG_VERSION=2.5.3
 | 
				
			||||||
XZ_VERSION=5.6.3
 | 
					if [[ $MB_ML_VER == 2014 ]]; then
 | 
				
			||||||
TIFF_VERSION=4.6.0
 | 
					  XZ_VERSION=5.6.4
 | 
				
			||||||
LCMS2_VERSION=2.16
 | 
					 | 
				
			||||||
if [[ -n "$IS_MACOS" ]]; then
 | 
					 | 
				
			||||||
    GIFLIB_VERSION=5.2.2
 | 
					 | 
				
			||||||
else
 | 
					else
 | 
				
			||||||
    GIFLIB_VERSION=5.2.1
 | 
					  XZ_VERSION=5.8.0
 | 
				
			||||||
fi
 | 
					fi
 | 
				
			||||||
if [[ -n "$IS_MACOS" ]] || [[ "$MB_ML_VER" != 2014 ]]; then
 | 
					TIFF_VERSION=4.7.0
 | 
				
			||||||
    ZLIB_VERSION=1.3.1
 | 
					LCMS2_VERSION=2.17
 | 
				
			||||||
else
 | 
					ZLIB_NG_VERSION=2.2.4
 | 
				
			||||||
    ZLIB_VERSION=1.2.8
 | 
					LIBWEBP_VERSION=1.5.0
 | 
				
			||||||
fi
 | 
					 | 
				
			||||||
LIBWEBP_VERSION=1.4.0
 | 
					 | 
				
			||||||
BZIP2_VERSION=1.0.8
 | 
					BZIP2_VERSION=1.0.8
 | 
				
			||||||
LIBXCB_VERSION=1.17.0
 | 
					LIBXCB_VERSION=1.17.0
 | 
				
			||||||
BROTLI_VERSION=1.1.0
 | 
					BROTLI_VERSION=1.1.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function build_pkg_config {
 | 
				
			||||||
 | 
					    if [ -e pkg-config-stamp ]; then return; fi
 | 
				
			||||||
 | 
					    # This essentially duplicates the Homebrew recipe
 | 
				
			||||||
 | 
					    CFLAGS="$CFLAGS -Wno-int-conversion" build_simple pkg-config 0.29.2 https://pkg-config.freedesktop.org/releases tar.gz \
 | 
				
			||||||
 | 
					        --disable-debug --disable-host-tool --with-internal-glib \
 | 
				
			||||||
 | 
					        --with-pc-path=$BUILD_PREFIX/share/pkgconfig:$BUILD_PREFIX/lib/pkgconfig \
 | 
				
			||||||
 | 
					        --with-system-include-path=$(xcrun --show-sdk-path --sdk macosx)/usr/include
 | 
				
			||||||
 | 
					    export PKG_CONFIG=$BUILD_PREFIX/bin/pkg-config
 | 
				
			||||||
 | 
					    touch pkg-config-stamp
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function build_zlib_ng {
 | 
				
			||||||
 | 
					    if [ -e zlib-stamp ]; then return; fi
 | 
				
			||||||
 | 
					    fetch_unpack https://github.com/zlib-ng/zlib-ng/archive/$ZLIB_NG_VERSION.tar.gz zlib-ng-$ZLIB_NG_VERSION.tar.gz
 | 
				
			||||||
 | 
					    (cd zlib-ng-$ZLIB_NG_VERSION \
 | 
				
			||||||
 | 
					        && ./configure --prefix=$BUILD_PREFIX --zlib-compat \
 | 
				
			||||||
 | 
					        && make -j4 \
 | 
				
			||||||
 | 
					        && make install)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if [ -n "$IS_MACOS" ]; then
 | 
				
			||||||
 | 
					        # Ensure that on macOS, the library name is an absolute path, not an
 | 
				
			||||||
 | 
					        # @rpath, so that delocate picks up the right library (and doesn't need
 | 
				
			||||||
 | 
					        # DYLD_LIBRARY_PATH to be set). The default Makefile doesn't have an
 | 
				
			||||||
 | 
					        # option to control the install_name.
 | 
				
			||||||
 | 
					        install_name_tool -id $BUILD_PREFIX/lib/libz.1.dylib $BUILD_PREFIX/lib/libz.1.dylib
 | 
				
			||||||
 | 
					    fi
 | 
				
			||||||
 | 
					    touch zlib-stamp
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function build_brotli {
 | 
					function build_brotli {
 | 
				
			||||||
    local cmake=$(get_modern_cmake)
 | 
					    if [ -e brotli-stamp ]; then return; fi
 | 
				
			||||||
    local out_dir=$(fetch_unpack https://github.com/google/brotli/archive/v$BROTLI_VERSION.tar.gz brotli-$BROTLI_VERSION.tar.gz)
 | 
					    local out_dir=$(fetch_unpack https://github.com/google/brotli/archive/v$BROTLI_VERSION.tar.gz brotli-$BROTLI_VERSION.tar.gz)
 | 
				
			||||||
    (cd $out_dir \
 | 
					    (cd $out_dir \
 | 
				
			||||||
        && $cmake -DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX -DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib . \
 | 
					        && cmake -DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX -DCMAKE_INSTALL_LIBDIR=$BUILD_PREFIX/lib -DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib . \
 | 
				
			||||||
        && make install)
 | 
					        && make install)
 | 
				
			||||||
    if [[ "$MB_ML_LIBC" == "manylinux" ]]; then
 | 
					    touch brotli-stamp
 | 
				
			||||||
        cp /usr/local/lib64/libbrotli* /usr/local/lib
 | 
					 | 
				
			||||||
        cp /usr/local/lib64/pkgconfig/libbrotli* /usr/local/lib/pkgconfig
 | 
					 | 
				
			||||||
    fi
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function build_harfbuzz {
 | 
					function build_harfbuzz {
 | 
				
			||||||
 | 
					    if [ -e harfbuzz-stamp ]; then return; fi
 | 
				
			||||||
    python3 -m pip install meson ninja
 | 
					    python3 -m pip install meson ninja
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    local out_dir=$(fetch_unpack https://github.com/harfbuzz/harfbuzz/releases/download/$HARFBUZZ_VERSION/$HARFBUZZ_VERSION.tar.xz harfbuzz-$HARFBUZZ_VERSION.tar.xz)
 | 
					    local out_dir=$(fetch_unpack https://github.com/harfbuzz/harfbuzz/releases/download/$HARFBUZZ_VERSION/harfbuzz-$HARFBUZZ_VERSION.tar.xz harfbuzz-$HARFBUZZ_VERSION.tar.xz)
 | 
				
			||||||
    (cd $out_dir \
 | 
					    (cd $out_dir \
 | 
				
			||||||
        && meson setup build --buildtype=release -Dfreetype=enabled -Dglib=disabled)
 | 
					        && meson setup build --prefix=$BUILD_PREFIX --libdir=$BUILD_PREFIX/lib --buildtype=release -Dfreetype=enabled -Dglib=disabled)
 | 
				
			||||||
    (cd $out_dir/build \
 | 
					    (cd $out_dir/build \
 | 
				
			||||||
        && meson install)
 | 
					        && meson install)
 | 
				
			||||||
    if [[ "$MB_ML_LIBC" == "manylinux" ]]; then
 | 
					    touch harfbuzz-stamp
 | 
				
			||||||
        cp /usr/local/lib64/libharfbuzz* /usr/local/lib
 | 
					 | 
				
			||||||
    fi
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function build {
 | 
					function build {
 | 
				
			||||||
    if [[ -n "$IS_MACOS" ]] && [[ "$CIBW_ARCHS" == "arm64" ]]; then
 | 
					 | 
				
			||||||
        sudo chown -R runner /usr/local
 | 
					 | 
				
			||||||
    fi
 | 
					 | 
				
			||||||
    build_xz
 | 
					    build_xz
 | 
				
			||||||
    if [ -z "$IS_ALPINE" ] && [ -z "$IS_MACOS" ]; then
 | 
					    if [ -z "$IS_ALPINE" ] && [ -z "$SANITIZER" ] && [ -z "$IS_MACOS" ]; then
 | 
				
			||||||
        yum remove -y zlib-devel
 | 
					        yum remove -y zlib-devel
 | 
				
			||||||
    fi
 | 
					    fi
 | 
				
			||||||
    build_new_zlib
 | 
					    build_zlib_ng
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    build_simple xcb-proto 1.17.0 https://xorg.freedesktop.org/archive/individual/proto
 | 
					    build_simple xcb-proto 1.17.0 https://xorg.freedesktop.org/archive/individual/proto
 | 
				
			||||||
    if [ -n "$IS_MACOS" ]; then
 | 
					    if [ -n "$IS_MACOS" ]; then
 | 
				
			||||||
        build_simple xorgproto 2024.1 https://www.x.org/pub/individual/proto
 | 
					        build_simple xorgproto 2024.1 https://www.x.org/pub/individual/proto
 | 
				
			||||||
        build_simple libXau 1.0.11 https://www.x.org/pub/individual/lib
 | 
					        build_simple libXau 1.0.12 https://www.x.org/pub/individual/lib
 | 
				
			||||||
        build_simple libpthread-stubs 0.5 https://xcb.freedesktop.org/dist
 | 
					        build_simple libpthread-stubs 0.5 https://xcb.freedesktop.org/dist
 | 
				
			||||||
        if [[ "$CIBW_ARCHS" == "arm64" ]]; then
 | 
					 | 
				
			||||||
            cp /usr/local/share/pkgconfig/xcb-proto.pc /usr/local/lib/pkgconfig
 | 
					 | 
				
			||||||
        fi
 | 
					 | 
				
			||||||
    else
 | 
					    else
 | 
				
			||||||
        sed s/\${pc_sysrootdir\}// /usr/local/share/pkgconfig/xcb-proto.pc > /usr/local/lib/pkgconfig/xcb-proto.pc
 | 
					        sed s/\${pc_sysrootdir\}// $BUILD_PREFIX/share/pkgconfig/xcb-proto.pc > $BUILD_PREFIX/lib/pkgconfig/xcb-proto.pc
 | 
				
			||||||
    fi
 | 
					    fi
 | 
				
			||||||
    build_simple libxcb $LIBXCB_VERSION https://www.x.org/releases/individual/lib
 | 
					    build_simple libxcb $LIBXCB_VERSION https://www.x.org/releases/individual/lib
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    build_libjpeg_turbo
 | 
					    build_libjpeg_turbo
 | 
				
			||||||
 | 
					    if [ -n "$IS_MACOS" ]; then
 | 
				
			||||||
 | 
					        # Custom tiff build to include jpeg; by default, configure won't include
 | 
				
			||||||
 | 
					        # headers/libs in the custom macOS prefix. Explicitly disable webp,
 | 
				
			||||||
 | 
					        # libdeflate and zstd, because on x86_64 macs, it will pick up the
 | 
				
			||||||
 | 
					        # Homebrew versions of those libraries from /usr/local.
 | 
				
			||||||
 | 
					        build_simple tiff $TIFF_VERSION https://download.osgeo.org/libtiff tar.gz \
 | 
				
			||||||
 | 
					            --with-jpeg-include-dir=$BUILD_PREFIX/include --with-jpeg-lib-dir=$BUILD_PREFIX/lib \
 | 
				
			||||||
 | 
					            --disable-webp --disable-libdeflate --disable-zstd
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
        build_tiff
 | 
					        build_tiff
 | 
				
			||||||
 | 
					    fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    build_libpng
 | 
					    build_libpng
 | 
				
			||||||
    build_lcms2
 | 
					    build_lcms2
 | 
				
			||||||
    build_openjpeg
 | 
					    build_openjpeg
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ORIGINAL_CFLAGS=$CFLAGS
 | 
					    webp_cflags="-O3 -DNDEBUG"
 | 
				
			||||||
    CFLAGS="$CFLAGS -O3 -DNDEBUG"
 | 
					 | 
				
			||||||
    if [[ -n "$IS_MACOS" ]]; then
 | 
					    if [[ -n "$IS_MACOS" ]]; then
 | 
				
			||||||
        CFLAGS="$CFLAGS -Wl,-headerpad_max_install_names"
 | 
					        webp_cflags="$webp_cflags -Wl,-headerpad_max_install_names"
 | 
				
			||||||
    fi
 | 
					    fi
 | 
				
			||||||
    build_libwebp
 | 
					    CFLAGS="$CFLAGS $webp_cflags" build_simple libwebp $LIBWEBP_VERSION \
 | 
				
			||||||
    CFLAGS=$ORIGINAL_CFLAGS
 | 
					        https://storage.googleapis.com/downloads.webmproject.org/releases/webp tar.gz \
 | 
				
			||||||
 | 
					        --enable-libwebpmux --enable-libwebpdemux
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    build_brotli
 | 
					    build_brotli
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -112,32 +159,47 @@ function build {
 | 
				
			||||||
    build_harfbuzz
 | 
					    build_harfbuzz
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Perform all dependency builds in the build subfolder.
 | 
				
			||||||
 | 
					mkdir -p $WORKDIR
 | 
				
			||||||
 | 
					pushd $WORKDIR > /dev/null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Any stuff that you need to do before you start building the wheels
 | 
					# Any stuff that you need to do before you start building the wheels
 | 
				
			||||||
# Runs in the root directory of this repository.
 | 
					# Runs in the root directory of this repository.
 | 
				
			||||||
curl -fsSL -o pillow-depends-main.zip https://github.com/python-pillow/pillow-depends/archive/main.zip
 | 
					if [[ ! -d $WORKDIR/pillow-depends-main ]]; then
 | 
				
			||||||
untar pillow-depends-main.zip
 | 
					  if [[ ! -f $PROJECTDIR/pillow-depends-main.zip ]]; then
 | 
				
			||||||
 | 
					    echo "Download pillow dependency sources..."
 | 
				
			||||||
if [[ -n "$IS_MACOS" ]]; then
 | 
					    curl -fSL -o $PROJECTDIR/pillow-depends-main.zip https://github.com/python-pillow/pillow-depends/archive/main.zip
 | 
				
			||||||
  # libdeflate may cause a minimum target error when repairing the wheel
 | 
					  fi
 | 
				
			||||||
  # libtiff and libxcb cause a conflict with building libtiff and libxcb
 | 
					  echo "Unpacking pillow dependency sources..."
 | 
				
			||||||
  # libxau and libxdmcp cause an issue on macOS < 11
 | 
					  untar $PROJECTDIR/pillow-depends-main.zip
 | 
				
			||||||
  # remove cairo to fix building harfbuzz on arm64
 | 
					 | 
				
			||||||
  # remove lcms2 and libpng to fix building openjpeg on arm64
 | 
					 | 
				
			||||||
  # remove jpeg-turbo to avoid inclusion on arm64
 | 
					 | 
				
			||||||
  # remove webp and zstd to avoid inclusion on x86_64
 | 
					 | 
				
			||||||
  # curl from brew requires zstd, use system curl
 | 
					 | 
				
			||||||
  brew remove --ignore-dependencies libpng libtiff libxcb libxau libxdmcp curl cairo lcms2 zstd
 | 
					 | 
				
			||||||
  if [[ "$CIBW_ARCHS" == "arm64" ]]; then
 | 
					 | 
				
			||||||
    brew remove --ignore-dependencies jpeg-turbo
 | 
					 | 
				
			||||||
  else
 | 
					 | 
				
			||||||
    brew remove --ignore-dependencies libdeflate webp
 | 
					 | 
				
			||||||
fi
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  brew install pkg-config
 | 
					if [[ -n "$IS_MACOS" ]]; then
 | 
				
			||||||
 | 
					    # Homebrew (or similar packaging environments) install can contain some of
 | 
				
			||||||
 | 
					    # the libraries that we're going to build. However, they may be compiled
 | 
				
			||||||
 | 
					    # with a MACOSX_DEPLOYMENT_TARGET that doesn't match what we want to use,
 | 
				
			||||||
 | 
					    # and they may bring in other dependencies that we don't want. The same will
 | 
				
			||||||
 | 
					    # be true of any other locations on the path. To avoid conflicts, strip the
 | 
				
			||||||
 | 
					    # path down to the bare minimum (which, on macOS, won't include any
 | 
				
			||||||
 | 
					    # development dependencies).
 | 
				
			||||||
 | 
					    export PATH="$BUILD_PREFIX/bin:$(dirname $(which python3)):/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin"
 | 
				
			||||||
 | 
					    export CMAKE_PREFIX_PATH=$BUILD_PREFIX
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Ensure the basic structure of the build prefix directory exists.
 | 
				
			||||||
 | 
					    mkdir -p "$BUILD_PREFIX/bin"
 | 
				
			||||||
 | 
					    mkdir -p "$BUILD_PREFIX/lib"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Ensure pkg-config is available
 | 
				
			||||||
 | 
					    build_pkg_config
 | 
				
			||||||
 | 
					    # Ensure cmake is available
 | 
				
			||||||
 | 
					    python3 -m pip install cmake
 | 
				
			||||||
fi
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
wrap_wheel_builder build
 | 
					wrap_wheel_builder build
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Return to the project root to finish the build
 | 
				
			||||||
 | 
					popd > /dev/null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Append licenses
 | 
					# Append licenses
 | 
				
			||||||
for filename in wheels/dependency_licenses/*; do
 | 
					for filename in wheels/dependency_licenses/*; do
 | 
				
			||||||
  echo -e "\n\n----\n\n$(basename $filename | cut -f 1 -d '.')\n" | cat >> LICENSE
 | 
					  echo -e "\n\n----\n\n$(basename $filename | cut -f 1 -d '.')\n" | cat >> LICENSE
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										3
									
								
								.github/workflows/wheels-test.ps1
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.github/workflows/wheels-test.ps1
									
									
									
									
										vendored
									
									
								
							| 
						 | 
					@ -11,6 +11,9 @@ if ("$venv" -like "*\cibw-run-*\pp*-win_amd64\*") {
 | 
				
			||||||
$env:path += ";$pillow\winbuild\build\bin\"
 | 
					$env:path += ";$pillow\winbuild\build\bin\"
 | 
				
			||||||
& "$venv\Scripts\activate.ps1"
 | 
					& "$venv\Scripts\activate.ps1"
 | 
				
			||||||
& reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\python.exe" /v "GlobalFlag" /t REG_SZ /d "0x02000000" /f
 | 
					& reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\python.exe" /v "GlobalFlag" /t REG_SZ /d "0x02000000" /f
 | 
				
			||||||
 | 
					if ("$venv" -like "*\cibw-run-*-win_amd64\*") {
 | 
				
			||||||
 | 
					  & python -m pip install numpy
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
cd $pillow
 | 
					cd $pillow
 | 
				
			||||||
& python -VV
 | 
					& python -VV
 | 
				
			||||||
if (!$?) { exit $LASTEXITCODE }
 | 
					if (!$?) { exit $LASTEXITCODE }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										20
									
								
								.github/workflows/wheels-test.sh
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										20
									
								
								.github/workflows/wheels-test.sh
									
									
									
									
										vendored
									
									
								
							| 
						 | 
					@ -1,12 +1,24 @@
 | 
				
			||||||
#!/bin/bash
 | 
					#!/bin/bash
 | 
				
			||||||
set -e
 | 
					set -e
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Ensure fribidi is installed by the system.
 | 
				
			||||||
if [[ "$OSTYPE" == "darwin"* ]]; then
 | 
					if [[ "$OSTYPE" == "darwin"* ]]; then
 | 
				
			||||||
    brew install fribidi
 | 
					    # If Homebrew is on the path during the build, it may leak into the wheels.
 | 
				
			||||||
    export PKG_CONFIG_PATH="/usr/local/opt/openblas/lib/pkgconfig"
 | 
					    # However, we *do* need Homebrew to provide a copy of fribidi for
 | 
				
			||||||
    if [ -f /opt/homebrew/lib/libfribidi.dylib ]; then
 | 
					    # testing purposes so that we can verify the fribidi shim works as expected.
 | 
				
			||||||
        sudo cp /opt/homebrew/lib/libfribidi.dylib /usr/local/lib
 | 
					    if [[ "$(uname -m)" == "x86_64" ]]; then
 | 
				
			||||||
 | 
					        HOMEBREW_PREFIX=/usr/local
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					        HOMEBREW_PREFIX=/opt/homebrew
 | 
				
			||||||
    fi
 | 
					    fi
 | 
				
			||||||
 | 
					    $HOMEBREW_PREFIX/bin/brew install fribidi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Add the lib folder for fribidi so that the vendored library can be found.
 | 
				
			||||||
 | 
					    # Don't use $HOMEWBREW_PREFIX/lib directly - use the lib folder where the
 | 
				
			||||||
 | 
					    # installed copy of fribidi is cellared. This ensures we don't pick up the
 | 
				
			||||||
 | 
					    # Homebrew version of any other library that we're dependent on (most notably,
 | 
				
			||||||
 | 
					    # freetype).
 | 
				
			||||||
 | 
					    export DYLD_LIBRARY_PATH=$(dirname $(realpath $HOMEBREW_PREFIX/lib/libfribidi.dylib))
 | 
				
			||||||
elif [ "${AUDITWHEEL_POLICY::9}" == "musllinux" ]; then
 | 
					elif [ "${AUDITWHEEL_POLICY::9}" == "musllinux" ]; then
 | 
				
			||||||
    apk add curl fribidi
 | 
					    apk add curl fribidi
 | 
				
			||||||
else
 | 
					else
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										83
									
								
								.github/workflows/wheels.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										83
									
								
								.github/workflows/wheels.yml
									
									
									
									
										vendored
									
									
								
							| 
						 | 
					@ -13,6 +13,7 @@ on:
 | 
				
			||||||
    paths:
 | 
					    paths:
 | 
				
			||||||
      - ".ci/requirements-cibw.txt"
 | 
					      - ".ci/requirements-cibw.txt"
 | 
				
			||||||
      - ".github/workflows/wheel*"
 | 
					      - ".github/workflows/wheel*"
 | 
				
			||||||
 | 
					      - "pyproject.toml"
 | 
				
			||||||
      - "setup.py"
 | 
					      - "setup.py"
 | 
				
			||||||
      - "wheels/*"
 | 
					      - "wheels/*"
 | 
				
			||||||
      - "winbuild/build_prepare.py"
 | 
					      - "winbuild/build_prepare.py"
 | 
				
			||||||
| 
						 | 
					@ -23,6 +24,7 @@ on:
 | 
				
			||||||
    paths:
 | 
					    paths:
 | 
				
			||||||
      - ".ci/requirements-cibw.txt"
 | 
					      - ".ci/requirements-cibw.txt"
 | 
				
			||||||
      - ".github/workflows/wheel*"
 | 
					      - ".github/workflows/wheel*"
 | 
				
			||||||
 | 
					      - "pyproject.toml"
 | 
				
			||||||
      - "setup.py"
 | 
					      - "setup.py"
 | 
				
			||||||
      - "wheels/*"
 | 
					      - "wheels/*"
 | 
				
			||||||
      - "winbuild/build_prepare.py"
 | 
					      - "winbuild/build_prepare.py"
 | 
				
			||||||
| 
						 | 
					@ -40,62 +42,7 @@ env:
 | 
				
			||||||
  FORCE_COLOR: 1
 | 
					  FORCE_COLOR: 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
jobs:
 | 
					jobs:
 | 
				
			||||||
  build-1-QEMU-emulated-wheels:
 | 
					  build-native-wheels:
 | 
				
			||||||
    if: github.event_name != 'schedule'
 | 
					 | 
				
			||||||
    name: aarch64 ${{ matrix.python-version }} ${{ matrix.spec }}
 | 
					 | 
				
			||||||
    runs-on: ubuntu-latest
 | 
					 | 
				
			||||||
    strategy:
 | 
					 | 
				
			||||||
      fail-fast: false
 | 
					 | 
				
			||||||
      matrix:
 | 
					 | 
				
			||||||
        python-version:
 | 
					 | 
				
			||||||
          - pp310
 | 
					 | 
				
			||||||
          - cp3{9,10,11}
 | 
					 | 
				
			||||||
          - cp3{12,13}
 | 
					 | 
				
			||||||
        spec:
 | 
					 | 
				
			||||||
          - manylinux2014
 | 
					 | 
				
			||||||
          - manylinux_2_28
 | 
					 | 
				
			||||||
          - musllinux
 | 
					 | 
				
			||||||
        exclude:
 | 
					 | 
				
			||||||
          - { python-version: pp310, spec: musllinux }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    steps:
 | 
					 | 
				
			||||||
      - uses: actions/checkout@v4
 | 
					 | 
				
			||||||
        with:
 | 
					 | 
				
			||||||
          persist-credentials: false
 | 
					 | 
				
			||||||
          submodules: true
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - uses: actions/setup-python@v5
 | 
					 | 
				
			||||||
        with:
 | 
					 | 
				
			||||||
          python-version: "3.x"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      # https://github.com/docker/setup-qemu-action
 | 
					 | 
				
			||||||
      - name: Set up QEMU
 | 
					 | 
				
			||||||
        uses: docker/setup-qemu-action@v3
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Install cibuildwheel
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          python3 -m pip install -r .ci/requirements-cibw.txt
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Build wheels
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          python3 -m cibuildwheel --output-dir wheelhouse
 | 
					 | 
				
			||||||
        env:
 | 
					 | 
				
			||||||
          # Build only the currently selected Linux architecture (so we can
 | 
					 | 
				
			||||||
          # parallelise for speed).
 | 
					 | 
				
			||||||
          CIBW_ARCHS: "aarch64"
 | 
					 | 
				
			||||||
          # Likewise, select only one Python version per job to speed this up.
 | 
					 | 
				
			||||||
          CIBW_BUILD: "${{ matrix.python-version }}-${{ matrix.spec == 'musllinux' && 'musllinux' || 'manylinux' }}*"
 | 
					 | 
				
			||||||
          CIBW_PRERELEASE_PYTHONS: True
 | 
					 | 
				
			||||||
          # Extra options for manylinux.
 | 
					 | 
				
			||||||
          CIBW_MANYLINUX_AARCH64_IMAGE: ${{ matrix.spec }}
 | 
					 | 
				
			||||||
          CIBW_MANYLINUX_PYPY_AARCH64_IMAGE: ${{ matrix.spec }}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - uses: actions/upload-artifact@v4
 | 
					 | 
				
			||||||
        with:
 | 
					 | 
				
			||||||
          name: dist-qemu-${{ matrix.python-version }}-${{ matrix.spec }}
 | 
					 | 
				
			||||||
          path: ./wheelhouse/*.whl
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  build-2-native-wheels:
 | 
					 | 
				
			||||||
    if: github.event_name != 'schedule' || github.repository_owner == 'python-pillow'
 | 
					    if: github.event_name != 'schedule' || github.repository_owner == 'python-pillow'
 | 
				
			||||||
    name: ${{ matrix.name }}
 | 
					    name: ${{ matrix.name }}
 | 
				
			||||||
    runs-on: ${{ matrix.os }}
 | 
					    runs-on: ${{ matrix.os }}
 | 
				
			||||||
| 
						 | 
					@ -116,7 +63,7 @@ jobs:
 | 
				
			||||||
          - name: "macOS 10.15 x86_64"
 | 
					          - name: "macOS 10.15 x86_64"
 | 
				
			||||||
            os: macos-13
 | 
					            os: macos-13
 | 
				
			||||||
            cibw_arch: x86_64
 | 
					            cibw_arch: x86_64
 | 
				
			||||||
            build: "pp310*"
 | 
					            build: "pp3*"
 | 
				
			||||||
            macosx_deployment_target: "10.15"
 | 
					            macosx_deployment_target: "10.15"
 | 
				
			||||||
          - name: "macOS arm64"
 | 
					          - name: "macOS arm64"
 | 
				
			||||||
            os: macos-latest
 | 
					            os: macos-latest
 | 
				
			||||||
| 
						 | 
					@ -130,6 +77,14 @@ jobs:
 | 
				
			||||||
            cibw_arch: x86_64
 | 
					            cibw_arch: x86_64
 | 
				
			||||||
            build: "*manylinux*"
 | 
					            build: "*manylinux*"
 | 
				
			||||||
            manylinux: "manylinux_2_28"
 | 
					            manylinux: "manylinux_2_28"
 | 
				
			||||||
 | 
					          - name: "manylinux2014 and musllinux aarch64"
 | 
				
			||||||
 | 
					            os: ubuntu-24.04-arm
 | 
				
			||||||
 | 
					            cibw_arch: aarch64
 | 
				
			||||||
 | 
					          - name: "manylinux_2_28 aarch64"
 | 
				
			||||||
 | 
					            os: ubuntu-24.04-arm
 | 
				
			||||||
 | 
					            cibw_arch: aarch64
 | 
				
			||||||
 | 
					            build: "*manylinux*"
 | 
				
			||||||
 | 
					            manylinux: "manylinux_2_28"
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - uses: actions/checkout@v4
 | 
					      - uses: actions/checkout@v4
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
| 
						 | 
					@ -150,10 +105,11 @@ jobs:
 | 
				
			||||||
        env:
 | 
					        env:
 | 
				
			||||||
          CIBW_ARCHS: ${{ matrix.cibw_arch }}
 | 
					          CIBW_ARCHS: ${{ matrix.cibw_arch }}
 | 
				
			||||||
          CIBW_BUILD: ${{ matrix.build }}
 | 
					          CIBW_BUILD: ${{ matrix.build }}
 | 
				
			||||||
          CIBW_FREE_THREADED_SUPPORT: True
 | 
					          CIBW_ENABLE: cpython-prerelease cpython-freethreading pypy
 | 
				
			||||||
 | 
					          CIBW_MANYLINUX_AARCH64_IMAGE: ${{ matrix.manylinux }}
 | 
				
			||||||
 | 
					          CIBW_MANYLINUX_PYPY_AARCH64_IMAGE: ${{ matrix.manylinux }}
 | 
				
			||||||
          CIBW_MANYLINUX_PYPY_X86_64_IMAGE: ${{ matrix.manylinux }}
 | 
					          CIBW_MANYLINUX_PYPY_X86_64_IMAGE: ${{ matrix.manylinux }}
 | 
				
			||||||
          CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.manylinux }}
 | 
					          CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.manylinux }}
 | 
				
			||||||
          CIBW_PRERELEASE_PYTHONS: True
 | 
					 | 
				
			||||||
          CIBW_SKIP: pp39-*
 | 
					          CIBW_SKIP: pp39-*
 | 
				
			||||||
          MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macosx_deployment_target }}
 | 
					          MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macosx_deployment_target }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -228,8 +184,7 @@ jobs:
 | 
				
			||||||
          CIBW_ARCHS: ${{ matrix.cibw_arch }}
 | 
					          CIBW_ARCHS: ${{ matrix.cibw_arch }}
 | 
				
			||||||
          CIBW_BEFORE_ALL: "{package}\\winbuild\\build\\build_dep_all.cmd"
 | 
					          CIBW_BEFORE_ALL: "{package}\\winbuild\\build\\build_dep_all.cmd"
 | 
				
			||||||
          CIBW_CACHE_PATH: "C:\\cibw"
 | 
					          CIBW_CACHE_PATH: "C:\\cibw"
 | 
				
			||||||
          CIBW_FREE_THREADED_SUPPORT: True
 | 
					          CIBW_ENABLE: cpython-prerelease cpython-freethreading pypy
 | 
				
			||||||
          CIBW_PRERELEASE_PYTHONS: True
 | 
					 | 
				
			||||||
          CIBW_SKIP: pp39-*
 | 
					          CIBW_SKIP: pp39-*
 | 
				
			||||||
          CIBW_TEST_SKIP: "*-win_arm64"
 | 
					          CIBW_TEST_SKIP: "*-win_arm64"
 | 
				
			||||||
          CIBW_TEST_COMMAND: 'docker run --rm
 | 
					          CIBW_TEST_COMMAND: 'docker run --rm
 | 
				
			||||||
| 
						 | 
					@ -265,8 +220,6 @@ jobs:
 | 
				
			||||||
      uses: actions/setup-python@v5
 | 
					      uses: actions/setup-python@v5
 | 
				
			||||||
      with:
 | 
					      with:
 | 
				
			||||||
        python-version: "3.x"
 | 
					        python-version: "3.x"
 | 
				
			||||||
        cache: pip
 | 
					 | 
				
			||||||
        cache-dependency-path: "Makefile"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    - run: make sdist
 | 
					    - run: make sdist
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -277,7 +230,7 @@ jobs:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  scientific-python-nightly-wheels-publish:
 | 
					  scientific-python-nightly-wheels-publish:
 | 
				
			||||||
    if: github.repository_owner == 'python-pillow' && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')
 | 
					    if: github.repository_owner == 'python-pillow' && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')
 | 
				
			||||||
    needs: [build-2-native-wheels, windows]
 | 
					    needs: [build-native-wheels, windows]
 | 
				
			||||||
    runs-on: ubuntu-latest
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
    name: Upload wheels to scientific-python-nightly-wheels
 | 
					    name: Upload wheels to scientific-python-nightly-wheels
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
| 
						 | 
					@ -294,7 +247,7 @@ jobs:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  pypi-publish:
 | 
					  pypi-publish:
 | 
				
			||||||
    if: github.repository_owner == 'python-pillow' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
 | 
					    if: github.repository_owner == 'python-pillow' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
 | 
				
			||||||
    needs: [build-1-QEMU-emulated-wheels, build-2-native-wheels, windows, sdist]
 | 
					    needs: [build-native-wheels, windows, sdist]
 | 
				
			||||||
    runs-on: ubuntu-latest
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
    name: Upload release to PyPI
 | 
					    name: Upload release to PyPI
 | 
				
			||||||
    environment:
 | 
					    environment:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										5
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| 
						 | 
					@ -19,6 +19,7 @@ lib64/
 | 
				
			||||||
parts/
 | 
					parts/
 | 
				
			||||||
sdist/
 | 
					sdist/
 | 
				
			||||||
var/
 | 
					var/
 | 
				
			||||||
 | 
					wheelhouse/
 | 
				
			||||||
*.egg-info/
 | 
					*.egg-info/
 | 
				
			||||||
.installed.cfg
 | 
					.installed.cfg
 | 
				
			||||||
*.egg
 | 
					*.egg
 | 
				
			||||||
| 
						 | 
					@ -90,5 +91,9 @@ Tests/images/msp
 | 
				
			||||||
Tests/images/picins
 | 
					Tests/images/picins
 | 
				
			||||||
Tests/images/sunraster
 | 
					Tests/images/sunraster
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Test and dependency downloads
 | 
				
			||||||
 | 
					pillow-depends-main.zip
 | 
				
			||||||
 | 
					pillow-test-images.zip
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# pyinstaller
 | 
					# pyinstaller
 | 
				
			||||||
*.spec
 | 
					*.spec
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,17 +1,17 @@
 | 
				
			||||||
repos:
 | 
					repos:
 | 
				
			||||||
  - repo: https://github.com/astral-sh/ruff-pre-commit
 | 
					  - repo: https://github.com/astral-sh/ruff-pre-commit
 | 
				
			||||||
    rev: v0.7.2
 | 
					    rev: v0.9.9
 | 
				
			||||||
    hooks:
 | 
					    hooks:
 | 
				
			||||||
      - id: ruff
 | 
					      - id: ruff
 | 
				
			||||||
        args: [--exit-non-zero-on-fix]
 | 
					        args: [--exit-non-zero-on-fix]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - repo: https://github.com/psf/black-pre-commit-mirror
 | 
					  - repo: https://github.com/psf/black-pre-commit-mirror
 | 
				
			||||||
    rev: 24.10.0
 | 
					    rev: 25.1.0
 | 
				
			||||||
    hooks:
 | 
					    hooks:
 | 
				
			||||||
      - id: black
 | 
					      - id: black
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - repo: https://github.com/PyCQA/bandit
 | 
					  - repo: https://github.com/PyCQA/bandit
 | 
				
			||||||
    rev: 1.7.10
 | 
					    rev: 1.8.3
 | 
				
			||||||
    hooks:
 | 
					    hooks:
 | 
				
			||||||
    - id: bandit
 | 
					    - id: bandit
 | 
				
			||||||
      args: [--severity-level=high]
 | 
					      args: [--severity-level=high]
 | 
				
			||||||
| 
						 | 
					@ -24,7 +24,7 @@ repos:
 | 
				
			||||||
        exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$)
 | 
					        exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - repo: https://github.com/pre-commit/mirrors-clang-format
 | 
					  - repo: https://github.com/pre-commit/mirrors-clang-format
 | 
				
			||||||
    rev: v19.1.3
 | 
					    rev: v19.1.7
 | 
				
			||||||
    hooks:
 | 
					    hooks:
 | 
				
			||||||
      - id: clang-format
 | 
					      - id: clang-format
 | 
				
			||||||
        types: [c]
 | 
					        types: [c]
 | 
				
			||||||
| 
						 | 
					@ -50,30 +50,35 @@ repos:
 | 
				
			||||||
        exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/
 | 
					        exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - repo: https://github.com/python-jsonschema/check-jsonschema
 | 
					  - repo: https://github.com/python-jsonschema/check-jsonschema
 | 
				
			||||||
    rev: 0.29.4
 | 
					    rev: 0.31.2
 | 
				
			||||||
    hooks:
 | 
					    hooks:
 | 
				
			||||||
      - id: check-github-workflows
 | 
					      - id: check-github-workflows
 | 
				
			||||||
      - id: check-readthedocs
 | 
					      - id: check-readthedocs
 | 
				
			||||||
      - id: check-renovate
 | 
					      - id: check-renovate
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - repo: https://github.com/woodruffw/zizmor-pre-commit
 | 
				
			||||||
 | 
					    rev: v1.4.1
 | 
				
			||||||
 | 
					    hooks:
 | 
				
			||||||
 | 
					      - id: zizmor
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - repo: https://github.com/sphinx-contrib/sphinx-lint
 | 
					  - repo: https://github.com/sphinx-contrib/sphinx-lint
 | 
				
			||||||
    rev: v1.0.0
 | 
					    rev: v1.0.0
 | 
				
			||||||
    hooks:
 | 
					    hooks:
 | 
				
			||||||
      - id: sphinx-lint
 | 
					      - id: sphinx-lint
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - repo: https://github.com/tox-dev/pyproject-fmt
 | 
					  - repo: https://github.com/tox-dev/pyproject-fmt
 | 
				
			||||||
    rev: v2.5.0
 | 
					    rev: v2.5.1
 | 
				
			||||||
    hooks:
 | 
					    hooks:
 | 
				
			||||||
      - id: pyproject-fmt
 | 
					      - id: pyproject-fmt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - repo: https://github.com/abravalheri/validate-pyproject
 | 
					  - repo: https://github.com/abravalheri/validate-pyproject
 | 
				
			||||||
    rev: v0.22
 | 
					    rev: v0.23
 | 
				
			||||||
    hooks:
 | 
					    hooks:
 | 
				
			||||||
      - id: validate-pyproject
 | 
					      - id: validate-pyproject
 | 
				
			||||||
        additional_dependencies: [trove-classifiers>=2024.10.12]
 | 
					        additional_dependencies: [trove-classifiers>=2024.10.12]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - repo: https://github.com/tox-dev/tox-ini-fmt
 | 
					  - repo: https://github.com/tox-dev/tox-ini-fmt
 | 
				
			||||||
    rev: 1.4.1
 | 
					    rev: 1.5.0
 | 
				
			||||||
    hooks:
 | 
					    hooks:
 | 
				
			||||||
      - id: tox-ini-fmt
 | 
					      - id: tox-ini-fmt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,8 @@
 | 
				
			||||||
version: 2
 | 
					version: 2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					sphinx:
 | 
				
			||||||
 | 
					  configuration: docs/conf.py
 | 
				
			||||||
 | 
					
 | 
				
			||||||
formats: [pdf]
 | 
					formats: [pdf]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
build:
 | 
					build:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										16
									
								
								CHANGES.rst
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								CHANGES.rst
									
									
									
									
									
								
							| 
						 | 
					@ -2,20 +2,12 @@
 | 
				
			||||||
Changelog (Pillow)
 | 
					Changelog (Pillow)
 | 
				
			||||||
==================
 | 
					==================
 | 
				
			||||||
 | 
					
 | 
				
			||||||
11.1.0 (unreleased)
 | 
					11.1.0 and newer
 | 
				
			||||||
-------------------
 | 
					----------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Detach PyQt6 QPixmap instance before returning #8509
 | 
					See GitHub Releases:
 | 
				
			||||||
  [radarhere]
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Corrected EMF DPI #8485
 | 
					- https://github.com/python-pillow/Pillow/releases
 | 
				
			||||||
  [radarhere]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- Fix IFDRational with a zero denominator #8474
 | 
					 | 
				
			||||||
  [radarhere]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- Fixed disabling a feature during install #8469
 | 
					 | 
				
			||||||
  [radarhere]
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
11.0.0 (2024-10-15)
 | 
					11.0.0 (2024-10-15)
 | 
				
			||||||
-------------------
 | 
					-------------------
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										2
									
								
								LICENSE
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								LICENSE
									
									
									
									
									
								
							| 
						 | 
					@ -5,7 +5,7 @@ The Python Imaging Library (PIL) is
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Pillow is the friendly PIL fork. It is
 | 
					Pillow is the friendly PIL fork. It is
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Copyright © 2010-2024 by Jeffrey A. Clark and contributors
 | 
					    Copyright © 2010 by Jeffrey A. Clark and contributors
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Like PIL, Pillow is licensed under the open source MIT-CMU License:
 | 
					Like PIL, Pillow is licensed under the open source MIT-CMU License:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -20,7 +20,6 @@ graft docs
 | 
				
			||||||
graft _custom_build
 | 
					graft _custom_build
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# build/src control detritus
 | 
					# build/src control detritus
 | 
				
			||||||
exclude .appveyor.yml
 | 
					 | 
				
			||||||
exclude .clang-format
 | 
					exclude .clang-format
 | 
				
			||||||
exclude .coveragerc
 | 
					exclude .coveragerc
 | 
				
			||||||
exclude .editorconfig
 | 
					exclude .editorconfig
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -42,9 +42,6 @@ As of 2019, Pillow development is
 | 
				
			||||||
            <a href="https://github.com/python-pillow/Pillow/actions/workflows/test-docker.yml"><img
 | 
					            <a href="https://github.com/python-pillow/Pillow/actions/workflows/test-docker.yml"><img
 | 
				
			||||||
                alt="GitHub Actions build status (Test Docker)"
 | 
					                alt="GitHub Actions build status (Test Docker)"
 | 
				
			||||||
                src="https://github.com/python-pillow/Pillow/workflows/Test%20Docker/badge.svg"></a>
 | 
					                src="https://github.com/python-pillow/Pillow/workflows/Test%20Docker/badge.svg"></a>
 | 
				
			||||||
            <a href="https://ci.appveyor.com/project/python-pillow/Pillow"><img
 | 
					 | 
				
			||||||
                alt="AppVeyor CI build status (Windows)"
 | 
					 | 
				
			||||||
                src="https://img.shields.io/appveyor/build/python-pillow/Pillow/main.svg?label=Windows%20build"></a>
 | 
					 | 
				
			||||||
            <a href="https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml"><img
 | 
					            <a href="https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml"><img
 | 
				
			||||||
                alt="GitHub Actions build status (Wheels)"
 | 
					                alt="GitHub Actions build status (Wheels)"
 | 
				
			||||||
                src="https://github.com/python-pillow/Pillow/workflows/Wheels/badge.svg"></a>
 | 
					                src="https://github.com/python-pillow/Pillow/workflows/Wheels/badge.svg"></a>
 | 
				
			||||||
| 
						 | 
					@ -107,7 +104,7 @@ The core image library is designed for fast access to data stored in a few basic
 | 
				
			||||||
  - [Issues](https://github.com/python-pillow/Pillow/issues)
 | 
					  - [Issues](https://github.com/python-pillow/Pillow/issues)
 | 
				
			||||||
  - [Pull requests](https://github.com/python-pillow/Pillow/pulls)
 | 
					  - [Pull requests](https://github.com/python-pillow/Pillow/pulls)
 | 
				
			||||||
- [Release notes](https://pillow.readthedocs.io/en/stable/releasenotes/index.html)
 | 
					- [Release notes](https://pillow.readthedocs.io/en/stable/releasenotes/index.html)
 | 
				
			||||||
- [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst)
 | 
					- [Changelog](https://github.com/python-pillow/Pillow/releases)
 | 
				
			||||||
  - [Pre-fork](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst#pre-fork)
 | 
					  - [Pre-fork](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst#pre-fork)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Report a Vulnerability
 | 
					## Report a Vulnerability
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,10 +9,9 @@ Released quarterly on January 2nd, April 1st, July 1st and October 15th.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* [ ] Open a release ticket e.g. https://github.com/python-pillow/Pillow/issues/3154
 | 
					* [ ] Open a release ticket e.g. https://github.com/python-pillow/Pillow/issues/3154
 | 
				
			||||||
* [ ] Develop and prepare release in `main` branch.
 | 
					* [ ] Develop and prepare release in `main` branch.
 | 
				
			||||||
* [ ] Check [GitHub Actions](https://github.com/python-pillow/Pillow/actions) and [AppVeyor](https://ci.appveyor.com/project/python-pillow/Pillow) to confirm passing tests in `main` branch.
 | 
					* [ ] Check [GitHub Actions](https://github.com/python-pillow/Pillow/actions) to confirm passing tests in `main` branch.
 | 
				
			||||||
* [ ] Check that all the wheel builds pass the tests in the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml) jobs by manually triggering them.
 | 
					* [ ] Check that all the wheel builds pass the tests in the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml) jobs by manually triggering them.
 | 
				
			||||||
* [ ] In compliance with [PEP 440](https://peps.python.org/pep-0440/), update version identifier in `src/PIL/_version.py`
 | 
					* [ ] In compliance with [PEP 440](https://peps.python.org/pep-0440/), update version identifier in `src/PIL/_version.py`
 | 
				
			||||||
* [ ] Update `CHANGES.rst`.
 | 
					 | 
				
			||||||
* [ ] Run pre-release check via `make release-test` in a freshly cloned repo.
 | 
					* [ ] Run pre-release check via `make release-test` in a freshly cloned repo.
 | 
				
			||||||
* [ ] Create branch and tag for release e.g.:
 | 
					* [ ] Create branch and tag for release e.g.:
 | 
				
			||||||
  ```bash
 | 
					  ```bash
 | 
				
			||||||
| 
						 | 
					@ -34,13 +33,12 @@ Released quarterly on January 2nd, April 1st, July 1st and October 15th.
 | 
				
			||||||
Released as needed for security, installation or critical bug fixes.
 | 
					Released as needed for security, installation or critical bug fixes.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* [ ] Make necessary changes in `main` branch.
 | 
					* [ ] Make necessary changes in `main` branch.
 | 
				
			||||||
* [ ] Update `CHANGES.rst`.
 | 
					 | 
				
			||||||
* [ ] Check out release branch e.g.:
 | 
					* [ ] Check out release branch e.g.:
 | 
				
			||||||
  ```bash
 | 
					  ```bash
 | 
				
			||||||
  git checkout -t remotes/origin/5.2.x
 | 
					  git checkout -t remotes/origin/5.2.x
 | 
				
			||||||
  ```
 | 
					  ```
 | 
				
			||||||
* [ ] Cherry pick individual commits from `main` branch to release branch e.g. `5.2.x`, then `git push`.
 | 
					* [ ] Cherry pick individual commits from `main` branch to release branch e.g. `5.2.x`, then `git push`.
 | 
				
			||||||
* [ ] Check [GitHub Actions](https://github.com/python-pillow/Pillow/actions) and [AppVeyor](https://ci.appveyor.com/project/python-pillow/Pillow) to confirm passing tests in release branch e.g. `5.2.x`.
 | 
					* [ ] Check [GitHub Actions](https://github.com/python-pillow/Pillow/actions) to confirm passing tests in release branch e.g. `5.2.x`.
 | 
				
			||||||
* [ ] In compliance with [PEP 440](https://peps.python.org/pep-0440/), update version identifier in `src/PIL/_version.py`
 | 
					* [ ] In compliance with [PEP 440](https://peps.python.org/pep-0440/), update version identifier in `src/PIL/_version.py`
 | 
				
			||||||
* [ ] Run pre-release check via `make release-test`.
 | 
					* [ ] Run pre-release check via `make release-test`.
 | 
				
			||||||
* [ ] Create tag for release e.g.:
 | 
					* [ ] Create tag for release e.g.:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,6 +9,6 @@ from PIL import Image
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_j2k_overflow(tmp_path: Path) -> None:
 | 
					def test_j2k_overflow(tmp_path: Path) -> None:
 | 
				
			||||||
    im = Image.new("RGBA", (1024, 131584))
 | 
					    im = Image.new("RGBA", (1024, 131584))
 | 
				
			||||||
    target = str(tmp_path / "temp.jpc")
 | 
					    target = tmp_path / "temp.jpc"
 | 
				
			||||||
    with pytest.raises(OSError):
 | 
					    with pytest.raises(OSError):
 | 
				
			||||||
        im.save(target)
 | 
					        im.save(target)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -32,7 +32,7 @@ pytestmark = pytest.mark.skipif(sys.maxsize <= 2**32, reason="requires 64-bit sy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def _write_png(tmp_path: Path, xdim: int, ydim: int) -> None:
 | 
					def _write_png(tmp_path: Path, xdim: int, ydim: int) -> None:
 | 
				
			||||||
    f = str(tmp_path / "temp.png")
 | 
					    f = tmp_path / "temp.png"
 | 
				
			||||||
    im = Image.new("L", (xdim, ydim), 0)
 | 
					    im = Image.new("L", (xdim, ydim), 0)
 | 
				
			||||||
    im.save(f)
 | 
					    im.save(f)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -28,7 +28,7 @@ pytestmark = pytest.mark.skipif(sys.maxsize <= 2**32, reason="requires 64-bit sy
 | 
				
			||||||
def _write_png(tmp_path: Path, xdim: int, ydim: int) -> None:
 | 
					def _write_png(tmp_path: Path, xdim: int, ydim: int) -> None:
 | 
				
			||||||
    dtype = np.uint8
 | 
					    dtype = np.uint8
 | 
				
			||||||
    a = np.zeros((xdim, ydim), dtype=dtype)
 | 
					    a = np.zeros((xdim, ydim), dtype=dtype)
 | 
				
			||||||
    f = str(tmp_path / "temp.png")
 | 
					    f = tmp_path / "temp.png"
 | 
				
			||||||
    im = Image.fromarray(a, "L")
 | 
					    im = Image.fromarray(a, "L")
 | 
				
			||||||
    im.save(f)
 | 
					    im.save(f)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,19 +3,18 @@ from __future__ import annotations
 | 
				
			||||||
import zlib
 | 
					import zlib
 | 
				
			||||||
from io import BytesIO
 | 
					from io import BytesIO
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import pytest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from PIL import Image, ImageFile, PngImagePlugin
 | 
					from PIL import Image, ImageFile, PngImagePlugin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TEST_FILE = "Tests/images/png_decompression_dos.png"
 | 
					TEST_FILE = "Tests/images/png_decompression_dos.png"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_ignore_dos_text() -> None:
 | 
					def test_ignore_dos_text(monkeypatch: pytest.MonkeyPatch) -> None:
 | 
				
			||||||
    ImageFile.LOAD_TRUNCATED_IMAGES = True
 | 
					    monkeypatch.setattr(ImageFile, "LOAD_TRUNCATED_IMAGES", True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try:
 | 
					    with Image.open(TEST_FILE) as im:
 | 
				
			||||||
        im = Image.open(TEST_FILE)
 | 
					 | 
				
			||||||
        im.load()
 | 
					        im.load()
 | 
				
			||||||
    finally:
 | 
					 | 
				
			||||||
        ImageFile.LOAD_TRUNCATED_IMAGES = False
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        assert isinstance(im, PngImagePlugin.PngImageFile)
 | 
					        assert isinstance(im, PngImagePlugin.PngImageFile)
 | 
				
			||||||
        for s in im.text.values():
 | 
					        for s in im.text.values():
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -34,6 +34,7 @@ def test_wheel_features() -> None:
 | 
				
			||||||
        "fribidi",
 | 
					        "fribidi",
 | 
				
			||||||
        "harfbuzz",
 | 
					        "harfbuzz",
 | 
				
			||||||
        "libjpeg_turbo",
 | 
					        "libjpeg_turbo",
 | 
				
			||||||
 | 
					        "zlib_ng",
 | 
				
			||||||
        "xcb",
 | 
					        "xcb",
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,11 +9,11 @@ import os
 | 
				
			||||||
import shutil
 | 
					import shutil
 | 
				
			||||||
import subprocess
 | 
					import subprocess
 | 
				
			||||||
import sys
 | 
					import sys
 | 
				
			||||||
import sysconfig
 | 
					 | 
				
			||||||
import tempfile
 | 
					import tempfile
 | 
				
			||||||
from collections.abc import Sequence
 | 
					from collections.abc import Sequence
 | 
				
			||||||
from functools import lru_cache
 | 
					from functools import lru_cache
 | 
				
			||||||
from io import BytesIO
 | 
					from io import BytesIO
 | 
				
			||||||
 | 
					from pathlib import Path
 | 
				
			||||||
from typing import Any, Callable
 | 
					from typing import Any, Callable
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import pytest
 | 
					import pytest
 | 
				
			||||||
| 
						 | 
					@ -96,7 +96,10 @@ def assert_image_equal(a: Image.Image, b: Image.Image, msg: str | None = None) -
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def assert_image_equal_tofile(
 | 
					def assert_image_equal_tofile(
 | 
				
			||||||
    a: Image.Image, filename: str, msg: str | None = None, mode: str | None = None
 | 
					    a: Image.Image,
 | 
				
			||||||
 | 
					    filename: str | Path,
 | 
				
			||||||
 | 
					    msg: str | None = None,
 | 
				
			||||||
 | 
					    mode: str | None = None,
 | 
				
			||||||
) -> None:
 | 
					) -> None:
 | 
				
			||||||
    with Image.open(filename) as img:
 | 
					    with Image.open(filename) as img:
 | 
				
			||||||
        if mode:
 | 
					        if mode:
 | 
				
			||||||
| 
						 | 
					@ -137,21 +140,14 @@ def assert_image_similar(
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def assert_image_similar_tofile(
 | 
					def assert_image_similar_tofile(
 | 
				
			||||||
    a: Image.Image,
 | 
					    a: Image.Image,
 | 
				
			||||||
    filename: str,
 | 
					    filename: str | Path,
 | 
				
			||||||
    epsilon: float,
 | 
					    epsilon: float,
 | 
				
			||||||
    msg: str | None = None,
 | 
					    msg: str | None = None,
 | 
				
			||||||
    mode: str | None = None,
 | 
					 | 
				
			||||||
) -> None:
 | 
					) -> None:
 | 
				
			||||||
    with Image.open(filename) as img:
 | 
					    with Image.open(filename) as img:
 | 
				
			||||||
        if mode:
 | 
					 | 
				
			||||||
            img = img.convert(mode)
 | 
					 | 
				
			||||||
        assert_image_similar(a, img, epsilon, msg)
 | 
					        assert_image_similar(a, img, epsilon, msg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def assert_all_same(items: Sequence[Any], msg: str | None = None) -> None:
 | 
					 | 
				
			||||||
    assert items.count(items[0]) == len(items), msg
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def assert_not_all_same(items: Sequence[Any], msg: str | None = None) -> None:
 | 
					def assert_not_all_same(items: Sequence[Any], msg: str | None = None) -> None:
 | 
				
			||||||
    assert items.count(items[0]) != len(items), msg
 | 
					    assert items.count(items[0]) != len(items), msg
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -327,16 +323,7 @@ def magick_command() -> list[str] | None:
 | 
				
			||||||
    return None
 | 
					    return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def on_appveyor() -> bool:
 | 
					 | 
				
			||||||
    return "APPVEYOR" in os.environ
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def on_github_actions() -> bool:
 | 
					 | 
				
			||||||
    return "GITHUB_ACTIONS" in os.environ
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def on_ci() -> bool:
 | 
					def on_ci() -> bool:
 | 
				
			||||||
    # GitHub Actions and AppVeyor have "CI"
 | 
					 | 
				
			||||||
    return "CI" in os.environ
 | 
					    return "CI" in os.environ
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -358,10 +345,6 @@ def is_pypy() -> bool:
 | 
				
			||||||
    return hasattr(sys, "pypy_translation_info")
 | 
					    return hasattr(sys, "pypy_translation_info")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def is_mingw() -> bool:
 | 
					 | 
				
			||||||
    return sysconfig.get_platform() == "mingw"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class CachedProperty:
 | 
					class CachedProperty:
 | 
				
			||||||
    def __init__(self, func: Callable[[Any], Any]) -> None:
 | 
					    def __init__(self, func: Callable[[Any], Any]) -> None:
 | 
				
			||||||
        self.func = func
 | 
					        self.func = func
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								Tests/images/drawing_emf_ref_72_144.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Tests/images/drawing_emf_ref_72_144.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 984 B  | 
							
								
								
									
										260
									
								
								Tests/images/full_gimp_palette.gpl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										260
									
								
								Tests/images/full_gimp_palette.gpl
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,260 @@
 | 
				
			||||||
 | 
					GIMP Palette
 | 
				
			||||||
 | 
					Name: fullpalette
 | 
				
			||||||
 | 
					Columns: 4
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					  0   0   0     Index 0
 | 
				
			||||||
 | 
					  1   1   1     Index 1
 | 
				
			||||||
 | 
					  2   2   2     Index 2
 | 
				
			||||||
 | 
					  3   3   3     Index 3
 | 
				
			||||||
 | 
					  4   4   4     Index 4
 | 
				
			||||||
 | 
					  5   5   5     Index 5
 | 
				
			||||||
 | 
					  6   6   6     Index 6
 | 
				
			||||||
 | 
					  7   7   7     Index 7
 | 
				
			||||||
 | 
					  8   8   8     Index 8
 | 
				
			||||||
 | 
					  9   9   9     Index 9
 | 
				
			||||||
 | 
					 10  10  10     Index 10
 | 
				
			||||||
 | 
					 11  11  11     Index 11
 | 
				
			||||||
 | 
					 12  12  12     Index 12
 | 
				
			||||||
 | 
					 13  13  13     Index 13
 | 
				
			||||||
 | 
					 14  14  14     Index 14
 | 
				
			||||||
 | 
					 15  15  15     Index 15
 | 
				
			||||||
 | 
					 16  16  16     Index 16
 | 
				
			||||||
 | 
					 17  17  17     Index 17
 | 
				
			||||||
 | 
					 18  18  18     Index 18
 | 
				
			||||||
 | 
					 19  19  19     Index 19
 | 
				
			||||||
 | 
					 20  20  20     Index 20
 | 
				
			||||||
 | 
					 21  21  21     Index 21
 | 
				
			||||||
 | 
					 22  22  22     Index 22
 | 
				
			||||||
 | 
					 23  23  23     Index 23
 | 
				
			||||||
 | 
					 24  24  24     Index 24
 | 
				
			||||||
 | 
					 25  25  25     Index 25
 | 
				
			||||||
 | 
					 26  26  26     Index 26
 | 
				
			||||||
 | 
					 27  27  27     Index 27
 | 
				
			||||||
 | 
					 28  28  28     Index 28
 | 
				
			||||||
 | 
					 29  29  29     Index 29
 | 
				
			||||||
 | 
					 30  30  30     Index 30
 | 
				
			||||||
 | 
					 31  31  31     Index 31
 | 
				
			||||||
 | 
					 32  32  32     Index 32
 | 
				
			||||||
 | 
					 33  33  33     Index 33
 | 
				
			||||||
 | 
					 34  34  34     Index 34
 | 
				
			||||||
 | 
					 35  35  35     Index 35
 | 
				
			||||||
 | 
					 36  36  36     Index 36
 | 
				
			||||||
 | 
					 37  37  37     Index 37
 | 
				
			||||||
 | 
					 38  38  38     Index 38
 | 
				
			||||||
 | 
					 39  39  39     Index 39
 | 
				
			||||||
 | 
					 40  40  40     Index 40
 | 
				
			||||||
 | 
					 41  41  41     Index 41
 | 
				
			||||||
 | 
					 42  42  42     Index 42
 | 
				
			||||||
 | 
					 43  43  43     Index 43
 | 
				
			||||||
 | 
					 44  44  44     Index 44
 | 
				
			||||||
 | 
					 45  45  45     Index 45
 | 
				
			||||||
 | 
					 46  46  46     Index 46
 | 
				
			||||||
 | 
					 47  47  47     Index 47
 | 
				
			||||||
 | 
					 48  48  48     Index 48
 | 
				
			||||||
 | 
					 49  49  49     Index 49
 | 
				
			||||||
 | 
					 50  50  50     Index 50
 | 
				
			||||||
 | 
					 51  51  51     Index 51
 | 
				
			||||||
 | 
					 52  52  52     Index 52
 | 
				
			||||||
 | 
					 53  53  53     Index 53
 | 
				
			||||||
 | 
					 54  54  54     Index 54
 | 
				
			||||||
 | 
					 55  55  55     Index 55
 | 
				
			||||||
 | 
					 56  56  56     Index 56
 | 
				
			||||||
 | 
					 57  57  57     Index 57
 | 
				
			||||||
 | 
					 58  58  58     Index 58
 | 
				
			||||||
 | 
					 59  59  59     Index 59
 | 
				
			||||||
 | 
					 60  60  60     Index 60
 | 
				
			||||||
 | 
					 61  61  61     Index 61
 | 
				
			||||||
 | 
					 62  62  62     Index 62
 | 
				
			||||||
 | 
					 63  63  63     Index 63
 | 
				
			||||||
 | 
					 64  64  64     Index 64
 | 
				
			||||||
 | 
					 65  65  65     Index 65
 | 
				
			||||||
 | 
					 66  66  66     Index 66
 | 
				
			||||||
 | 
					 67  67  67     Index 67
 | 
				
			||||||
 | 
					 68  68  68     Index 68
 | 
				
			||||||
 | 
					 69  69  69     Index 69
 | 
				
			||||||
 | 
					 70  70  70     Index 70
 | 
				
			||||||
 | 
					 71  71  71     Index 71
 | 
				
			||||||
 | 
					 72  72  72     Index 72
 | 
				
			||||||
 | 
					 73  73  73     Index 73
 | 
				
			||||||
 | 
					 74  74  74     Index 74
 | 
				
			||||||
 | 
					 75  75  75     Index 75
 | 
				
			||||||
 | 
					 76  76  76     Index 76
 | 
				
			||||||
 | 
					 77  77  77     Index 77
 | 
				
			||||||
 | 
					 78  78  78     Index 78
 | 
				
			||||||
 | 
					 79  79  79     Index 79
 | 
				
			||||||
 | 
					 80  80  80     Index 80
 | 
				
			||||||
 | 
					 81  81  81     Index 81
 | 
				
			||||||
 | 
					 82  82  82     Index 82
 | 
				
			||||||
 | 
					 83  83  83     Index 83
 | 
				
			||||||
 | 
					 84  84  84     Index 84
 | 
				
			||||||
 | 
					 85  85  85     Index 85
 | 
				
			||||||
 | 
					 86  86  86     Index 86
 | 
				
			||||||
 | 
					 87  87  87     Index 87
 | 
				
			||||||
 | 
					 88  88  88     Index 88
 | 
				
			||||||
 | 
					 89  89  89     Index 89
 | 
				
			||||||
 | 
					 90  90  90     Index 90
 | 
				
			||||||
 | 
					 91  91  91     Index 91
 | 
				
			||||||
 | 
					 92  92  92     Index 92
 | 
				
			||||||
 | 
					 93  93  93     Index 93
 | 
				
			||||||
 | 
					 94  94  94     Index 94
 | 
				
			||||||
 | 
					 95  95  95     Index 95
 | 
				
			||||||
 | 
					 96  96  96     Index 96
 | 
				
			||||||
 | 
					 97  97  97     Index 97
 | 
				
			||||||
 | 
					 98  98  98     Index 98
 | 
				
			||||||
 | 
					 99  99  99     Index 99
 | 
				
			||||||
 | 
					100 100 100     Index 100
 | 
				
			||||||
 | 
					101 101 101     Index 101
 | 
				
			||||||
 | 
					102 102 102     Index 102
 | 
				
			||||||
 | 
					103 103 103     Index 103
 | 
				
			||||||
 | 
					104 104 104     Index 104
 | 
				
			||||||
 | 
					105 105 105     Index 105
 | 
				
			||||||
 | 
					106 106 106     Index 106
 | 
				
			||||||
 | 
					107 107 107     Index 107
 | 
				
			||||||
 | 
					108 108 108     Index 108
 | 
				
			||||||
 | 
					109 109 109     Index 109
 | 
				
			||||||
 | 
					110 110 110     Index 110
 | 
				
			||||||
 | 
					111 111 111     Index 111
 | 
				
			||||||
 | 
					112 112 112     Index 112
 | 
				
			||||||
 | 
					113 113 113     Index 113
 | 
				
			||||||
 | 
					114 114 114     Index 114
 | 
				
			||||||
 | 
					115 115 115     Index 115
 | 
				
			||||||
 | 
					116 116 116     Index 116
 | 
				
			||||||
 | 
					117 117 117     Index 117
 | 
				
			||||||
 | 
					118 118 118     Index 118
 | 
				
			||||||
 | 
					119 119 119     Index 119
 | 
				
			||||||
 | 
					120 120 120     Index 120
 | 
				
			||||||
 | 
					121 121 121     Index 121
 | 
				
			||||||
 | 
					122 122 122     Index 122
 | 
				
			||||||
 | 
					123 123 123     Index 123
 | 
				
			||||||
 | 
					124 124 124     Index 124
 | 
				
			||||||
 | 
					125 125 125     Index 125
 | 
				
			||||||
 | 
					126 126 126     Index 126
 | 
				
			||||||
 | 
					127 127 127     Index 127
 | 
				
			||||||
 | 
					128 128 128     Index 128
 | 
				
			||||||
 | 
					129 129 129     Index 129
 | 
				
			||||||
 | 
					130 130 130     Index 130
 | 
				
			||||||
 | 
					131 131 131     Index 131
 | 
				
			||||||
 | 
					132 132 132     Index 132
 | 
				
			||||||
 | 
					133 133 133     Index 133
 | 
				
			||||||
 | 
					134 134 134     Index 134
 | 
				
			||||||
 | 
					135 135 135     Index 135
 | 
				
			||||||
 | 
					136 136 136     Index 136
 | 
				
			||||||
 | 
					137 137 137     Index 137
 | 
				
			||||||
 | 
					138 138 138     Index 138
 | 
				
			||||||
 | 
					139 139 139     Index 139
 | 
				
			||||||
 | 
					140 140 140     Index 140
 | 
				
			||||||
 | 
					141 141 141     Index 141
 | 
				
			||||||
 | 
					142 142 142     Index 142
 | 
				
			||||||
 | 
					143 143 143     Index 143
 | 
				
			||||||
 | 
					144 144 144     Index 144
 | 
				
			||||||
 | 
					145 145 145     Index 145
 | 
				
			||||||
 | 
					146 146 146     Index 146
 | 
				
			||||||
 | 
					147 147 147     Index 147
 | 
				
			||||||
 | 
					148 148 148     Index 148
 | 
				
			||||||
 | 
					149 149 149     Index 149
 | 
				
			||||||
 | 
					150 150 150     Index 150
 | 
				
			||||||
 | 
					151 151 151     Index 151
 | 
				
			||||||
 | 
					152 152 152     Index 152
 | 
				
			||||||
 | 
					153 153 153     Index 153
 | 
				
			||||||
 | 
					154 154 154     Index 154
 | 
				
			||||||
 | 
					155 155 155     Index 155
 | 
				
			||||||
 | 
					156 156 156     Index 156
 | 
				
			||||||
 | 
					157 157 157     Index 157
 | 
				
			||||||
 | 
					158 158 158     Index 158
 | 
				
			||||||
 | 
					159 159 159     Index 159
 | 
				
			||||||
 | 
					160 160 160     Index 160
 | 
				
			||||||
 | 
					161 161 161     Index 161
 | 
				
			||||||
 | 
					162 162 162     Index 162
 | 
				
			||||||
 | 
					163 163 163     Index 163
 | 
				
			||||||
 | 
					164 164 164     Index 164
 | 
				
			||||||
 | 
					165 165 165     Index 165
 | 
				
			||||||
 | 
					166 166 166     Index 166
 | 
				
			||||||
 | 
					167 167 167     Index 167
 | 
				
			||||||
 | 
					168 168 168     Index 168
 | 
				
			||||||
 | 
					169 169 169     Index 169
 | 
				
			||||||
 | 
					170 170 170     Index 170
 | 
				
			||||||
 | 
					171 171 171     Index 171
 | 
				
			||||||
 | 
					172 172 172     Index 172
 | 
				
			||||||
 | 
					173 173 173     Index 173
 | 
				
			||||||
 | 
					174 174 174     Index 174
 | 
				
			||||||
 | 
					175 175 175     Index 175
 | 
				
			||||||
 | 
					176 176 176     Index 176
 | 
				
			||||||
 | 
					177 177 177     Index 177
 | 
				
			||||||
 | 
					178 178 178     Index 178
 | 
				
			||||||
 | 
					179 179 179     Index 179
 | 
				
			||||||
 | 
					180 180 180     Index 180
 | 
				
			||||||
 | 
					181 181 181     Index 181
 | 
				
			||||||
 | 
					182 182 182     Index 182
 | 
				
			||||||
 | 
					183 183 183     Index 183
 | 
				
			||||||
 | 
					184 184 184     Index 184
 | 
				
			||||||
 | 
					185 185 185     Index 185
 | 
				
			||||||
 | 
					186 186 186     Index 186
 | 
				
			||||||
 | 
					187 187 187     Index 187
 | 
				
			||||||
 | 
					188 188 188     Index 188
 | 
				
			||||||
 | 
					189 189 189     Index 189
 | 
				
			||||||
 | 
					190 190 190     Index 190
 | 
				
			||||||
 | 
					191 191 191     Index 191
 | 
				
			||||||
 | 
					192 192 192     Index 192
 | 
				
			||||||
 | 
					193 193 193     Index 193
 | 
				
			||||||
 | 
					194 194 194     Index 194
 | 
				
			||||||
 | 
					195 195 195     Index 195
 | 
				
			||||||
 | 
					196 196 196     Index 196
 | 
				
			||||||
 | 
					197 197 197     Index 197
 | 
				
			||||||
 | 
					198 198 198     Index 198
 | 
				
			||||||
 | 
					199 199 199     Index 199
 | 
				
			||||||
 | 
					200 200 200     Index 200
 | 
				
			||||||
 | 
					201 201 201     Index 201
 | 
				
			||||||
 | 
					202 202 202     Index 202
 | 
				
			||||||
 | 
					203 203 203     Index 203
 | 
				
			||||||
 | 
					204 204 204     Index 204
 | 
				
			||||||
 | 
					205 205 205     Index 205
 | 
				
			||||||
 | 
					206 206 206     Index 206
 | 
				
			||||||
 | 
					207 207 207     Index 207
 | 
				
			||||||
 | 
					208 208 208     Index 208
 | 
				
			||||||
 | 
					209 209 209     Index 209
 | 
				
			||||||
 | 
					210 210 210     Index 210
 | 
				
			||||||
 | 
					211 211 211     Index 211
 | 
				
			||||||
 | 
					212 212 212     Index 212
 | 
				
			||||||
 | 
					213 213 213     Index 213
 | 
				
			||||||
 | 
					214 214 214     Index 214
 | 
				
			||||||
 | 
					215 215 215     Index 215
 | 
				
			||||||
 | 
					216 216 216     Index 216
 | 
				
			||||||
 | 
					217 217 217     Index 217
 | 
				
			||||||
 | 
					218 218 218     Index 218
 | 
				
			||||||
 | 
					219 219 219     Index 219
 | 
				
			||||||
 | 
					220 220 220     Index 220
 | 
				
			||||||
 | 
					221 221 221     Index 221
 | 
				
			||||||
 | 
					222 222 222     Index 222
 | 
				
			||||||
 | 
					223 223 223     Index 223
 | 
				
			||||||
 | 
					224 224 224     Index 224
 | 
				
			||||||
 | 
					225 225 225     Index 225
 | 
				
			||||||
 | 
					226 226 226     Index 226
 | 
				
			||||||
 | 
					227 227 227     Index 227
 | 
				
			||||||
 | 
					228 228 228     Index 228
 | 
				
			||||||
 | 
					229 229 229     Index 229
 | 
				
			||||||
 | 
					230 230 230     Index 230
 | 
				
			||||||
 | 
					231 231 231     Index 231
 | 
				
			||||||
 | 
					232 232 232     Index 232
 | 
				
			||||||
 | 
					233 233 233     Index 233
 | 
				
			||||||
 | 
					234 234 234     Index 234
 | 
				
			||||||
 | 
					235 235 235     Index 235
 | 
				
			||||||
 | 
					236 236 236     Index 236
 | 
				
			||||||
 | 
					237 237 237     Index 237
 | 
				
			||||||
 | 
					238 238 238     Index 238
 | 
				
			||||||
 | 
					239 239 239     Index 239
 | 
				
			||||||
 | 
					240 240 240     Index 240
 | 
				
			||||||
 | 
					241 241 241     Index 241
 | 
				
			||||||
 | 
					242 242 242     Index 242
 | 
				
			||||||
 | 
					243 243 243     Index 243
 | 
				
			||||||
 | 
					244 244 244     Index 244
 | 
				
			||||||
 | 
					245 245 245     Index 245
 | 
				
			||||||
 | 
					246 246 246     Index 246
 | 
				
			||||||
 | 
					247 247 247     Index 247
 | 
				
			||||||
 | 
					248 248 248     Index 248
 | 
				
			||||||
 | 
					249 249 249     Index 249
 | 
				
			||||||
 | 
					250 250 250     Index 250
 | 
				
			||||||
 | 
					251 251 251     Index 251
 | 
				
			||||||
 | 
					252 252 252     Index 252
 | 
				
			||||||
 | 
					253 253 253     Index 253
 | 
				
			||||||
 | 
					254 254 254     Index 254
 | 
				
			||||||
 | 
					255 255 255     Index 255
 | 
				
			||||||
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 486 B After Width: | Height: | Size: 533 B  | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/jfif_unit_cm.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Tests/images/jfif_unit_cm.jpg
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 391 B  | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/multiline_text_justify.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Tests/images/multiline_text_justify.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 3.2 KiB  | 
| 
						 | 
					@ -7,7 +7,7 @@ import fuzzers
 | 
				
			||||||
import packaging
 | 
					import packaging
 | 
				
			||||||
import pytest
 | 
					import pytest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from PIL import Image, UnidentifiedImageError, features
 | 
					from PIL import Image, features
 | 
				
			||||||
from Tests.helper import skip_unless_feature
 | 
					from Tests.helper import skip_unless_feature
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if sys.platform.startswith("win32"):
 | 
					if sys.platform.startswith("win32"):
 | 
				
			||||||
| 
						 | 
					@ -32,21 +32,17 @@ def test_fuzz_images(path: str) -> None:
 | 
				
			||||||
            fuzzers.fuzz_image(f.read())
 | 
					            fuzzers.fuzz_image(f.read())
 | 
				
			||||||
            assert True
 | 
					            assert True
 | 
				
			||||||
    except (
 | 
					    except (
 | 
				
			||||||
 | 
					        # Known exceptions from Pillow
 | 
				
			||||||
        OSError,
 | 
					        OSError,
 | 
				
			||||||
        SyntaxError,
 | 
					        SyntaxError,
 | 
				
			||||||
        MemoryError,
 | 
					        MemoryError,
 | 
				
			||||||
        ValueError,
 | 
					        ValueError,
 | 
				
			||||||
        NotImplementedError,
 | 
					        NotImplementedError,
 | 
				
			||||||
        OverflowError,
 | 
					        OverflowError,
 | 
				
			||||||
    ):
 | 
					        # Known Image.* exceptions
 | 
				
			||||||
        # Known exceptions that are through from Pillow
 | 
					 | 
				
			||||||
        assert True
 | 
					 | 
				
			||||||
    except (
 | 
					 | 
				
			||||||
        Image.DecompressionBombError,
 | 
					        Image.DecompressionBombError,
 | 
				
			||||||
        Image.DecompressionBombWarning,
 | 
					        Image.DecompressionBombWarning,
 | 
				
			||||||
        UnidentifiedImageError,
 | 
					 | 
				
			||||||
    ):
 | 
					    ):
 | 
				
			||||||
        # Known Image.* exceptions
 | 
					 | 
				
			||||||
        assert True
 | 
					        assert True
 | 
				
			||||||
    finally:
 | 
					    finally:
 | 
				
			||||||
        fuzzers.disable_decompressionbomb_error()
 | 
					        fuzzers.disable_decompressionbomb_error()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -19,7 +19,7 @@ except ImportError:
 | 
				
			||||||
class TestColorLut3DCoreAPI:
 | 
					class TestColorLut3DCoreAPI:
 | 
				
			||||||
    def generate_identity_table(
 | 
					    def generate_identity_table(
 | 
				
			||||||
        self, channels: int, size: int | tuple[int, int, int]
 | 
					        self, channels: int, size: int | tuple[int, int, int]
 | 
				
			||||||
    ) -> tuple[int, int, int, int, list[float]]:
 | 
					    ) -> tuple[int, tuple[int, int, int], list[float]]:
 | 
				
			||||||
        if isinstance(size, tuple):
 | 
					        if isinstance(size, tuple):
 | 
				
			||||||
            size_1d, size_2d, size_3d = size
 | 
					            size_1d, size_2d, size_3d = size
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
| 
						 | 
					@ -39,9 +39,7 @@ class TestColorLut3DCoreAPI:
 | 
				
			||||||
        ]
 | 
					        ]
 | 
				
			||||||
        return (
 | 
					        return (
 | 
				
			||||||
            channels,
 | 
					            channels,
 | 
				
			||||||
            size_1d,
 | 
					            (size_1d, size_2d, size_3d),
 | 
				
			||||||
            size_2d,
 | 
					 | 
				
			||||||
            size_3d,
 | 
					 | 
				
			||||||
            [item for sublist in table for item in sublist],
 | 
					            [item for sublist in table for item in sublist],
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -89,21 +87,21 @@ class TestColorLut3DCoreAPI:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        with pytest.raises(ValueError, match=r"size1D \* size2D \* size3D"):
 | 
					        with pytest.raises(ValueError, match=r"size1D \* size2D \* size3D"):
 | 
				
			||||||
            im.im.color_lut_3d(
 | 
					            im.im.color_lut_3d(
 | 
				
			||||||
                "RGB", Image.Resampling.BILINEAR, 3, 2, 2, 2, [0, 0, 0] * 7
 | 
					                "RGB", Image.Resampling.BILINEAR, 3, (2, 2, 2), [0, 0, 0] * 7
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        with pytest.raises(ValueError, match=r"size1D \* size2D \* size3D"):
 | 
					        with pytest.raises(ValueError, match=r"size1D \* size2D \* size3D"):
 | 
				
			||||||
            im.im.color_lut_3d(
 | 
					            im.im.color_lut_3d(
 | 
				
			||||||
                "RGB", Image.Resampling.BILINEAR, 3, 2, 2, 2, [0, 0, 0] * 9
 | 
					                "RGB", Image.Resampling.BILINEAR, 3, (2, 2, 2), [0, 0, 0] * 9
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        with pytest.raises(TypeError):
 | 
					        with pytest.raises(TypeError):
 | 
				
			||||||
            im.im.color_lut_3d(
 | 
					            im.im.color_lut_3d(
 | 
				
			||||||
                "RGB", Image.Resampling.BILINEAR, 3, 2, 2, 2, [0, 0, "0"] * 8
 | 
					                "RGB", Image.Resampling.BILINEAR, 3, (2, 2, 2), [0, 0, "0"] * 8
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        with pytest.raises(TypeError):
 | 
					        with pytest.raises(TypeError):
 | 
				
			||||||
            im.im.color_lut_3d("RGB", Image.Resampling.BILINEAR, 3, 2, 2, 2, 16)
 | 
					            im.im.color_lut_3d("RGB", Image.Resampling.BILINEAR, 3, (2, 2, 2), 16)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @pytest.mark.parametrize(
 | 
					    @pytest.mark.parametrize(
 | 
				
			||||||
        "lut_mode, table_channels, table_size",
 | 
					        "lut_mode, table_channels, table_size",
 | 
				
			||||||
| 
						 | 
					@ -264,7 +262,7 @@ class TestColorLut3DCoreAPI:
 | 
				
			||||||
        assert_image_equal(
 | 
					        assert_image_equal(
 | 
				
			||||||
            Image.merge('RGB', im.split()[::-1]),
 | 
					            Image.merge('RGB', im.split()[::-1]),
 | 
				
			||||||
            im._new(im.im.color_lut_3d('RGB', Image.Resampling.BILINEAR,
 | 
					            im._new(im.im.color_lut_3d('RGB', Image.Resampling.BILINEAR,
 | 
				
			||||||
                    3, 2, 2, 2, [
 | 
					                    3, (2, 2, 2), [
 | 
				
			||||||
                        0, 0, 0,  0, 0, 1,
 | 
					                        0, 0, 0,  0, 0, 1,
 | 
				
			||||||
                        0, 1, 0,  0, 1, 1,
 | 
					                        0, 1, 0,  0, 1, 1,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -286,7 +284,7 @@ class TestColorLut3DCoreAPI:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # fmt: off
 | 
					        # fmt: off
 | 
				
			||||||
        transformed = im._new(im.im.color_lut_3d('RGB', Image.Resampling.BILINEAR,
 | 
					        transformed = im._new(im.im.color_lut_3d('RGB', Image.Resampling.BILINEAR,
 | 
				
			||||||
                              3, 2, 2, 2,
 | 
					                              3, (2, 2, 2),
 | 
				
			||||||
                              [
 | 
					                              [
 | 
				
			||||||
                                  -1, -1, -1,   2, -1, -1,
 | 
					                                  -1, -1, -1,   2, -1, -1,
 | 
				
			||||||
                                  -1,  2, -1,   2,  2, -1,
 | 
					                                  -1,  2, -1,   2,  2, -1,
 | 
				
			||||||
| 
						 | 
					@ -307,7 +305,7 @@ class TestColorLut3DCoreAPI:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # fmt: off
 | 
					        # fmt: off
 | 
				
			||||||
        transformed = im._new(im.im.color_lut_3d('RGB', Image.Resampling.BILINEAR,
 | 
					        transformed = im._new(im.im.color_lut_3d('RGB', Image.Resampling.BILINEAR,
 | 
				
			||||||
                              3, 2, 2, 2,
 | 
					                              3, (2, 2, 2),
 | 
				
			||||||
                              [
 | 
					                              [
 | 
				
			||||||
                                  -3, -3, -3,   5, -3, -3,
 | 
					                                  -3, -3, -3,   5, -3, -3,
 | 
				
			||||||
                                  -3,  5, -3,   5,  5, -3,
 | 
					                                  -3,  5, -3,   5,  5, -3,
 | 
				
			||||||
| 
						 | 
					@ -388,10 +386,12 @@ class TestColorLut3DFilter:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        table = numpy.ones((7 * 6 * 5, 3), dtype=numpy.float16)
 | 
					        table = numpy.ones((7 * 6 * 5, 3), dtype=numpy.float16)
 | 
				
			||||||
        lut = ImageFilter.Color3DLUT((5, 6, 7), table)
 | 
					        lut = ImageFilter.Color3DLUT((5, 6, 7), table)
 | 
				
			||||||
 | 
					        assert isinstance(lut.table, numpy.ndarray)
 | 
				
			||||||
        assert lut.table.shape == (table.size,)
 | 
					        assert lut.table.shape == (table.size,)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        table = numpy.ones((7 * 6 * 5 * 3), dtype=numpy.float16)
 | 
					        table = numpy.ones((7 * 6 * 5 * 3), dtype=numpy.float16)
 | 
				
			||||||
        lut = ImageFilter.Color3DLUT((5, 6, 7), table)
 | 
					        lut = ImageFilter.Color3DLUT((5, 6, 7), table)
 | 
				
			||||||
 | 
					        assert isinstance(lut.table, numpy.ndarray)
 | 
				
			||||||
        assert lut.table.shape == (table.size,)
 | 
					        assert lut.table.shape == (table.size,)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Check application
 | 
					        # Check application
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,19 +12,16 @@ ORIGINAL_LIMIT = Image.MAX_IMAGE_PIXELS
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TestDecompressionBomb:
 | 
					class TestDecompressionBomb:
 | 
				
			||||||
    def teardown_method(self) -> None:
 | 
					 | 
				
			||||||
        Image.MAX_IMAGE_PIXELS = ORIGINAL_LIMIT
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_no_warning_small_file(self) -> None:
 | 
					    def test_no_warning_small_file(self) -> None:
 | 
				
			||||||
        # Implicit assert: no warning.
 | 
					        # Implicit assert: no warning.
 | 
				
			||||||
        # A warning would cause a failure.
 | 
					        # A warning would cause a failure.
 | 
				
			||||||
        with Image.open(TEST_FILE):
 | 
					        with Image.open(TEST_FILE):
 | 
				
			||||||
            pass
 | 
					            pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_no_warning_no_limit(self) -> None:
 | 
					    def test_no_warning_no_limit(self, monkeypatch: pytest.MonkeyPatch) -> None:
 | 
				
			||||||
        # Arrange
 | 
					        # Arrange
 | 
				
			||||||
        # Turn limit off
 | 
					        # Turn limit off
 | 
				
			||||||
        Image.MAX_IMAGE_PIXELS = None
 | 
					        monkeypatch.setattr(Image, "MAX_IMAGE_PIXELS", None)
 | 
				
			||||||
        assert Image.MAX_IMAGE_PIXELS is None
 | 
					        assert Image.MAX_IMAGE_PIXELS is None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Act / Assert
 | 
					        # Act / Assert
 | 
				
			||||||
| 
						 | 
					@ -33,18 +30,18 @@ class TestDecompressionBomb:
 | 
				
			||||||
        with Image.open(TEST_FILE):
 | 
					        with Image.open(TEST_FILE):
 | 
				
			||||||
            pass
 | 
					            pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_warning(self) -> None:
 | 
					    def test_warning(self, monkeypatch: pytest.MonkeyPatch) -> None:
 | 
				
			||||||
        # Set limit to trigger warning on the test file
 | 
					        # Set limit to trigger warning on the test file
 | 
				
			||||||
        Image.MAX_IMAGE_PIXELS = 128 * 128 - 1
 | 
					        monkeypatch.setattr(Image, "MAX_IMAGE_PIXELS", 128 * 128 - 1)
 | 
				
			||||||
        assert Image.MAX_IMAGE_PIXELS == 128 * 128 - 1
 | 
					        assert Image.MAX_IMAGE_PIXELS == 128 * 128 - 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        with pytest.warns(Image.DecompressionBombWarning):
 | 
					        with pytest.warns(Image.DecompressionBombWarning):
 | 
				
			||||||
            with Image.open(TEST_FILE):
 | 
					            with Image.open(TEST_FILE):
 | 
				
			||||||
                pass
 | 
					                pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_exception(self) -> None:
 | 
					    def test_exception(self, monkeypatch: pytest.MonkeyPatch) -> None:
 | 
				
			||||||
        # 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
 | 
					        monkeypatch.setattr(Image, "MAX_IMAGE_PIXELS", 64 * 128 - 1)
 | 
				
			||||||
        assert Image.MAX_IMAGE_PIXELS == 64 * 128 - 1
 | 
					        assert Image.MAX_IMAGE_PIXELS == 64 * 128 - 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        with pytest.raises(Image.DecompressionBombError):
 | 
					        with pytest.raises(Image.DecompressionBombError):
 | 
				
			||||||
| 
						 | 
					@ -66,9 +63,9 @@ class TestDecompressionBomb:
 | 
				
			||||||
            with pytest.raises(Image.DecompressionBombError):
 | 
					            with pytest.raises(Image.DecompressionBombError):
 | 
				
			||||||
                im.seek(1)
 | 
					                im.seek(1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_exception_gif_zero_width(self) -> None:
 | 
					    def test_exception_gif_zero_width(self, monkeypatch: pytest.MonkeyPatch) -> None:
 | 
				
			||||||
        # Set limit to trigger exception on the test file
 | 
					        # Set limit to trigger exception on the test file
 | 
				
			||||||
        Image.MAX_IMAGE_PIXELS = 4 * 64 * 128
 | 
					        monkeypatch.setattr(Image, "MAX_IMAGE_PIXELS", 4 * 64 * 128)
 | 
				
			||||||
        assert Image.MAX_IMAGE_PIXELS == 4 * 64 * 128
 | 
					        assert Image.MAX_IMAGE_PIXELS == 4 * 64 * 128
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        with pytest.raises(Image.DecompressionBombError):
 | 
					        with pytest.raises(Image.DecompressionBombError):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -36,9 +36,10 @@ def test_version() -> None:
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            assert function(name) == version
 | 
					            assert function(name) == version
 | 
				
			||||||
            if name != "PIL":
 | 
					            if name != "PIL":
 | 
				
			||||||
                if name == "zlib" and version is not None:
 | 
					                if version is not None:
 | 
				
			||||||
 | 
					                    if name == "zlib" and features.check_feature("zlib_ng"):
 | 
				
			||||||
                        version = re.sub(".zlib-ng$", "", version)
 | 
					                        version = re.sub(".zlib-ng$", "", version)
 | 
				
			||||||
                elif name == "libtiff" and version is not None:
 | 
					                    elif name == "libtiff":
 | 
				
			||||||
                        version = re.sub("t$", "", version)
 | 
					                        version = re.sub("t$", "", version)
 | 
				
			||||||
                assert version is None or re.search(r"\d+(\.\d+)*$", version)
 | 
					                assert version is None or re.search(r"\d+(\.\d+)*$", version)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,6 +12,7 @@ from PIL import Image, ImageSequence, PngImagePlugin
 | 
				
			||||||
# (referenced from https://wiki.mozilla.org/APNG_Specification)
 | 
					# (referenced from https://wiki.mozilla.org/APNG_Specification)
 | 
				
			||||||
def test_apng_basic() -> None:
 | 
					def test_apng_basic() -> None:
 | 
				
			||||||
    with Image.open("Tests/images/apng/single_frame.png") as im:
 | 
					    with Image.open("Tests/images/apng/single_frame.png") as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, PngImagePlugin.PngImageFile)
 | 
				
			||||||
        assert not im.is_animated
 | 
					        assert not im.is_animated
 | 
				
			||||||
        assert im.n_frames == 1
 | 
					        assert im.n_frames == 1
 | 
				
			||||||
        assert im.get_format_mimetype() == "image/apng"
 | 
					        assert im.get_format_mimetype() == "image/apng"
 | 
				
			||||||
| 
						 | 
					@ -20,6 +21,7 @@ def test_apng_basic() -> None:
 | 
				
			||||||
        assert im.getpixel((64, 32)) == (0, 255, 0, 255)
 | 
					        assert im.getpixel((64, 32)) == (0, 255, 0, 255)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open("Tests/images/apng/single_frame_default.png") as im:
 | 
					    with Image.open("Tests/images/apng/single_frame_default.png") as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, PngImagePlugin.PngImageFile)
 | 
				
			||||||
        assert im.is_animated
 | 
					        assert im.is_animated
 | 
				
			||||||
        assert im.n_frames == 2
 | 
					        assert im.n_frames == 2
 | 
				
			||||||
        assert im.get_format_mimetype() == "image/apng"
 | 
					        assert im.get_format_mimetype() == "image/apng"
 | 
				
			||||||
| 
						 | 
					@ -34,8 +36,11 @@ def test_apng_basic() -> None:
 | 
				
			||||||
        with pytest.raises(EOFError):
 | 
					        with pytest.raises(EOFError):
 | 
				
			||||||
            im.seek(2)
 | 
					            im.seek(2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # test rewind support
 | 
					 | 
				
			||||||
        im.seek(0)
 | 
					        im.seek(0)
 | 
				
			||||||
 | 
					        with pytest.raises(ValueError, match="cannot seek to frame 2"):
 | 
				
			||||||
 | 
					            im._seek(2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # test rewind support
 | 
				
			||||||
        assert im.getpixel((0, 0)) == (255, 0, 0, 255)
 | 
					        assert im.getpixel((0, 0)) == (255, 0, 0, 255)
 | 
				
			||||||
        assert im.getpixel((64, 32)) == (255, 0, 0, 255)
 | 
					        assert im.getpixel((64, 32)) == (255, 0, 0, 255)
 | 
				
			||||||
        im.seek(1)
 | 
					        im.seek(1)
 | 
				
			||||||
| 
						 | 
					@ -49,6 +54,7 @@ def test_apng_basic() -> None:
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
def test_apng_fdat(filename: str) -> None:
 | 
					def test_apng_fdat(filename: str) -> None:
 | 
				
			||||||
    with Image.open(filename) as im:
 | 
					    with Image.open(filename) as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, PngImagePlugin.PngImageFile)
 | 
				
			||||||
        im.seek(im.n_frames - 1)
 | 
					        im.seek(im.n_frames - 1)
 | 
				
			||||||
        assert im.getpixel((0, 0)) == (0, 255, 0, 255)
 | 
					        assert im.getpixel((0, 0)) == (0, 255, 0, 255)
 | 
				
			||||||
        assert im.getpixel((64, 32)) == (0, 255, 0, 255)
 | 
					        assert im.getpixel((64, 32)) == (0, 255, 0, 255)
 | 
				
			||||||
| 
						 | 
					@ -56,31 +62,37 @@ def test_apng_fdat(filename: str) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_apng_dispose() -> None:
 | 
					def test_apng_dispose() -> None:
 | 
				
			||||||
    with Image.open("Tests/images/apng/dispose_op_none.png") as im:
 | 
					    with Image.open("Tests/images/apng/dispose_op_none.png") as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, PngImagePlugin.PngImageFile)
 | 
				
			||||||
        im.seek(im.n_frames - 1)
 | 
					        im.seek(im.n_frames - 1)
 | 
				
			||||||
        assert im.getpixel((0, 0)) == (0, 255, 0, 255)
 | 
					        assert im.getpixel((0, 0)) == (0, 255, 0, 255)
 | 
				
			||||||
        assert im.getpixel((64, 32)) == (0, 255, 0, 255)
 | 
					        assert im.getpixel((64, 32)) == (0, 255, 0, 255)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open("Tests/images/apng/dispose_op_background.png") as im:
 | 
					    with Image.open("Tests/images/apng/dispose_op_background.png") as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, PngImagePlugin.PngImageFile)
 | 
				
			||||||
        im.seek(im.n_frames - 1)
 | 
					        im.seek(im.n_frames - 1)
 | 
				
			||||||
        assert im.getpixel((0, 0)) == (0, 0, 0, 0)
 | 
					        assert im.getpixel((0, 0)) == (0, 0, 0, 0)
 | 
				
			||||||
        assert im.getpixel((64, 32)) == (0, 0, 0, 0)
 | 
					        assert im.getpixel((64, 32)) == (0, 0, 0, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open("Tests/images/apng/dispose_op_background_final.png") as im:
 | 
					    with Image.open("Tests/images/apng/dispose_op_background_final.png") as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, PngImagePlugin.PngImageFile)
 | 
				
			||||||
        im.seek(im.n_frames - 1)
 | 
					        im.seek(im.n_frames - 1)
 | 
				
			||||||
        assert im.getpixel((0, 0)) == (0, 255, 0, 255)
 | 
					        assert im.getpixel((0, 0)) == (0, 255, 0, 255)
 | 
				
			||||||
        assert im.getpixel((64, 32)) == (0, 255, 0, 255)
 | 
					        assert im.getpixel((64, 32)) == (0, 255, 0, 255)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open("Tests/images/apng/dispose_op_previous.png") as im:
 | 
					    with Image.open("Tests/images/apng/dispose_op_previous.png") as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, PngImagePlugin.PngImageFile)
 | 
				
			||||||
        im.seek(im.n_frames - 1)
 | 
					        im.seek(im.n_frames - 1)
 | 
				
			||||||
        assert im.getpixel((0, 0)) == (0, 255, 0, 255)
 | 
					        assert im.getpixel((0, 0)) == (0, 255, 0, 255)
 | 
				
			||||||
        assert im.getpixel((64, 32)) == (0, 255, 0, 255)
 | 
					        assert im.getpixel((64, 32)) == (0, 255, 0, 255)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open("Tests/images/apng/dispose_op_previous_final.png") as im:
 | 
					    with Image.open("Tests/images/apng/dispose_op_previous_final.png") as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, PngImagePlugin.PngImageFile)
 | 
				
			||||||
        im.seek(im.n_frames - 1)
 | 
					        im.seek(im.n_frames - 1)
 | 
				
			||||||
        assert im.getpixel((0, 0)) == (0, 255, 0, 255)
 | 
					        assert im.getpixel((0, 0)) == (0, 255, 0, 255)
 | 
				
			||||||
        assert im.getpixel((64, 32)) == (0, 255, 0, 255)
 | 
					        assert im.getpixel((64, 32)) == (0, 255, 0, 255)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open("Tests/images/apng/dispose_op_previous_first.png") as im:
 | 
					    with Image.open("Tests/images/apng/dispose_op_previous_first.png") as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, PngImagePlugin.PngImageFile)
 | 
				
			||||||
        im.seek(im.n_frames - 1)
 | 
					        im.seek(im.n_frames - 1)
 | 
				
			||||||
        assert im.getpixel((0, 0)) == (0, 0, 0, 0)
 | 
					        assert im.getpixel((0, 0)) == (0, 0, 0, 0)
 | 
				
			||||||
        assert im.getpixel((64, 32)) == (0, 0, 0, 0)
 | 
					        assert im.getpixel((64, 32)) == (0, 0, 0, 0)
 | 
				
			||||||
| 
						 | 
					@ -88,21 +100,25 @@ def test_apng_dispose() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_apng_dispose_region() -> None:
 | 
					def test_apng_dispose_region() -> None:
 | 
				
			||||||
    with Image.open("Tests/images/apng/dispose_op_none_region.png") as im:
 | 
					    with Image.open("Tests/images/apng/dispose_op_none_region.png") as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, PngImagePlugin.PngImageFile)
 | 
				
			||||||
        im.seek(im.n_frames - 1)
 | 
					        im.seek(im.n_frames - 1)
 | 
				
			||||||
        assert im.getpixel((0, 0)) == (0, 255, 0, 255)
 | 
					        assert im.getpixel((0, 0)) == (0, 255, 0, 255)
 | 
				
			||||||
        assert im.getpixel((64, 32)) == (0, 255, 0, 255)
 | 
					        assert im.getpixel((64, 32)) == (0, 255, 0, 255)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open("Tests/images/apng/dispose_op_background_before_region.png") as im:
 | 
					    with Image.open("Tests/images/apng/dispose_op_background_before_region.png") as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, PngImagePlugin.PngImageFile)
 | 
				
			||||||
        im.seek(im.n_frames - 1)
 | 
					        im.seek(im.n_frames - 1)
 | 
				
			||||||
        assert im.getpixel((0, 0)) == (0, 0, 0, 0)
 | 
					        assert im.getpixel((0, 0)) == (0, 0, 0, 0)
 | 
				
			||||||
        assert im.getpixel((64, 32)) == (0, 0, 0, 0)
 | 
					        assert im.getpixel((64, 32)) == (0, 0, 0, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open("Tests/images/apng/dispose_op_background_region.png") as im:
 | 
					    with Image.open("Tests/images/apng/dispose_op_background_region.png") as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, PngImagePlugin.PngImageFile)
 | 
				
			||||||
        im.seek(im.n_frames - 1)
 | 
					        im.seek(im.n_frames - 1)
 | 
				
			||||||
        assert im.getpixel((0, 0)) == (0, 0, 255, 255)
 | 
					        assert im.getpixel((0, 0)) == (0, 0, 255, 255)
 | 
				
			||||||
        assert im.getpixel((64, 32)) == (0, 0, 0, 0)
 | 
					        assert im.getpixel((64, 32)) == (0, 0, 0, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open("Tests/images/apng/dispose_op_previous_region.png") as im:
 | 
					    with Image.open("Tests/images/apng/dispose_op_previous_region.png") as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, PngImagePlugin.PngImageFile)
 | 
				
			||||||
        im.seek(im.n_frames - 1)
 | 
					        im.seek(im.n_frames - 1)
 | 
				
			||||||
        assert im.getpixel((0, 0)) == (0, 255, 0, 255)
 | 
					        assert im.getpixel((0, 0)) == (0, 255, 0, 255)
 | 
				
			||||||
        assert im.getpixel((64, 32)) == (0, 255, 0, 255)
 | 
					        assert im.getpixel((64, 32)) == (0, 255, 0, 255)
 | 
				
			||||||
| 
						 | 
					@ -129,6 +145,7 @@ def test_apng_dispose_op_previous_frame() -> None:
 | 
				
			||||||
    #     ],
 | 
					    #     ],
 | 
				
			||||||
    # )
 | 
					    # )
 | 
				
			||||||
    with Image.open("Tests/images/apng/dispose_op_previous_frame.png") as im:
 | 
					    with Image.open("Tests/images/apng/dispose_op_previous_frame.png") as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, PngImagePlugin.PngImageFile)
 | 
				
			||||||
        im.seek(im.n_frames - 1)
 | 
					        im.seek(im.n_frames - 1)
 | 
				
			||||||
        assert im.getpixel((0, 0)) == (255, 0, 0, 255)
 | 
					        assert im.getpixel((0, 0)) == (255, 0, 0, 255)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -142,26 +159,31 @@ def test_apng_dispose_op_background_p_mode() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_apng_blend() -> None:
 | 
					def test_apng_blend() -> None:
 | 
				
			||||||
    with Image.open("Tests/images/apng/blend_op_source_solid.png") as im:
 | 
					    with Image.open("Tests/images/apng/blend_op_source_solid.png") as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, PngImagePlugin.PngImageFile)
 | 
				
			||||||
        im.seek(im.n_frames - 1)
 | 
					        im.seek(im.n_frames - 1)
 | 
				
			||||||
        assert im.getpixel((0, 0)) == (0, 255, 0, 255)
 | 
					        assert im.getpixel((0, 0)) == (0, 255, 0, 255)
 | 
				
			||||||
        assert im.getpixel((64, 32)) == (0, 255, 0, 255)
 | 
					        assert im.getpixel((64, 32)) == (0, 255, 0, 255)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open("Tests/images/apng/blend_op_source_transparent.png") as im:
 | 
					    with Image.open("Tests/images/apng/blend_op_source_transparent.png") as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, PngImagePlugin.PngImageFile)
 | 
				
			||||||
        im.seek(im.n_frames - 1)
 | 
					        im.seek(im.n_frames - 1)
 | 
				
			||||||
        assert im.getpixel((0, 0)) == (0, 0, 0, 0)
 | 
					        assert im.getpixel((0, 0)) == (0, 0, 0, 0)
 | 
				
			||||||
        assert im.getpixel((64, 32)) == (0, 0, 0, 0)
 | 
					        assert im.getpixel((64, 32)) == (0, 0, 0, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open("Tests/images/apng/blend_op_source_near_transparent.png") as im:
 | 
					    with Image.open("Tests/images/apng/blend_op_source_near_transparent.png") as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, PngImagePlugin.PngImageFile)
 | 
				
			||||||
        im.seek(im.n_frames - 1)
 | 
					        im.seek(im.n_frames - 1)
 | 
				
			||||||
        assert im.getpixel((0, 0)) == (0, 255, 0, 2)
 | 
					        assert im.getpixel((0, 0)) == (0, 255, 0, 2)
 | 
				
			||||||
        assert im.getpixel((64, 32)) == (0, 255, 0, 2)
 | 
					        assert im.getpixel((64, 32)) == (0, 255, 0, 2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open("Tests/images/apng/blend_op_over.png") as im:
 | 
					    with Image.open("Tests/images/apng/blend_op_over.png") as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, PngImagePlugin.PngImageFile)
 | 
				
			||||||
        im.seek(im.n_frames - 1)
 | 
					        im.seek(im.n_frames - 1)
 | 
				
			||||||
        assert im.getpixel((0, 0)) == (0, 255, 0, 255)
 | 
					        assert im.getpixel((0, 0)) == (0, 255, 0, 255)
 | 
				
			||||||
        assert im.getpixel((64, 32)) == (0, 255, 0, 255)
 | 
					        assert im.getpixel((64, 32)) == (0, 255, 0, 255)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open("Tests/images/apng/blend_op_over_near_transparent.png") as im:
 | 
					    with Image.open("Tests/images/apng/blend_op_over_near_transparent.png") as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, PngImagePlugin.PngImageFile)
 | 
				
			||||||
        im.seek(im.n_frames - 1)
 | 
					        im.seek(im.n_frames - 1)
 | 
				
			||||||
        assert im.getpixel((0, 0)) == (0, 255, 0, 97)
 | 
					        assert im.getpixel((0, 0)) == (0, 255, 0, 97)
 | 
				
			||||||
        assert im.getpixel((64, 32)) == (0, 255, 0, 255)
 | 
					        assert im.getpixel((64, 32)) == (0, 255, 0, 255)
 | 
				
			||||||
| 
						 | 
					@ -175,6 +197,7 @@ def test_apng_blend_transparency() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_apng_chunk_order() -> None:
 | 
					def test_apng_chunk_order() -> None:
 | 
				
			||||||
    with Image.open("Tests/images/apng/fctl_actl.png") as im:
 | 
					    with Image.open("Tests/images/apng/fctl_actl.png") as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, PngImagePlugin.PngImageFile)
 | 
				
			||||||
        im.seek(im.n_frames - 1)
 | 
					        im.seek(im.n_frames - 1)
 | 
				
			||||||
        assert im.getpixel((0, 0)) == (0, 255, 0, 255)
 | 
					        assert im.getpixel((0, 0)) == (0, 255, 0, 255)
 | 
				
			||||||
        assert im.getpixel((64, 32)) == (0, 255, 0, 255)
 | 
					        assert im.getpixel((64, 32)) == (0, 255, 0, 255)
 | 
				
			||||||
| 
						 | 
					@ -230,24 +253,28 @@ def test_apng_num_plays() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_apng_mode() -> None:
 | 
					def test_apng_mode() -> None:
 | 
				
			||||||
    with Image.open("Tests/images/apng/mode_16bit.png") as im:
 | 
					    with Image.open("Tests/images/apng/mode_16bit.png") as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, PngImagePlugin.PngImageFile)
 | 
				
			||||||
        assert im.mode == "RGBA"
 | 
					        assert im.mode == "RGBA"
 | 
				
			||||||
        im.seek(im.n_frames - 1)
 | 
					        im.seek(im.n_frames - 1)
 | 
				
			||||||
        assert im.getpixel((0, 0)) == (0, 0, 128, 191)
 | 
					        assert im.getpixel((0, 0)) == (0, 0, 128, 191)
 | 
				
			||||||
        assert im.getpixel((64, 32)) == (0, 0, 128, 191)
 | 
					        assert im.getpixel((64, 32)) == (0, 0, 128, 191)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open("Tests/images/apng/mode_grayscale.png") as im:
 | 
					    with Image.open("Tests/images/apng/mode_grayscale.png") as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, PngImagePlugin.PngImageFile)
 | 
				
			||||||
        assert im.mode == "L"
 | 
					        assert im.mode == "L"
 | 
				
			||||||
        im.seek(im.n_frames - 1)
 | 
					        im.seek(im.n_frames - 1)
 | 
				
			||||||
        assert im.getpixel((0, 0)) == 128
 | 
					        assert im.getpixel((0, 0)) == 128
 | 
				
			||||||
        assert im.getpixel((64, 32)) == 255
 | 
					        assert im.getpixel((64, 32)) == 255
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open("Tests/images/apng/mode_grayscale_alpha.png") as im:
 | 
					    with Image.open("Tests/images/apng/mode_grayscale_alpha.png") as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, PngImagePlugin.PngImageFile)
 | 
				
			||||||
        assert im.mode == "LA"
 | 
					        assert im.mode == "LA"
 | 
				
			||||||
        im.seek(im.n_frames - 1)
 | 
					        im.seek(im.n_frames - 1)
 | 
				
			||||||
        assert im.getpixel((0, 0)) == (128, 191)
 | 
					        assert im.getpixel((0, 0)) == (128, 191)
 | 
				
			||||||
        assert im.getpixel((64, 32)) == (128, 191)
 | 
					        assert im.getpixel((64, 32)) == (128, 191)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open("Tests/images/apng/mode_palette.png") as im:
 | 
					    with Image.open("Tests/images/apng/mode_palette.png") as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, PngImagePlugin.PngImageFile)
 | 
				
			||||||
        assert im.mode == "P"
 | 
					        assert im.mode == "P"
 | 
				
			||||||
        im.seek(im.n_frames - 1)
 | 
					        im.seek(im.n_frames - 1)
 | 
				
			||||||
        im = im.convert("RGB")
 | 
					        im = im.convert("RGB")
 | 
				
			||||||
| 
						 | 
					@ -255,6 +282,7 @@ def test_apng_mode() -> None:
 | 
				
			||||||
        assert im.getpixel((64, 32)) == (0, 255, 0)
 | 
					        assert im.getpixel((64, 32)) == (0, 255, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open("Tests/images/apng/mode_palette_alpha.png") as im:
 | 
					    with Image.open("Tests/images/apng/mode_palette_alpha.png") as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, PngImagePlugin.PngImageFile)
 | 
				
			||||||
        assert im.mode == "P"
 | 
					        assert im.mode == "P"
 | 
				
			||||||
        im.seek(im.n_frames - 1)
 | 
					        im.seek(im.n_frames - 1)
 | 
				
			||||||
        im = im.convert("RGBA")
 | 
					        im = im.convert("RGBA")
 | 
				
			||||||
| 
						 | 
					@ -262,6 +290,7 @@ def test_apng_mode() -> None:
 | 
				
			||||||
        assert im.getpixel((64, 32)) == (0, 255, 0, 255)
 | 
					        assert im.getpixel((64, 32)) == (0, 255, 0, 255)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open("Tests/images/apng/mode_palette_1bit_alpha.png") as im:
 | 
					    with Image.open("Tests/images/apng/mode_palette_1bit_alpha.png") as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, PngImagePlugin.PngImageFile)
 | 
				
			||||||
        assert im.mode == "P"
 | 
					        assert im.mode == "P"
 | 
				
			||||||
        im.seek(im.n_frames - 1)
 | 
					        im.seek(im.n_frames - 1)
 | 
				
			||||||
        im = im.convert("RGBA")
 | 
					        im = im.convert("RGBA")
 | 
				
			||||||
| 
						 | 
					@ -271,25 +300,31 @@ def test_apng_mode() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_apng_chunk_errors() -> None:
 | 
					def test_apng_chunk_errors() -> None:
 | 
				
			||||||
    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 isinstance(im, PngImagePlugin.PngImageFile)
 | 
				
			||||||
        assert not im.is_animated
 | 
					        assert not im.is_animated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with pytest.warns(UserWarning):
 | 
					    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 isinstance(im, PngImagePlugin.PngImageFile)
 | 
				
			||||||
        assert not im.is_animated
 | 
					        assert not im.is_animated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    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 isinstance(im, PngImagePlugin.PngImageFile)
 | 
				
			||||||
        assert not im.is_animated
 | 
					        assert not im.is_animated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open("Tests/images/apng/chunk_no_fctl.png") as im:
 | 
					    with Image.open("Tests/images/apng/chunk_no_fctl.png") as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, PngImagePlugin.PngImageFile)
 | 
				
			||||||
        with pytest.raises(SyntaxError):
 | 
					        with pytest.raises(SyntaxError):
 | 
				
			||||||
            im.seek(im.n_frames - 1)
 | 
					            im.seek(im.n_frames - 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open("Tests/images/apng/chunk_repeat_fctl.png") as im:
 | 
					    with Image.open("Tests/images/apng/chunk_repeat_fctl.png") as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, PngImagePlugin.PngImageFile)
 | 
				
			||||||
        with pytest.raises(SyntaxError):
 | 
					        with pytest.raises(SyntaxError):
 | 
				
			||||||
            im.seek(im.n_frames - 1)
 | 
					            im.seek(im.n_frames - 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open("Tests/images/apng/chunk_no_fdat.png") as im:
 | 
					    with Image.open("Tests/images/apng/chunk_no_fdat.png") as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, PngImagePlugin.PngImageFile)
 | 
				
			||||||
        with pytest.raises(SyntaxError):
 | 
					        with pytest.raises(SyntaxError):
 | 
				
			||||||
            im.seek(im.n_frames - 1)
 | 
					            im.seek(im.n_frames - 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -297,31 +332,31 @@ def test_apng_chunk_errors() -> None:
 | 
				
			||||||
def test_apng_syntax_errors() -> None:
 | 
					def test_apng_syntax_errors() -> None:
 | 
				
			||||||
    with pytest.warns(UserWarning):
 | 
					    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 isinstance(im, PngImagePlugin.PngImageFile)
 | 
				
			||||||
            assert not im.is_animated
 | 
					            assert not im.is_animated
 | 
				
			||||||
            with pytest.raises(OSError):
 | 
					            with pytest.raises(OSError):
 | 
				
			||||||
                im.load()
 | 
					                im.load()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with pytest.warns(UserWarning):
 | 
					    with pytest.warns(UserWarning):
 | 
				
			||||||
        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 isinstance(im, PngImagePlugin.PngImageFile)
 | 
				
			||||||
            assert not im.is_animated
 | 
					            assert not im.is_animated
 | 
				
			||||||
            im.load()
 | 
					            im.load()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # we can handle this case gracefully
 | 
					    # we can handle this case gracefully
 | 
				
			||||||
    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:
 | 
				
			||||||
        try:
 | 
					        assert isinstance(im, PngImagePlugin.PngImageFile)
 | 
				
			||||||
        im.seek(im.n_frames - 1)
 | 
					        im.seek(im.n_frames - 1)
 | 
				
			||||||
        except Exception as e:
 | 
					 | 
				
			||||||
            exception = e
 | 
					 | 
				
			||||||
        assert exception is None
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with pytest.raises(OSError):
 | 
					    with pytest.raises(OSError):
 | 
				
			||||||
        with Image.open("Tests/images/apng/syntax_num_frames_high.png") as im:
 | 
					        with Image.open("Tests/images/apng/syntax_num_frames_high.png") as im:
 | 
				
			||||||
 | 
					            assert isinstance(im, PngImagePlugin.PngImageFile)
 | 
				
			||||||
            im.seek(im.n_frames - 1)
 | 
					            im.seek(im.n_frames - 1)
 | 
				
			||||||
            im.load()
 | 
					            im.load()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with pytest.warns(UserWarning):
 | 
					    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 isinstance(im, PngImagePlugin.PngImageFile)
 | 
				
			||||||
            assert not im.is_animated
 | 
					            assert not im.is_animated
 | 
				
			||||||
            im.load()
 | 
					            im.load()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -341,16 +376,18 @@ def test_apng_syntax_errors() -> None:
 | 
				
			||||||
def test_apng_sequence_errors(test_file: str) -> None:
 | 
					def test_apng_sequence_errors(test_file: str) -> None:
 | 
				
			||||||
    with pytest.raises(SyntaxError):
 | 
					    with pytest.raises(SyntaxError):
 | 
				
			||||||
        with Image.open(f"Tests/images/apng/{test_file}") as im:
 | 
					        with Image.open(f"Tests/images/apng/{test_file}") as im:
 | 
				
			||||||
 | 
					            assert isinstance(im, PngImagePlugin.PngImageFile)
 | 
				
			||||||
            im.seek(im.n_frames - 1)
 | 
					            im.seek(im.n_frames - 1)
 | 
				
			||||||
            im.load()
 | 
					            im.load()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_apng_save(tmp_path: Path) -> None:
 | 
					def test_apng_save(tmp_path: Path) -> None:
 | 
				
			||||||
    with Image.open("Tests/images/apng/single_frame.png") as im:
 | 
					    with Image.open("Tests/images/apng/single_frame.png") as im:
 | 
				
			||||||
        test_file = str(tmp_path / "temp.png")
 | 
					        test_file = tmp_path / "temp.png"
 | 
				
			||||||
        im.save(test_file, save_all=True)
 | 
					        im.save(test_file, save_all=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open(test_file) as im:
 | 
					    with Image.open(test_file) as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, PngImagePlugin.PngImageFile)
 | 
				
			||||||
        im.load()
 | 
					        im.load()
 | 
				
			||||||
        assert not im.is_animated
 | 
					        assert not im.is_animated
 | 
				
			||||||
        assert im.n_frames == 1
 | 
					        assert im.n_frames == 1
 | 
				
			||||||
| 
						 | 
					@ -366,6 +403,7 @@ def test_apng_save(tmp_path: Path) -> None:
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open(test_file) as im:
 | 
					    with Image.open(test_file) as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, PngImagePlugin.PngImageFile)
 | 
				
			||||||
        im.load()
 | 
					        im.load()
 | 
				
			||||||
        assert im.is_animated
 | 
					        assert im.is_animated
 | 
				
			||||||
        assert im.n_frames == 2
 | 
					        assert im.n_frames == 2
 | 
				
			||||||
| 
						 | 
					@ -377,7 +415,7 @@ def test_apng_save(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_apng_save_alpha(tmp_path: Path) -> None:
 | 
					def test_apng_save_alpha(tmp_path: Path) -> None:
 | 
				
			||||||
    test_file = str(tmp_path / "temp.png")
 | 
					    test_file = tmp_path / "temp.png"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    im = Image.new("RGBA", (1, 1), (255, 0, 0, 255))
 | 
					    im = Image.new("RGBA", (1, 1), (255, 0, 0, 255))
 | 
				
			||||||
    im2 = Image.new("RGBA", (1, 1), (255, 0, 0, 127))
 | 
					    im2 = Image.new("RGBA", (1, 1), (255, 0, 0, 127))
 | 
				
			||||||
| 
						 | 
					@ -395,7 +433,7 @@ def test_apng_save_split_fdat(tmp_path: Path) -> None:
 | 
				
			||||||
    # frames with image data spanning multiple fdAT chunks (in this case
 | 
					    # frames with image data spanning multiple fdAT chunks (in this case
 | 
				
			||||||
    # both the default image and first animation frame will span multiple
 | 
					    # both the default image and first animation frame will span multiple
 | 
				
			||||||
    # data chunks)
 | 
					    # data chunks)
 | 
				
			||||||
    test_file = str(tmp_path / "temp.png")
 | 
					    test_file = tmp_path / "temp.png"
 | 
				
			||||||
    with Image.open("Tests/images/old-style-jpeg-compression.png") as im:
 | 
					    with Image.open("Tests/images/old-style-jpeg-compression.png") as im:
 | 
				
			||||||
        frames = [im.copy(), Image.new("RGBA", im.size, (255, 0, 0, 255))]
 | 
					        frames = [im.copy(), Image.new("RGBA", im.size, (255, 0, 0, 255))]
 | 
				
			||||||
        im.save(
 | 
					        im.save(
 | 
				
			||||||
| 
						 | 
					@ -405,17 +443,13 @@ def test_apng_save_split_fdat(tmp_path: Path) -> None:
 | 
				
			||||||
            append_images=frames,
 | 
					            append_images=frames,
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
    with Image.open(test_file) as im:
 | 
					    with Image.open(test_file) as im:
 | 
				
			||||||
        exception = None
 | 
					        assert isinstance(im, PngImagePlugin.PngImageFile)
 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
        im.seek(im.n_frames - 1)
 | 
					        im.seek(im.n_frames - 1)
 | 
				
			||||||
        im.load()
 | 
					        im.load()
 | 
				
			||||||
        except Exception as e:
 | 
					 | 
				
			||||||
            exception = e
 | 
					 | 
				
			||||||
        assert exception is None
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_apng_save_duration_loop(tmp_path: Path) -> None:
 | 
					def test_apng_save_duration_loop(tmp_path: Path) -> None:
 | 
				
			||||||
    test_file = str(tmp_path / "temp.png")
 | 
					    test_file = tmp_path / "temp.png"
 | 
				
			||||||
    with Image.open("Tests/images/apng/delay.png") as im:
 | 
					    with Image.open("Tests/images/apng/delay.png") as im:
 | 
				
			||||||
        frames = []
 | 
					        frames = []
 | 
				
			||||||
        durations = []
 | 
					        durations = []
 | 
				
			||||||
| 
						 | 
					@ -452,6 +486,7 @@ def test_apng_save_duration_loop(tmp_path: Path) -> None:
 | 
				
			||||||
        test_file, save_all=True, append_images=[frame, frame], duration=[500, 100, 150]
 | 
					        test_file, save_all=True, append_images=[frame, frame], duration=[500, 100, 150]
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    with Image.open(test_file) as im:
 | 
					    with Image.open(test_file) as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, PngImagePlugin.PngImageFile)
 | 
				
			||||||
        assert im.n_frames == 1
 | 
					        assert im.n_frames == 1
 | 
				
			||||||
        assert "duration" not in im.info
 | 
					        assert "duration" not in im.info
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -463,6 +498,7 @@ def test_apng_save_duration_loop(tmp_path: Path) -> None:
 | 
				
			||||||
        duration=[500, 100, 150],
 | 
					        duration=[500, 100, 150],
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    with Image.open(test_file) as im:
 | 
					    with Image.open(test_file) as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, PngImagePlugin.PngImageFile)
 | 
				
			||||||
        assert im.n_frames == 2
 | 
					        assert im.n_frames == 2
 | 
				
			||||||
        assert im.info["duration"] == 600
 | 
					        assert im.info["duration"] == 600
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -473,12 +509,13 @@ def test_apng_save_duration_loop(tmp_path: Path) -> None:
 | 
				
			||||||
    frame.info["duration"] = 300
 | 
					    frame.info["duration"] = 300
 | 
				
			||||||
    frame.save(test_file, save_all=True, append_images=[frame, different_frame])
 | 
					    frame.save(test_file, save_all=True, append_images=[frame, different_frame])
 | 
				
			||||||
    with Image.open(test_file) as im:
 | 
					    with Image.open(test_file) as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, PngImagePlugin.PngImageFile)
 | 
				
			||||||
        assert im.n_frames == 2
 | 
					        assert im.n_frames == 2
 | 
				
			||||||
        assert im.info["duration"] == 600
 | 
					        assert im.info["duration"] == 600
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_apng_save_disposal(tmp_path: Path) -> None:
 | 
					def test_apng_save_disposal(tmp_path: Path) -> None:
 | 
				
			||||||
    test_file = str(tmp_path / "temp.png")
 | 
					    test_file = tmp_path / "temp.png"
 | 
				
			||||||
    size = (128, 64)
 | 
					    size = (128, 64)
 | 
				
			||||||
    red = Image.new("RGBA", size, (255, 0, 0, 255))
 | 
					    red = Image.new("RGBA", size, (255, 0, 0, 255))
 | 
				
			||||||
    green = Image.new("RGBA", size, (0, 255, 0, 255))
 | 
					    green = Image.new("RGBA", size, (0, 255, 0, 255))
 | 
				
			||||||
| 
						 | 
					@ -579,7 +616,7 @@ def test_apng_save_disposal(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_apng_save_disposal_previous(tmp_path: Path) -> None:
 | 
					def test_apng_save_disposal_previous(tmp_path: Path) -> None:
 | 
				
			||||||
    test_file = str(tmp_path / "temp.png")
 | 
					    test_file = tmp_path / "temp.png"
 | 
				
			||||||
    size = (128, 64)
 | 
					    size = (128, 64)
 | 
				
			||||||
    blue = Image.new("RGBA", size, (0, 0, 255, 255))
 | 
					    blue = Image.new("RGBA", size, (0, 0, 255, 255))
 | 
				
			||||||
    red = Image.new("RGBA", size, (255, 0, 0, 255))
 | 
					    red = Image.new("RGBA", size, (255, 0, 0, 255))
 | 
				
			||||||
| 
						 | 
					@ -601,7 +638,7 @@ def test_apng_save_disposal_previous(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_apng_save_blend(tmp_path: Path) -> None:
 | 
					def test_apng_save_blend(tmp_path: Path) -> None:
 | 
				
			||||||
    test_file = str(tmp_path / "temp.png")
 | 
					    test_file = tmp_path / "temp.png"
 | 
				
			||||||
    size = (128, 64)
 | 
					    size = (128, 64)
 | 
				
			||||||
    red = Image.new("RGBA", size, (255, 0, 0, 255))
 | 
					    red = Image.new("RGBA", size, (255, 0, 0, 255))
 | 
				
			||||||
    green = Image.new("RGBA", size, (0, 255, 0, 255))
 | 
					    green = Image.new("RGBA", size, (0, 255, 0, 255))
 | 
				
			||||||
| 
						 | 
					@ -669,7 +706,7 @@ def test_apng_save_blend(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_apng_save_size(tmp_path: Path) -> None:
 | 
					def test_apng_save_size(tmp_path: Path) -> None:
 | 
				
			||||||
    test_file = str(tmp_path / "temp.png")
 | 
					    test_file = tmp_path / "temp.png"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    im = Image.new("L", (100, 100))
 | 
					    im = Image.new("L", (100, 100))
 | 
				
			||||||
    im.save(test_file, save_all=True, append_images=[Image.new("L", (200, 200))])
 | 
					    im.save(test_file, save_all=True, append_images=[Image.new("L", (200, 200))])
 | 
				
			||||||
| 
						 | 
					@ -693,7 +730,7 @@ def test_seek_after_close() -> None:
 | 
				
			||||||
def test_different_modes_in_later_frames(
 | 
					def test_different_modes_in_later_frames(
 | 
				
			||||||
    mode: str, default_image: bool, duplicate: bool, tmp_path: Path
 | 
					    mode: str, default_image: bool, duplicate: bool, tmp_path: Path
 | 
				
			||||||
) -> None:
 | 
					) -> None:
 | 
				
			||||||
    test_file = str(tmp_path / "temp.png")
 | 
					    test_file = tmp_path / "temp.png"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    im = Image.new("L", (1, 1))
 | 
					    im = Image.new("L", (1, 1))
 | 
				
			||||||
    im.save(
 | 
					    im.save(
 | 
				
			||||||
| 
						 | 
					@ -707,7 +744,7 @@ def test_different_modes_in_later_frames(
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_different_durations(tmp_path: Path) -> None:
 | 
					def test_different_durations(tmp_path: Path) -> None:
 | 
				
			||||||
    test_file = str(tmp_path / "temp.png")
 | 
					    test_file = tmp_path / "temp.png"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open("Tests/images/apng/different_durations.png") as im:
 | 
					    with Image.open("Tests/images/apng/different_durations.png") as im:
 | 
				
			||||||
        for _ in range(3):
 | 
					        for _ in range(3):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,7 +4,7 @@ from pathlib import Path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import pytest
 | 
					import pytest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from PIL import Image
 | 
					from PIL import BlpImagePlugin, Image
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .helper import (
 | 
					from .helper import (
 | 
				
			||||||
    assert_image_equal,
 | 
					    assert_image_equal,
 | 
				
			||||||
| 
						 | 
					@ -19,6 +19,7 @@ def test_load_blp1() -> None:
 | 
				
			||||||
        assert_image_equal_tofile(im, "Tests/images/blp/blp1_jpeg.png")
 | 
					        assert_image_equal_tofile(im, "Tests/images/blp/blp1_jpeg.png")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open("Tests/images/blp/blp1_jpeg2.blp") as im:
 | 
					    with Image.open("Tests/images/blp/blp1_jpeg2.blp") as im:
 | 
				
			||||||
 | 
					        assert im.mode == "RGBA"
 | 
				
			||||||
        im.load()
 | 
					        im.load()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -37,8 +38,15 @@ def test_load_blp2_dxt1a() -> None:
 | 
				
			||||||
        assert_image_equal_tofile(im, "Tests/images/blp/blp2_dxt1a.png")
 | 
					        assert_image_equal_tofile(im, "Tests/images/blp/blp2_dxt1a.png")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_invalid_file() -> None:
 | 
				
			||||||
 | 
					    invalid_file = "Tests/images/flower.jpg"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    with pytest.raises(BlpImagePlugin.BLPFormatError):
 | 
				
			||||||
 | 
					        BlpImagePlugin.BlpImageFile(invalid_file)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_save(tmp_path: Path) -> None:
 | 
					def test_save(tmp_path: Path) -> None:
 | 
				
			||||||
    f = str(tmp_path / "temp.blp")
 | 
					    f = tmp_path / "temp.blp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for version in ("BLP1", "BLP2"):
 | 
					    for version in ("BLP1", "BLP2"):
 | 
				
			||||||
        im = hopper("P")
 | 
					        im = hopper("P")
 | 
				
			||||||
| 
						 | 
					@ -48,7 +56,7 @@ def test_save(tmp_path: Path) -> None:
 | 
				
			||||||
            assert_image_equal(im.convert("RGB"), reloaded)
 | 
					            assert_image_equal(im.convert("RGB"), reloaded)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        with Image.open("Tests/images/transparent.png") as im:
 | 
					        with Image.open("Tests/images/transparent.png") as im:
 | 
				
			||||||
            f = str(tmp_path / "temp.blp")
 | 
					            f = tmp_path / "temp.blp"
 | 
				
			||||||
            im.convert("P").save(f, blp_version=version)
 | 
					            im.convert("P").save(f, blp_version=version)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            with Image.open(f) as reloaded:
 | 
					            with Image.open(f) as reloaded:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,10 +15,11 @@ from .helper import (
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_sanity(tmp_path: Path) -> None:
 | 
					@pytest.mark.parametrize("mode", ("1", "L", "P", "RGB"))
 | 
				
			||||||
    def roundtrip(im: Image.Image) -> None:
 | 
					def test_sanity(mode: str, tmp_path: Path) -> None:
 | 
				
			||||||
        outfile = str(tmp_path / "temp.bmp")
 | 
					    outfile = tmp_path / "temp.bmp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    im = hopper(mode)
 | 
				
			||||||
    im.save(outfile, "BMP")
 | 
					    im.save(outfile, "BMP")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open(outfile) as reloaded:
 | 
					    with Image.open(outfile) as reloaded:
 | 
				
			||||||
| 
						 | 
					@ -28,13 +29,6 @@ def test_sanity(tmp_path: Path) -> None:
 | 
				
			||||||
        assert reloaded.format == "BMP"
 | 
					        assert reloaded.format == "BMP"
 | 
				
			||||||
        assert reloaded.get_format_mimetype() == "image/bmp"
 | 
					        assert reloaded.get_format_mimetype() == "image/bmp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    roundtrip(hopper())
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    roundtrip(hopper("1"))
 | 
					 | 
				
			||||||
    roundtrip(hopper("L"))
 | 
					 | 
				
			||||||
    roundtrip(hopper("P"))
 | 
					 | 
				
			||||||
    roundtrip(hopper("RGB"))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_invalid_file() -> None:
 | 
					def test_invalid_file() -> None:
 | 
				
			||||||
    with open("Tests/images/flower.jpg", "rb") as fp:
 | 
					    with open("Tests/images/flower.jpg", "rb") as fp:
 | 
				
			||||||
| 
						 | 
					@ -66,7 +60,7 @@ def test_small_palette(tmp_path: Path) -> None:
 | 
				
			||||||
    colors = [0, 0, 0, 125, 125, 125, 255, 255, 255]
 | 
					    colors = [0, 0, 0, 125, 125, 125, 255, 255, 255]
 | 
				
			||||||
    im.putpalette(colors)
 | 
					    im.putpalette(colors)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    out = str(tmp_path / "temp.bmp")
 | 
					    out = tmp_path / "temp.bmp"
 | 
				
			||||||
    im.save(out)
 | 
					    im.save(out)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open(out) as reloaded:
 | 
					    with Image.open(out) as reloaded:
 | 
				
			||||||
| 
						 | 
					@ -74,7 +68,7 @@ def test_small_palette(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_save_too_large(tmp_path: Path) -> None:
 | 
					def test_save_too_large(tmp_path: Path) -> None:
 | 
				
			||||||
    outfile = str(tmp_path / "temp.bmp")
 | 
					    outfile = tmp_path / "temp.bmp"
 | 
				
			||||||
    with Image.new("RGB", (1, 1)) as im:
 | 
					    with Image.new("RGB", (1, 1)) as im:
 | 
				
			||||||
        im._size = (37838, 37838)
 | 
					        im._size = (37838, 37838)
 | 
				
			||||||
        with pytest.raises(ValueError):
 | 
					        with pytest.raises(ValueError):
 | 
				
			||||||
| 
						 | 
					@ -96,7 +90,7 @@ def test_dpi() -> None:
 | 
				
			||||||
def test_save_bmp_with_dpi(tmp_path: Path) -> None:
 | 
					def test_save_bmp_with_dpi(tmp_path: Path) -> None:
 | 
				
			||||||
    # Test for #1301
 | 
					    # Test for #1301
 | 
				
			||||||
    # Arrange
 | 
					    # Arrange
 | 
				
			||||||
    outfile = str(tmp_path / "temp.jpg")
 | 
					    outfile = tmp_path / "temp.jpg"
 | 
				
			||||||
    with Image.open("Tests/images/hopper.bmp") as im:
 | 
					    with Image.open("Tests/images/hopper.bmp") as im:
 | 
				
			||||||
        assert im.info["dpi"] == (95.98654816726399, 95.98654816726399)
 | 
					        assert im.info["dpi"] == (95.98654816726399, 95.98654816726399)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -112,7 +106,7 @@ def test_save_bmp_with_dpi(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_save_float_dpi(tmp_path: Path) -> None:
 | 
					def test_save_float_dpi(tmp_path: Path) -> None:
 | 
				
			||||||
    outfile = str(tmp_path / "temp.bmp")
 | 
					    outfile = tmp_path / "temp.bmp"
 | 
				
			||||||
    with Image.open("Tests/images/hopper.bmp") as im:
 | 
					    with Image.open("Tests/images/hopper.bmp") as im:
 | 
				
			||||||
        im.save(outfile, dpi=(72.21216100543306, 72.21216100543306))
 | 
					        im.save(outfile, dpi=(72.21216100543306, 72.21216100543306))
 | 
				
			||||||
        with Image.open(outfile) as reloaded:
 | 
					        with Image.open(outfile) as reloaded:
 | 
				
			||||||
| 
						 | 
					@ -152,7 +146,7 @@ def test_dib_header_size(header_size: int, path: str) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_save_dib(tmp_path: Path) -> None:
 | 
					def test_save_dib(tmp_path: Path) -> None:
 | 
				
			||||||
    outfile = str(tmp_path / "temp.dib")
 | 
					    outfile = tmp_path / "temp.dib"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open("Tests/images/clipboard.dib") as im:
 | 
					    with Image.open("Tests/images/clipboard.dib") as im:
 | 
				
			||||||
        im.save(outfile)
 | 
					        im.save(outfile)
 | 
				
			||||||
| 
						 | 
					@ -230,3 +224,13 @@ def test_offset() -> None:
 | 
				
			||||||
    # to exclude the palette size from the pixel data offset
 | 
					    # to exclude the palette size from the pixel data offset
 | 
				
			||||||
    with Image.open("Tests/images/pal8_offset.bmp") as im:
 | 
					    with Image.open("Tests/images/pal8_offset.bmp") as im:
 | 
				
			||||||
        assert_image_equal_tofile(im, "Tests/images/bmp/g/pal8.bmp")
 | 
					        assert_image_equal_tofile(im, "Tests/images/bmp/g/pal8.bmp")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_use_raw_alpha(monkeypatch: pytest.MonkeyPatch) -> None:
 | 
				
			||||||
 | 
					    with Image.open("Tests/images/bmp/g/rgb32.bmp") as im:
 | 
				
			||||||
 | 
					        assert im.info["compression"] == BmpImagePlugin.BmpImageFile.COMPRESSIONS["RAW"]
 | 
				
			||||||
 | 
					        assert im.mode == "RGB"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    monkeypatch.setattr(BmpImagePlugin, "USE_RAW_ALPHA", True)
 | 
				
			||||||
 | 
					    with Image.open("Tests/images/bmp/g/rgb32.bmp") as im:
 | 
				
			||||||
 | 
					        assert im.mode == "RGBA"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -43,7 +43,7 @@ def test_load() -> None:
 | 
				
			||||||
def test_save(tmp_path: Path) -> None:
 | 
					def test_save(tmp_path: Path) -> None:
 | 
				
			||||||
    # Arrange
 | 
					    # Arrange
 | 
				
			||||||
    im = hopper()
 | 
					    im = hopper()
 | 
				
			||||||
    tmpfile = str(tmp_path / "temp.bufr")
 | 
					    tmpfile = tmp_path / "temp.bufr"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Act / Assert: stub cannot save without an implemented handler
 | 
					    # Act / Assert: stub cannot save without an implemented handler
 | 
				
			||||||
    with pytest.raises(OSError):
 | 
					    with pytest.raises(OSError):
 | 
				
			||||||
| 
						 | 
					@ -79,8 +79,8 @@ def test_handler(tmp_path: Path) -> None:
 | 
				
			||||||
        im.load()
 | 
					        im.load()
 | 
				
			||||||
        assert handler.is_loaded()
 | 
					        assert handler.is_loaded()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        temp_file = str(tmp_path / "temp.bufr")
 | 
					        temp_file = tmp_path / "temp.bufr"
 | 
				
			||||||
        im.save(temp_file)
 | 
					        im.save(temp_file)
 | 
				
			||||||
        assert handler.saved
 | 
					        assert handler.saved
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    BufrStubImagePlugin._handler = None
 | 
					    BufrStubImagePlugin.register_handler(None)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,8 +4,6 @@ import pytest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from PIL import ContainerIO, Image
 | 
					from PIL import ContainerIO, Image
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .helper import hopper
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
TEST_FILE = "Tests/images/dummy.container"
 | 
					TEST_FILE = "Tests/images/dummy.container"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,15 +13,15 @@ def test_sanity() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_isatty() -> None:
 | 
					def test_isatty() -> None:
 | 
				
			||||||
    with hopper() as im:
 | 
					    with open(TEST_FILE, "rb") as fh:
 | 
				
			||||||
        container = ContainerIO.ContainerIO(im, 0, 0)
 | 
					        container = ContainerIO.ContainerIO(fh, 0, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    assert container.isatty() is False
 | 
					    assert container.isatty() is False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_seekable() -> None:
 | 
					def test_seekable() -> None:
 | 
				
			||||||
    with hopper() as im:
 | 
					    with open(TEST_FILE, "rb") as fh:
 | 
				
			||||||
        container = ContainerIO.ContainerIO(im, 0, 0)
 | 
					        container = ContainerIO.ContainerIO(fh, 0, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    assert container.seekable() is True
 | 
					    assert container.seekable() is True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -26,12 +26,12 @@ def test_sanity() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@pytest.mark.skipif(is_pypy(), reason="Requires CPython")
 | 
					@pytest.mark.skipif(is_pypy(), reason="Requires CPython")
 | 
				
			||||||
def test_unclosed_file() -> None:
 | 
					def test_unclosed_file() -> None:
 | 
				
			||||||
    def open() -> None:
 | 
					    def open_test_image() -> None:
 | 
				
			||||||
        im = Image.open(TEST_FILE)
 | 
					        im = Image.open(TEST_FILE)
 | 
				
			||||||
        im.load()
 | 
					        im.load()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with pytest.warns(ResourceWarning):
 | 
					    with pytest.warns(ResourceWarning):
 | 
				
			||||||
        open()
 | 
					        open_test_image()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_closed_file() -> None:
 | 
					def test_closed_file() -> None:
 | 
				
			||||||
| 
						 | 
					@ -69,12 +69,14 @@ def test_tell() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_n_frames() -> None:
 | 
					def test_n_frames() -> None:
 | 
				
			||||||
    with Image.open(TEST_FILE) as im:
 | 
					    with Image.open(TEST_FILE) as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, DcxImagePlugin.DcxImageFile)
 | 
				
			||||||
        assert im.n_frames == 1
 | 
					        assert im.n_frames == 1
 | 
				
			||||||
        assert not im.is_animated
 | 
					        assert not im.is_animated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_eoferror() -> None:
 | 
					def test_eoferror() -> None:
 | 
				
			||||||
    with Image.open(TEST_FILE) as im:
 | 
					    with Image.open(TEST_FILE) as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, DcxImagePlugin.DcxImageFile)
 | 
				
			||||||
        n_frames = im.n_frames
 | 
					        n_frames = im.n_frames
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Test seeking past the last frame
 | 
					        # Test seeking past the last frame
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,7 +9,13 @@ import pytest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from PIL import DdsImagePlugin, Image
 | 
					from PIL import DdsImagePlugin, Image
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .helper import assert_image_equal, assert_image_equal_tofile, hopper
 | 
					from .helper import (
 | 
				
			||||||
 | 
					    assert_image_equal,
 | 
				
			||||||
 | 
					    assert_image_equal_tofile,
 | 
				
			||||||
 | 
					    assert_image_similar,
 | 
				
			||||||
 | 
					    assert_image_similar_tofile,
 | 
				
			||||||
 | 
					    hopper,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TEST_FILE_DXT1 = "Tests/images/dxt1-rgb-4bbp-noalpha_MipMaps-1.dds"
 | 
					TEST_FILE_DXT1 = "Tests/images/dxt1-rgb-4bbp-noalpha_MipMaps-1.dds"
 | 
				
			||||||
TEST_FILE_DXT3 = "Tests/images/dxt3-argb-8bbp-explicitalpha_MipMaps-1.dds"
 | 
					TEST_FILE_DXT3 = "Tests/images/dxt3-argb-8bbp-explicitalpha_MipMaps-1.dds"
 | 
				
			||||||
| 
						 | 
					@ -109,6 +115,32 @@ def test_sanity_ati1_bc4u(image_path: str) -> None:
 | 
				
			||||||
        assert_image_equal_tofile(im, TEST_FILE_ATI1.replace(".dds", ".png"))
 | 
					        assert_image_equal_tofile(im, TEST_FILE_ATI1.replace(".dds", ".png"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_dx10_bc2(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					    out = tmp_path / "temp.dds"
 | 
				
			||||||
 | 
					    with Image.open(TEST_FILE_DXT3) as im:
 | 
				
			||||||
 | 
					        im.save(out, pixel_format="BC2")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    with Image.open(out) as reloaded:
 | 
				
			||||||
 | 
					        assert reloaded.format == "DDS"
 | 
				
			||||||
 | 
					        assert reloaded.mode == "RGBA"
 | 
				
			||||||
 | 
					        assert reloaded.size == (256, 256)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assert_image_similar(im, reloaded, 3.81)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_dx10_bc3(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					    out = tmp_path / "temp.dds"
 | 
				
			||||||
 | 
					    with Image.open(TEST_FILE_DXT5) as im:
 | 
				
			||||||
 | 
					        im.save(out, pixel_format="BC3")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    with Image.open(out) as reloaded:
 | 
				
			||||||
 | 
					        assert reloaded.format == "DDS"
 | 
				
			||||||
 | 
					        assert reloaded.mode == "RGBA"
 | 
				
			||||||
 | 
					        assert reloaded.size == (256, 256)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assert_image_similar(im, reloaded, 3.69)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@pytest.mark.parametrize(
 | 
					@pytest.mark.parametrize(
 | 
				
			||||||
    "image_path",
 | 
					    "image_path",
 | 
				
			||||||
    (
 | 
					    (
 | 
				
			||||||
| 
						 | 
					@ -331,11 +363,13 @@ def test_dxt5_colorblock_alpha_issue_4142() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open("Tests/images/dxt5-colorblock-alpha-issue-4142.dds") as im:
 | 
					    with Image.open("Tests/images/dxt5-colorblock-alpha-issue-4142.dds") as im:
 | 
				
			||||||
        px = im.getpixel((0, 0))
 | 
					        px = im.getpixel((0, 0))
 | 
				
			||||||
 | 
					        assert isinstance(px, tuple)
 | 
				
			||||||
        assert px[0] != 0
 | 
					        assert px[0] != 0
 | 
				
			||||||
        assert px[1] != 0
 | 
					        assert px[1] != 0
 | 
				
			||||||
        assert px[2] != 0
 | 
					        assert px[2] != 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        px = im.getpixel((1, 0))
 | 
					        px = im.getpixel((1, 0))
 | 
				
			||||||
 | 
					        assert isinstance(px, tuple)
 | 
				
			||||||
        assert px[0] != 0
 | 
					        assert px[0] != 0
 | 
				
			||||||
        assert px[1] != 0
 | 
					        assert px[1] != 0
 | 
				
			||||||
        assert px[2] != 0
 | 
					        assert px[2] != 0
 | 
				
			||||||
| 
						 | 
					@ -366,9 +400,9 @@ def test_not_implemented(test_file: str) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_save_unsupported_mode(tmp_path: Path) -> None:
 | 
					def test_save_unsupported_mode(tmp_path: Path) -> None:
 | 
				
			||||||
    out = str(tmp_path / "temp.dds")
 | 
					    out = tmp_path / "temp.dds"
 | 
				
			||||||
    im = hopper("HSV")
 | 
					    im = hopper("HSV")
 | 
				
			||||||
    with pytest.raises(OSError):
 | 
					    with pytest.raises(OSError, match="cannot write mode HSV as DDS"):
 | 
				
			||||||
        im.save(out)
 | 
					        im.save(out)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -382,10 +416,98 @@ def test_save_unsupported_mode(tmp_path: Path) -> None:
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
def test_save(mode: str, test_file: str, tmp_path: Path) -> None:
 | 
					def test_save(mode: str, test_file: str, tmp_path: Path) -> None:
 | 
				
			||||||
    out = str(tmp_path / "temp.dds")
 | 
					    out = tmp_path / "temp.dds"
 | 
				
			||||||
    with Image.open(test_file) as im:
 | 
					    with Image.open(test_file) as im:
 | 
				
			||||||
        assert im.mode == mode
 | 
					        assert im.mode == mode
 | 
				
			||||||
        im.save(out)
 | 
					        im.save(out)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assert_image_equal_tofile(im, out)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_save_unsupported_pixel_format(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					    out = tmp_path / "temp.dds"
 | 
				
			||||||
 | 
					    im = hopper()
 | 
				
			||||||
 | 
					    with pytest.raises(OSError, match="cannot write pixel format UNKNOWN"):
 | 
				
			||||||
 | 
					        im.save(out, pixel_format="UNKNOWN")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_save_dxt1(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					    # RGB
 | 
				
			||||||
 | 
					    out = tmp_path / "temp.dds"
 | 
				
			||||||
 | 
					    with Image.open(TEST_FILE_DXT1) as im:
 | 
				
			||||||
 | 
					        im.convert("RGB").save(out, pixel_format="DXT1")
 | 
				
			||||||
 | 
					    assert_image_similar_tofile(im, out, 1.84)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # RGBA
 | 
				
			||||||
 | 
					    im_alpha = im.copy()
 | 
				
			||||||
 | 
					    im_alpha.putpixel((0, 0), (0, 0, 0, 0))
 | 
				
			||||||
 | 
					    im_alpha.save(out, pixel_format="DXT1")
 | 
				
			||||||
    with Image.open(out) as reloaded:
 | 
					    with Image.open(out) as reloaded:
 | 
				
			||||||
            assert_image_equal(im, reloaded)
 | 
					        assert reloaded.getpixel((0, 0)) == (0, 0, 0, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # L
 | 
				
			||||||
 | 
					    im_l = im.convert("L")
 | 
				
			||||||
 | 
					    im_l.save(out, pixel_format="DXT1")
 | 
				
			||||||
 | 
					    assert_image_similar_tofile(im_l.convert("RGBA"), out, 6.07)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # LA
 | 
				
			||||||
 | 
					    im_alpha.convert("LA").save(out, pixel_format="DXT1")
 | 
				
			||||||
 | 
					    with Image.open(out) as reloaded:
 | 
				
			||||||
 | 
					        assert reloaded.getpixel((0, 0)) == (0, 0, 0, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_save_dxt3(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					    # RGB
 | 
				
			||||||
 | 
					    out = tmp_path / "temp.dds"
 | 
				
			||||||
 | 
					    with Image.open(TEST_FILE_DXT3) as im:
 | 
				
			||||||
 | 
					        im_rgb = im.convert("RGB")
 | 
				
			||||||
 | 
					    im_rgb.save(out, pixel_format="DXT3")
 | 
				
			||||||
 | 
					    assert_image_similar_tofile(im_rgb.convert("RGBA"), out, 1.26)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # RGBA
 | 
				
			||||||
 | 
					    im.save(out, pixel_format="DXT3")
 | 
				
			||||||
 | 
					    assert_image_similar_tofile(im, out, 3.81)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # L
 | 
				
			||||||
 | 
					    im_l = im.convert("L")
 | 
				
			||||||
 | 
					    im_l.save(out, pixel_format="DXT3")
 | 
				
			||||||
 | 
					    assert_image_similar_tofile(im_l.convert("RGBA"), out, 5.89)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # LA
 | 
				
			||||||
 | 
					    im_la = im.convert("LA")
 | 
				
			||||||
 | 
					    im_la.save(out, pixel_format="DXT3")
 | 
				
			||||||
 | 
					    assert_image_similar_tofile(im_la.convert("RGBA"), out, 8.44)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_save_dxt5(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					    # RGB
 | 
				
			||||||
 | 
					    out = tmp_path / "temp.dds"
 | 
				
			||||||
 | 
					    with Image.open(TEST_FILE_DXT1) as im:
 | 
				
			||||||
 | 
					        im.convert("RGB").save(out, pixel_format="DXT5")
 | 
				
			||||||
 | 
					    assert_image_similar_tofile(im, out, 1.84)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # RGBA
 | 
				
			||||||
 | 
					    with Image.open(TEST_FILE_DXT5) as im_rgba:
 | 
				
			||||||
 | 
					        im_rgba.save(out, pixel_format="DXT5")
 | 
				
			||||||
 | 
					    assert_image_similar_tofile(im_rgba, out, 3.69)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # L
 | 
				
			||||||
 | 
					    im_l = im.convert("L")
 | 
				
			||||||
 | 
					    im_l.save(out, pixel_format="DXT5")
 | 
				
			||||||
 | 
					    assert_image_similar_tofile(im_l.convert("RGBA"), out, 6.07)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # LA
 | 
				
			||||||
 | 
					    im_la = im_rgba.convert("LA")
 | 
				
			||||||
 | 
					    im_la.save(out, pixel_format="DXT5")
 | 
				
			||||||
 | 
					    assert_image_similar_tofile(im_la.convert("RGBA"), out, 8.32)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_save_dx10_bc5(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					    out = tmp_path / "temp.dds"
 | 
				
			||||||
 | 
					    with Image.open(TEST_FILE_DX10_BC5_TYPELESS) as im:
 | 
				
			||||||
 | 
					        im.save(out, pixel_format="BC5")
 | 
				
			||||||
 | 
					    assert_image_similar_tofile(im, out, 9.56)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    im = hopper("L")
 | 
				
			||||||
 | 
					    with pytest.raises(OSError, match="only RGB mode can be written as BC5"):
 | 
				
			||||||
 | 
					        im.save(out, pixel_format="BC5")
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -86,6 +86,8 @@ simple_eps_file_with_long_binary_data = (
 | 
				
			||||||
def test_sanity(filename: str, size: tuple[int, int], scale: int) -> None:
 | 
					def test_sanity(filename: str, size: tuple[int, int], scale: int) -> None:
 | 
				
			||||||
    expected_size = tuple(s * scale for s in size)
 | 
					    expected_size = tuple(s * scale for s in size)
 | 
				
			||||||
    with Image.open(filename) as image:
 | 
					    with Image.open(filename) as image:
 | 
				
			||||||
 | 
					        assert isinstance(image, EpsImagePlugin.EpsImageFile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        image.load(scale=scale)
 | 
					        image.load(scale=scale)
 | 
				
			||||||
        assert image.mode == "RGB"
 | 
					        assert image.mode == "RGB"
 | 
				
			||||||
        assert image.size == expected_size
 | 
					        assert image.size == expected_size
 | 
				
			||||||
| 
						 | 
					@ -95,10 +97,14 @@ def test_sanity(filename: str, size: tuple[int, int], scale: int) -> None:
 | 
				
			||||||
@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")
 | 
					@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")
 | 
				
			||||||
def test_load() -> None:
 | 
					def test_load() -> None:
 | 
				
			||||||
    with Image.open(FILE1) as im:
 | 
					    with Image.open(FILE1) as im:
 | 
				
			||||||
        assert im.load()[0, 0] == (255, 255, 255)
 | 
					        px = im.load()
 | 
				
			||||||
 | 
					        assert px is not None
 | 
				
			||||||
 | 
					        assert px[0, 0] == (255, 255, 255)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Test again now that it has already been loaded once
 | 
					        # Test again now that it has already been loaded once
 | 
				
			||||||
        assert im.load()[0, 0] == (255, 255, 255)
 | 
					        px = im.load()
 | 
				
			||||||
 | 
					        assert px is not None
 | 
				
			||||||
 | 
					        assert px[0, 0] == (255, 255, 255)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_binary() -> None:
 | 
					def test_binary() -> None:
 | 
				
			||||||
| 
						 | 
					@ -223,6 +229,8 @@ def test_showpage() -> None:
 | 
				
			||||||
@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")
 | 
					@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")
 | 
				
			||||||
def test_transparency() -> None:
 | 
					def test_transparency() -> None:
 | 
				
			||||||
    with Image.open("Tests/images/eps/reqd_showpage.eps") as plot_image:
 | 
					    with Image.open("Tests/images/eps/reqd_showpage.eps") as plot_image:
 | 
				
			||||||
 | 
					        assert isinstance(plot_image, EpsImagePlugin.EpsImageFile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        plot_image.load(transparency=True)
 | 
					        plot_image.load(transparency=True)
 | 
				
			||||||
        assert plot_image.mode == "RGBA"
 | 
					        assert plot_image.mode == "RGBA"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -235,7 +243,7 @@ def test_transparency() -> None:
 | 
				
			||||||
def test_file_object(tmp_path: Path) -> None:
 | 
					def test_file_object(tmp_path: Path) -> None:
 | 
				
			||||||
    # issue 479
 | 
					    # issue 479
 | 
				
			||||||
    with Image.open(FILE1) as image1:
 | 
					    with Image.open(FILE1) as image1:
 | 
				
			||||||
        with open(str(tmp_path / "temp.eps"), "wb") as fh:
 | 
					        with open(tmp_path / "temp.eps", "wb") as fh:
 | 
				
			||||||
            image1.save(fh, "EPS")
 | 
					            image1.save(fh, "EPS")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -270,7 +278,7 @@ def test_1(filename: str) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_image_mode_not_supported(tmp_path: Path) -> None:
 | 
					def test_image_mode_not_supported(tmp_path: Path) -> None:
 | 
				
			||||||
    im = hopper("RGBA")
 | 
					    im = hopper("RGBA")
 | 
				
			||||||
    tmpfile = str(tmp_path / "temp.eps")
 | 
					    tmpfile = tmp_path / "temp.eps"
 | 
				
			||||||
    with pytest.raises(ValueError):
 | 
					    with pytest.raises(ValueError):
 | 
				
			||||||
        im.save(tmpfile)
 | 
					        im.save(tmpfile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -304,6 +312,7 @@ def test_render_scale2() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Zero bounding box
 | 
					    # Zero bounding box
 | 
				
			||||||
    with Image.open(FILE1) as image1_scale2:
 | 
					    with Image.open(FILE1) as image1_scale2:
 | 
				
			||||||
 | 
					        assert isinstance(image1_scale2, EpsImagePlugin.EpsImageFile)
 | 
				
			||||||
        image1_scale2.load(scale=2)
 | 
					        image1_scale2.load(scale=2)
 | 
				
			||||||
        with Image.open(FILE1_COMPARE_SCALE2) as image1_scale2_compare:
 | 
					        with Image.open(FILE1_COMPARE_SCALE2) as image1_scale2_compare:
 | 
				
			||||||
            image1_scale2_compare = image1_scale2_compare.convert("RGB")
 | 
					            image1_scale2_compare = image1_scale2_compare.convert("RGB")
 | 
				
			||||||
| 
						 | 
					@ -312,6 +321,7 @@ def test_render_scale2() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Non-zero bounding box
 | 
					    # Non-zero bounding box
 | 
				
			||||||
    with Image.open(FILE2) as image2_scale2:
 | 
					    with Image.open(FILE2) as image2_scale2:
 | 
				
			||||||
 | 
					        assert isinstance(image2_scale2, EpsImagePlugin.EpsImageFile)
 | 
				
			||||||
        image2_scale2.load(scale=2)
 | 
					        image2_scale2.load(scale=2)
 | 
				
			||||||
        with Image.open(FILE2_COMPARE_SCALE2) as image2_scale2_compare:
 | 
					        with Image.open(FILE2_COMPARE_SCALE2) as image2_scale2_compare:
 | 
				
			||||||
            image2_scale2_compare = image2_scale2_compare.convert("RGB")
 | 
					            image2_scale2_compare = image2_scale2_compare.convert("RGB")
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,6 @@
 | 
				
			||||||
from __future__ import annotations
 | 
					from __future__ import annotations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import io
 | 
				
			||||||
import warnings
 | 
					import warnings
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import pytest
 | 
					import pytest
 | 
				
			||||||
| 
						 | 
					@ -21,6 +22,8 @@ animated_test_file_with_prefix_chunk = "Tests/images/2422.flc"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_sanity() -> None:
 | 
					def test_sanity() -> None:
 | 
				
			||||||
    with Image.open(static_test_file) as im:
 | 
					    with Image.open(static_test_file) as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, FliImagePlugin.FliImageFile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        im.load()
 | 
					        im.load()
 | 
				
			||||||
        assert im.mode == "P"
 | 
					        assert im.mode == "P"
 | 
				
			||||||
        assert im.size == (128, 128)
 | 
					        assert im.size == (128, 128)
 | 
				
			||||||
| 
						 | 
					@ -28,6 +31,8 @@ def test_sanity() -> None:
 | 
				
			||||||
        assert not im.is_animated
 | 
					        assert not im.is_animated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open(animated_test_file) as im:
 | 
					    with Image.open(animated_test_file) as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, FliImagePlugin.FliImageFile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        assert im.mode == "P"
 | 
					        assert im.mode == "P"
 | 
				
			||||||
        assert im.size == (320, 200)
 | 
					        assert im.size == (320, 200)
 | 
				
			||||||
        assert im.format == "FLI"
 | 
					        assert im.format == "FLI"
 | 
				
			||||||
| 
						 | 
					@ -35,9 +40,8 @@ def test_sanity() -> None:
 | 
				
			||||||
        assert im.is_animated
 | 
					        assert im.is_animated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_prefix_chunk() -> None:
 | 
					def test_prefix_chunk(monkeypatch: pytest.MonkeyPatch) -> None:
 | 
				
			||||||
    ImageFile.LOAD_TRUNCATED_IMAGES = True
 | 
					    monkeypatch.setattr(ImageFile, "LOAD_TRUNCATED_IMAGES", True)
 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
    with Image.open(animated_test_file_with_prefix_chunk) as im:
 | 
					    with Image.open(animated_test_file_with_prefix_chunk) as im:
 | 
				
			||||||
        assert im.mode == "P"
 | 
					        assert im.mode == "P"
 | 
				
			||||||
        assert im.size == (320, 200)
 | 
					        assert im.size == (320, 200)
 | 
				
			||||||
| 
						 | 
					@ -49,18 +53,16 @@ def test_prefix_chunk() -> None:
 | 
				
			||||||
        assert palette[3:6] == [255, 255, 255]
 | 
					        assert palette[3:6] == [255, 255, 255]
 | 
				
			||||||
        assert palette[381:384] == [204, 204, 12]
 | 
					        assert palette[381:384] == [204, 204, 12]
 | 
				
			||||||
        assert palette[765:] == [252, 0, 0]
 | 
					        assert palette[765:] == [252, 0, 0]
 | 
				
			||||||
    finally:
 | 
					 | 
				
			||||||
        ImageFile.LOAD_TRUNCATED_IMAGES = False
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@pytest.mark.skipif(is_pypy(), reason="Requires CPython")
 | 
					@pytest.mark.skipif(is_pypy(), reason="Requires CPython")
 | 
				
			||||||
def test_unclosed_file() -> None:
 | 
					def test_unclosed_file() -> None:
 | 
				
			||||||
    def open() -> None:
 | 
					    def open_test_image() -> None:
 | 
				
			||||||
        im = Image.open(static_test_file)
 | 
					        im = Image.open(static_test_file)
 | 
				
			||||||
        im.load()
 | 
					        im.load()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with pytest.warns(ResourceWarning):
 | 
					    with pytest.warns(ResourceWarning):
 | 
				
			||||||
        open()
 | 
					        open_test_image()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_closed_file() -> None:
 | 
					def test_closed_file() -> None:
 | 
				
			||||||
| 
						 | 
					@ -114,16 +116,19 @@ def test_palette_chunk_second() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_n_frames() -> None:
 | 
					def test_n_frames() -> None:
 | 
				
			||||||
    with Image.open(static_test_file) as im:
 | 
					    with Image.open(static_test_file) as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, FliImagePlugin.FliImageFile)
 | 
				
			||||||
        assert im.n_frames == 1
 | 
					        assert im.n_frames == 1
 | 
				
			||||||
        assert not im.is_animated
 | 
					        assert not im.is_animated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open(animated_test_file) as im:
 | 
					    with Image.open(animated_test_file) as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, FliImagePlugin.FliImageFile)
 | 
				
			||||||
        assert im.n_frames == 384
 | 
					        assert im.n_frames == 384
 | 
				
			||||||
        assert im.is_animated
 | 
					        assert im.is_animated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_eoferror() -> None:
 | 
					def test_eoferror() -> None:
 | 
				
			||||||
    with Image.open(animated_test_file) as im:
 | 
					    with Image.open(animated_test_file) as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, FliImagePlugin.FliImageFile)
 | 
				
			||||||
        n_frames = im.n_frames
 | 
					        n_frames = im.n_frames
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Test seeking past the last frame
 | 
					        # Test seeking past the last frame
 | 
				
			||||||
| 
						 | 
					@ -135,6 +140,15 @@ def test_eoferror() -> None:
 | 
				
			||||||
        im.seek(n_frames - 1)
 | 
					        im.seek(n_frames - 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_missing_frame_size() -> None:
 | 
				
			||||||
 | 
					    with open(animated_test_file, "rb") as fp:
 | 
				
			||||||
 | 
					        data = fp.read()
 | 
				
			||||||
 | 
					    data = data[:6188]
 | 
				
			||||||
 | 
					    with Image.open(io.BytesIO(data)) as im:
 | 
				
			||||||
 | 
					        with pytest.raises(EOFError, match="missing frame size"):
 | 
				
			||||||
 | 
					            im.seek(1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_seek_tell() -> None:
 | 
					def test_seek_tell() -> None:
 | 
				
			||||||
    with Image.open(animated_test_file) as im:
 | 
					    with Image.open(animated_test_file) as im:
 | 
				
			||||||
        layer_number = im.tell()
 | 
					        layer_number = im.tell()
 | 
				
			||||||
| 
						 | 
					@ -159,10 +173,14 @@ def test_seek_tell() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_seek() -> None:
 | 
					def test_seek() -> None:
 | 
				
			||||||
    with Image.open(animated_test_file) as im:
 | 
					    with Image.open(animated_test_file) as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, FliImagePlugin.FliImageFile)
 | 
				
			||||||
        im.seek(50)
 | 
					        im.seek(50)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        assert_image_equal_tofile(im, "Tests/images/a_fli.png")
 | 
					        assert_image_equal_tofile(im, "Tests/images/a_fli.png")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        with pytest.raises(ValueError, match="cannot seek to frame 52"):
 | 
				
			||||||
 | 
					            im._seek(52)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@pytest.mark.parametrize(
 | 
					@pytest.mark.parametrize(
 | 
				
			||||||
    "test_file",
 | 
					    "test_file",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -22,10 +22,11 @@ def test_sanity() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_close() -> None:
 | 
					def test_close() -> None:
 | 
				
			||||||
    with Image.open("Tests/images/input_bw_one_band.fpx") as im:
 | 
					    with Image.open("Tests/images/input_bw_one_band.fpx") as im:
 | 
				
			||||||
        pass
 | 
					        assert isinstance(im, FpxImagePlugin.FpxImageFile)
 | 
				
			||||||
    assert im.ole.fp.closed
 | 
					    assert im.ole.fp.closed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    im = Image.open("Tests/images/input_bw_one_band.fpx")
 | 
					    im = Image.open("Tests/images/input_bw_one_band.fpx")
 | 
				
			||||||
 | 
					    assert isinstance(im, FpxImagePlugin.FpxImageFile)
 | 
				
			||||||
    im.close()
 | 
					    im.close()
 | 
				
			||||||
    assert im.ole.fp.closed
 | 
					    assert im.ole.fp.closed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,8 @@
 | 
				
			||||||
from __future__ import annotations
 | 
					from __future__ import annotations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import io
 | 
				
			||||||
 | 
					import struct
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import pytest
 | 
					import pytest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from PIL import FtexImagePlugin, Image
 | 
					from PIL import FtexImagePlugin, Image
 | 
				
			||||||
| 
						 | 
					@ -23,3 +26,15 @@ def test_invalid_file() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with pytest.raises(SyntaxError):
 | 
					    with pytest.raises(SyntaxError):
 | 
				
			||||||
        FtexImagePlugin.FtexImageFile(invalid_file)
 | 
					        FtexImagePlugin.FtexImageFile(invalid_file)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_invalid_texture() -> None:
 | 
				
			||||||
 | 
					    with open("Tests/images/ftex_dxt1.ftc", "rb") as fp:
 | 
				
			||||||
 | 
					        data = fp.read()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Change texture compression format
 | 
				
			||||||
 | 
					    data = data[:24] + struct.pack("<i", 2) + data[28:]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    with pytest.raises(ValueError, match="Invalid texture compression format: 2"):
 | 
				
			||||||
 | 
					        with Image.open(io.BytesIO(data)):
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,10 +14,14 @@ def test_gbr_file() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_load() -> None:
 | 
					def test_load() -> None:
 | 
				
			||||||
    with Image.open("Tests/images/gbr.gbr") as im:
 | 
					    with Image.open("Tests/images/gbr.gbr") as im:
 | 
				
			||||||
        assert im.load()[0, 0] == (0, 0, 0, 0)
 | 
					        px = im.load()
 | 
				
			||||||
 | 
					        assert px is not None
 | 
				
			||||||
 | 
					        assert px[0, 0] == (0, 0, 0, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Test again now that it has already been loaded once
 | 
					        # Test again now that it has already been loaded once
 | 
				
			||||||
        assert im.load()[0, 0] == (0, 0, 0, 0)
 | 
					        px = im.load()
 | 
				
			||||||
 | 
					        assert px is not None
 | 
				
			||||||
 | 
					        assert px[0, 0] == (0, 0, 0, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_multiple_load_operations() -> None:
 | 
					def test_multiple_load_operations() -> None:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,6 +4,8 @@ import pytest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from PIL import GdImageFile, UnidentifiedImageError
 | 
					from PIL import GdImageFile, UnidentifiedImageError
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from .helper import assert_image_similar_tofile
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TEST_GD_FILE = "Tests/images/hopper.gd"
 | 
					TEST_GD_FILE = "Tests/images/hopper.gd"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,6 +13,7 @@ def test_sanity() -> None:
 | 
				
			||||||
    with GdImageFile.open(TEST_GD_FILE) as im:
 | 
					    with GdImageFile.open(TEST_GD_FILE) as im:
 | 
				
			||||||
        assert im.size == (128, 128)
 | 
					        assert im.size == (128, 128)
 | 
				
			||||||
        assert im.format == "GD"
 | 
					        assert im.format == "GD"
 | 
				
			||||||
 | 
					        assert_image_similar_tofile(im.convert("RGB"), "Tests/images/hopper.jpg", 14)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_bad_mode() -> None:
 | 
					def test_bad_mode() -> None:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,6 +4,7 @@ import warnings
 | 
				
			||||||
from collections.abc import Generator
 | 
					from collections.abc import Generator
 | 
				
			||||||
from io import BytesIO
 | 
					from io import BytesIO
 | 
				
			||||||
from pathlib import Path
 | 
					from pathlib import Path
 | 
				
			||||||
 | 
					from typing import Any
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import pytest
 | 
					import pytest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -21,9 +22,6 @@ from .helper import (
 | 
				
			||||||
# sample gif stream
 | 
					# sample gif stream
 | 
				
			||||||
TEST_GIF = "Tests/images/hopper.gif"
 | 
					TEST_GIF = "Tests/images/hopper.gif"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
with open(TEST_GIF, "rb") as f:
 | 
					 | 
				
			||||||
    data = f.read()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_sanity() -> None:
 | 
					def test_sanity() -> None:
 | 
				
			||||||
    with Image.open(TEST_GIF) as im:
 | 
					    with Image.open(TEST_GIF) as im:
 | 
				
			||||||
| 
						 | 
					@ -36,12 +34,12 @@ def test_sanity() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@pytest.mark.skipif(is_pypy(), reason="Requires CPython")
 | 
					@pytest.mark.skipif(is_pypy(), reason="Requires CPython")
 | 
				
			||||||
def test_unclosed_file() -> None:
 | 
					def test_unclosed_file() -> None:
 | 
				
			||||||
    def open() -> None:
 | 
					    def open_test_image() -> None:
 | 
				
			||||||
        im = Image.open(TEST_GIF)
 | 
					        im = Image.open(TEST_GIF)
 | 
				
			||||||
        im.load()
 | 
					        im.load()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with pytest.warns(ResourceWarning):
 | 
					    with pytest.warns(ResourceWarning):
 | 
				
			||||||
        open()
 | 
					        open_test_image()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_closed_file() -> None:
 | 
					def test_closed_file() -> None:
 | 
				
			||||||
| 
						 | 
					@ -85,12 +83,12 @@ def test_invalid_file() -> None:
 | 
				
			||||||
def test_l_mode_transparency() -> None:
 | 
					def test_l_mode_transparency() -> None:
 | 
				
			||||||
    with Image.open("Tests/images/no_palette_with_transparency.gif") as im:
 | 
					    with Image.open("Tests/images/no_palette_with_transparency.gif") as im:
 | 
				
			||||||
        assert im.mode == "L"
 | 
					        assert im.mode == "L"
 | 
				
			||||||
        assert im.load()[0, 0] == 128
 | 
					        assert im.getpixel((0, 0)) == 128
 | 
				
			||||||
        assert im.info["transparency"] == 255
 | 
					        assert im.info["transparency"] == 255
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        im.seek(1)
 | 
					        im.seek(1)
 | 
				
			||||||
        assert im.mode == "L"
 | 
					        assert im.mode == "L"
 | 
				
			||||||
        assert im.load()[0, 0] == 128
 | 
					        assert im.getpixel((0, 0)) == 128
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_l_mode_after_rgb() -> None:
 | 
					def test_l_mode_after_rgb() -> None:
 | 
				
			||||||
| 
						 | 
					@ -108,7 +106,7 @@ def test_palette_not_needed_for_second_frame() -> None:
 | 
				
			||||||
        assert_image_similar(im, hopper("L").convert("RGB"), 8)
 | 
					        assert_image_similar(im, hopper("L").convert("RGB"), 8)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_strategy() -> None:
 | 
					def test_strategy(monkeypatch: pytest.MonkeyPatch) -> None:
 | 
				
			||||||
    with Image.open("Tests/images/iss634.gif") as im:
 | 
					    with Image.open("Tests/images/iss634.gif") as im:
 | 
				
			||||||
        expected_rgb_always = im.convert("RGB")
 | 
					        expected_rgb_always = im.convert("RGB")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -118,8 +116,9 @@ def test_strategy() -> None:
 | 
				
			||||||
        im.seek(1)
 | 
					        im.seek(1)
 | 
				
			||||||
        expected_different = im.convert("RGB")
 | 
					        expected_different = im.convert("RGB")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try:
 | 
					    monkeypatch.setattr(
 | 
				
			||||||
        GifImagePlugin.LOADING_STRATEGY = GifImagePlugin.LoadingStrategy.RGB_ALWAYS
 | 
					        GifImagePlugin, "LOADING_STRATEGY", GifImagePlugin.LoadingStrategy.RGB_ALWAYS
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
    with Image.open("Tests/images/iss634.gif") as im:
 | 
					    with Image.open("Tests/images/iss634.gif") as im:
 | 
				
			||||||
        assert im.mode == "RGB"
 | 
					        assert im.mode == "RGB"
 | 
				
			||||||
        assert_image_equal(im, expected_rgb_always)
 | 
					        assert_image_equal(im, expected_rgb_always)
 | 
				
			||||||
| 
						 | 
					@ -128,8 +127,10 @@ def test_strategy() -> None:
 | 
				
			||||||
        assert im.mode == "RGBA"
 | 
					        assert im.mode == "RGBA"
 | 
				
			||||||
        assert_image_equal(im, expected_rgb_always_rgba)
 | 
					        assert_image_equal(im, expected_rgb_always_rgba)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        GifImagePlugin.LOADING_STRATEGY = (
 | 
					    monkeypatch.setattr(
 | 
				
			||||||
            GifImagePlugin.LoadingStrategy.RGB_AFTER_DIFFERENT_PALETTE_ONLY
 | 
					        GifImagePlugin,
 | 
				
			||||||
 | 
					        "LOADING_STRATEGY",
 | 
				
			||||||
 | 
					        GifImagePlugin.LoadingStrategy.RGB_AFTER_DIFFERENT_PALETTE_ONLY,
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    # Stay in P mode with only a global palette
 | 
					    # Stay in P mode with only a global palette
 | 
				
			||||||
    with Image.open("Tests/images/chi.gif") as im:
 | 
					    with Image.open("Tests/images/chi.gif") as im:
 | 
				
			||||||
| 
						 | 
					@ -145,8 +146,6 @@ def test_strategy() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        im.seek(1)
 | 
					        im.seek(1)
 | 
				
			||||||
        assert im.mode == "RGB"
 | 
					        assert im.mode == "RGB"
 | 
				
			||||||
    finally:
 | 
					 | 
				
			||||||
        GifImagePlugin.LOADING_STRATEGY = GifImagePlugin.LoadingStrategy.RGB_AFTER_FIRST
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_optimize() -> None:
 | 
					def test_optimize() -> None:
 | 
				
			||||||
| 
						 | 
					@ -229,7 +228,7 @@ def test_optimize_if_palette_can_be_reduced_by_half() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_full_palette_second_frame(tmp_path: Path) -> None:
 | 
					def test_full_palette_second_frame(tmp_path: Path) -> None:
 | 
				
			||||||
    out = str(tmp_path / "temp.gif")
 | 
					    out = tmp_path / "temp.gif"
 | 
				
			||||||
    im = Image.new("P", (1, 256))
 | 
					    im = Image.new("P", (1, 256))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    full_palette_im = Image.new("P", (1, 256))
 | 
					    full_palette_im = Image.new("P", (1, 256))
 | 
				
			||||||
| 
						 | 
					@ -250,7 +249,7 @@ def test_full_palette_second_frame(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_roundtrip(tmp_path: Path) -> None:
 | 
					def test_roundtrip(tmp_path: Path) -> None:
 | 
				
			||||||
    out = str(tmp_path / "temp.gif")
 | 
					    out = tmp_path / "temp.gif"
 | 
				
			||||||
    im = hopper()
 | 
					    im = hopper()
 | 
				
			||||||
    im.save(out)
 | 
					    im.save(out)
 | 
				
			||||||
    with Image.open(out) as reread:
 | 
					    with Image.open(out) as reread:
 | 
				
			||||||
| 
						 | 
					@ -259,7 +258,7 @@ def test_roundtrip(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_roundtrip2(tmp_path: Path) -> None:
 | 
					def test_roundtrip2(tmp_path: Path) -> None:
 | 
				
			||||||
    # see https://github.com/python-pillow/Pillow/issues/403
 | 
					    # see https://github.com/python-pillow/Pillow/issues/403
 | 
				
			||||||
    out = str(tmp_path / "temp.gif")
 | 
					    out = tmp_path / "temp.gif"
 | 
				
			||||||
    with Image.open(TEST_GIF) as im:
 | 
					    with Image.open(TEST_GIF) as im:
 | 
				
			||||||
        im2 = im.copy()
 | 
					        im2 = im.copy()
 | 
				
			||||||
        im2.save(out)
 | 
					        im2.save(out)
 | 
				
			||||||
| 
						 | 
					@ -269,7 +268,7 @@ def test_roundtrip2(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_roundtrip_save_all(tmp_path: Path) -> None:
 | 
					def test_roundtrip_save_all(tmp_path: Path) -> None:
 | 
				
			||||||
    # Single frame image
 | 
					    # Single frame image
 | 
				
			||||||
    out = str(tmp_path / "temp.gif")
 | 
					    out = tmp_path / "temp.gif"
 | 
				
			||||||
    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:
 | 
				
			||||||
| 
						 | 
					@ -277,7 +276,7 @@ def test_roundtrip_save_all(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Multiframe image
 | 
					    # Multiframe image
 | 
				
			||||||
    with Image.open("Tests/images/dispose_bgnd.gif") as im:
 | 
					    with Image.open("Tests/images/dispose_bgnd.gif") as im:
 | 
				
			||||||
        out = str(tmp_path / "temp.gif")
 | 
					        out = 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:
 | 
				
			||||||
| 
						 | 
					@ -285,7 +284,7 @@ def test_roundtrip_save_all(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_roundtrip_save_all_1(tmp_path: Path) -> None:
 | 
					def test_roundtrip_save_all_1(tmp_path: Path) -> None:
 | 
				
			||||||
    out = str(tmp_path / "temp.gif")
 | 
					    out = tmp_path / "temp.gif"
 | 
				
			||||||
    im = Image.new("1", (1, 1))
 | 
					    im = Image.new("1", (1, 1))
 | 
				
			||||||
    im2 = Image.new("1", (1, 1), 1)
 | 
					    im2 = Image.new("1", (1, 1), 1)
 | 
				
			||||||
    im.save(out, save_all=True, append_images=[im2])
 | 
					    im.save(out, save_all=True, append_images=[im2])
 | 
				
			||||||
| 
						 | 
					@ -308,8 +307,9 @@ def test_roundtrip_save_all_1(tmp_path: Path) -> None:
 | 
				
			||||||
def test_loading_multiple_palettes(path: str, mode: str) -> None:
 | 
					def test_loading_multiple_palettes(path: str, mode: str) -> None:
 | 
				
			||||||
    with Image.open(path) as im:
 | 
					    with Image.open(path) as im:
 | 
				
			||||||
        assert im.mode == "P"
 | 
					        assert im.mode == "P"
 | 
				
			||||||
 | 
					        assert im.palette is not None
 | 
				
			||||||
        first_frame_colors = im.palette.colors.keys()
 | 
					        first_frame_colors = im.palette.colors.keys()
 | 
				
			||||||
        original_color = im.convert("RGB").load()[0, 0]
 | 
					        original_color = im.convert("RGB").getpixel((0, 0))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        im.seek(1)
 | 
					        im.seek(1)
 | 
				
			||||||
        assert im.mode == mode
 | 
					        assert im.mode == mode
 | 
				
			||||||
| 
						 | 
					@ -317,10 +317,10 @@ def test_loading_multiple_palettes(path: str, mode: str) -> None:
 | 
				
			||||||
            im = im.convert("RGB")
 | 
					            im = im.convert("RGB")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Check a color only from the old palette
 | 
					        # Check a color only from the old palette
 | 
				
			||||||
        assert im.load()[0, 0] == original_color
 | 
					        assert im.getpixel((0, 0)) == original_color
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Check a color from the new palette
 | 
					        # Check a color from the new palette
 | 
				
			||||||
        assert im.load()[24, 24] not in first_frame_colors
 | 
					        assert im.getpixel((24, 24)) not in first_frame_colors
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_headers_saving_for_animated_gifs(tmp_path: Path) -> None:
 | 
					def test_headers_saving_for_animated_gifs(tmp_path: Path) -> None:
 | 
				
			||||||
| 
						 | 
					@ -329,7 +329,7 @@ def test_headers_saving_for_animated_gifs(tmp_path: Path) -> None:
 | 
				
			||||||
    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 = 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:
 | 
				
			||||||
| 
						 | 
					@ -345,7 +345,7 @@ def test_palette_handling(tmp_path: Path) -> None:
 | 
				
			||||||
        im = im.resize((100, 100), Image.Resampling.LANCZOS)
 | 
					        im = im.resize((100, 100), Image.Resampling.LANCZOS)
 | 
				
			||||||
        im2 = im.convert("P", palette=Image.Palette.ADAPTIVE, colors=256)
 | 
					        im2 = im.convert("P", palette=Image.Palette.ADAPTIVE, colors=256)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        f = str(tmp_path / "temp.gif")
 | 
					        f = tmp_path / "temp.gif"
 | 
				
			||||||
        im2.save(f, optimize=True)
 | 
					        im2.save(f, optimize=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open(f) as reloaded:
 | 
					    with Image.open(f) as reloaded:
 | 
				
			||||||
| 
						 | 
					@ -356,7 +356,7 @@ def test_palette_434(tmp_path: Path) -> None:
 | 
				
			||||||
    # see https://github.com/python-pillow/Pillow/issues/434
 | 
					    # see https://github.com/python-pillow/Pillow/issues/434
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def roundtrip(im: Image.Image, **kwargs: bool) -> Image.Image:
 | 
					    def roundtrip(im: Image.Image, **kwargs: bool) -> Image.Image:
 | 
				
			||||||
        out = str(tmp_path / "temp.gif")
 | 
					        out = tmp_path / "temp.gif"
 | 
				
			||||||
        im.copy().save(out, "GIF", **kwargs)
 | 
					        im.copy().save(out, "GIF", **kwargs)
 | 
				
			||||||
        reloaded = Image.open(out)
 | 
					        reloaded = Image.open(out)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -402,6 +402,7 @@ def test_save_netpbm_l_mode(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_seek() -> None:
 | 
					def test_seek() -> None:
 | 
				
			||||||
    with Image.open("Tests/images/dispose_none.gif") as img:
 | 
					    with Image.open("Tests/images/dispose_none.gif") as img:
 | 
				
			||||||
 | 
					        assert isinstance(img, GifImagePlugin.GifImageFile)
 | 
				
			||||||
        frame_count = 0
 | 
					        frame_count = 0
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            while True:
 | 
					            while True:
 | 
				
			||||||
| 
						 | 
					@ -410,6 +411,10 @@ def test_seek() -> None:
 | 
				
			||||||
        except EOFError:
 | 
					        except EOFError:
 | 
				
			||||||
            assert frame_count == 5
 | 
					            assert frame_count == 5
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        img.seek(0)
 | 
				
			||||||
 | 
					        with pytest.raises(ValueError, match="cannot seek to frame 2"):
 | 
				
			||||||
 | 
					            img._seek(2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_seek_info() -> None:
 | 
					def test_seek_info() -> None:
 | 
				
			||||||
    with Image.open("Tests/images/iss634.gif") as im:
 | 
					    with Image.open("Tests/images/iss634.gif") as im:
 | 
				
			||||||
| 
						 | 
					@ -442,10 +447,12 @@ def test_seek_rewind() -> None:
 | 
				
			||||||
def test_n_frames(path: str, n_frames: int) -> None:
 | 
					def test_n_frames(path: str, n_frames: int) -> None:
 | 
				
			||||||
    # Test is_animated before n_frames
 | 
					    # Test is_animated before n_frames
 | 
				
			||||||
    with Image.open(path) as im:
 | 
					    with Image.open(path) as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, GifImagePlugin.GifImageFile)
 | 
				
			||||||
        assert im.is_animated == (n_frames != 1)
 | 
					        assert im.is_animated == (n_frames != 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Test is_animated after n_frames
 | 
					    # Test is_animated after n_frames
 | 
				
			||||||
    with Image.open(path) as im:
 | 
					    with Image.open(path) as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, GifImagePlugin.GifImageFile)
 | 
				
			||||||
        assert im.n_frames == n_frames
 | 
					        assert im.n_frames == n_frames
 | 
				
			||||||
        assert im.is_animated == (n_frames != 1)
 | 
					        assert im.is_animated == (n_frames != 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -455,6 +462,7 @@ def test_no_change() -> None:
 | 
				
			||||||
    with Image.open("Tests/images/dispose_bgnd.gif") as im:
 | 
					    with Image.open("Tests/images/dispose_bgnd.gif") as im:
 | 
				
			||||||
        im.seek(1)
 | 
					        im.seek(1)
 | 
				
			||||||
        expected = im.copy()
 | 
					        expected = im.copy()
 | 
				
			||||||
 | 
					        assert isinstance(im, GifImagePlugin.GifImageFile)
 | 
				
			||||||
        assert im.n_frames == 5
 | 
					        assert im.n_frames == 5
 | 
				
			||||||
        assert_image_equal(im, expected)
 | 
					        assert_image_equal(im, expected)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -462,17 +470,20 @@ def test_no_change() -> None:
 | 
				
			||||||
    with Image.open("Tests/images/dispose_bgnd.gif") as im:
 | 
					    with Image.open("Tests/images/dispose_bgnd.gif") as im:
 | 
				
			||||||
        im.seek(3)
 | 
					        im.seek(3)
 | 
				
			||||||
        expected = im.copy()
 | 
					        expected = im.copy()
 | 
				
			||||||
 | 
					        assert isinstance(im, GifImagePlugin.GifImageFile)
 | 
				
			||||||
        assert im.is_animated
 | 
					        assert im.is_animated
 | 
				
			||||||
        assert_image_equal(im, expected)
 | 
					        assert_image_equal(im, expected)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open("Tests/images/comment_after_only_frame.gif") as im:
 | 
					    with Image.open("Tests/images/comment_after_only_frame.gif") as im:
 | 
				
			||||||
        expected = Image.new("P", (1, 1))
 | 
					        expected = Image.new("P", (1, 1))
 | 
				
			||||||
 | 
					        assert isinstance(im, GifImagePlugin.GifImageFile)
 | 
				
			||||||
        assert not im.is_animated
 | 
					        assert not im.is_animated
 | 
				
			||||||
        assert_image_equal(im, expected)
 | 
					        assert_image_equal(im, expected)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_eoferror() -> None:
 | 
					def test_eoferror() -> None:
 | 
				
			||||||
    with Image.open(TEST_GIF) as im:
 | 
					    with Image.open(TEST_GIF) as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, GifImagePlugin.GifImageFile)
 | 
				
			||||||
        n_frames = im.n_frames
 | 
					        n_frames = im.n_frames
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Test seeking past the last frame
 | 
					        # Test seeking past the last frame
 | 
				
			||||||
| 
						 | 
					@ -486,12 +497,12 @@ def test_eoferror() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_first_frame_transparency() -> None:
 | 
					def test_first_frame_transparency() -> None:
 | 
				
			||||||
    with Image.open("Tests/images/first_frame_transparency.gif") as im:
 | 
					    with Image.open("Tests/images/first_frame_transparency.gif") as im:
 | 
				
			||||||
        px = im.load()
 | 
					        assert im.getpixel((0, 0)) == im.info["transparency"]
 | 
				
			||||||
        assert px[0, 0] == im.info["transparency"]
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_dispose_none() -> None:
 | 
					def test_dispose_none() -> None:
 | 
				
			||||||
    with Image.open("Tests/images/dispose_none.gif") as img:
 | 
					    with Image.open("Tests/images/dispose_none.gif") as img:
 | 
				
			||||||
 | 
					        assert isinstance(img, GifImagePlugin.GifImageFile)
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            while True:
 | 
					            while True:
 | 
				
			||||||
                img.seek(img.tell() + 1)
 | 
					                img.seek(img.tell() + 1)
 | 
				
			||||||
| 
						 | 
					@ -515,6 +526,7 @@ def test_dispose_none_load_end() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_dispose_background() -> None:
 | 
					def test_dispose_background() -> None:
 | 
				
			||||||
    with Image.open("Tests/images/dispose_bgnd.gif") as img:
 | 
					    with Image.open("Tests/images/dispose_bgnd.gif") as img:
 | 
				
			||||||
 | 
					        assert isinstance(img, GifImagePlugin.GifImageFile)
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            while True:
 | 
					            while True:
 | 
				
			||||||
                img.seek(img.tell() + 1)
 | 
					                img.seek(img.tell() + 1)
 | 
				
			||||||
| 
						 | 
					@ -527,6 +539,7 @@ def test_dispose_background_transparency() -> None:
 | 
				
			||||||
    with Image.open("Tests/images/dispose_bgnd_transparency.gif") as img:
 | 
					    with Image.open("Tests/images/dispose_bgnd_transparency.gif") as img:
 | 
				
			||||||
        img.seek(2)
 | 
					        img.seek(2)
 | 
				
			||||||
        px = img.load()
 | 
					        px = img.load()
 | 
				
			||||||
 | 
					        assert px is not None
 | 
				
			||||||
        assert px[35, 30][3] == 0
 | 
					        assert px[35, 30][3] == 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -554,21 +567,20 @@ def test_dispose_background_transparency() -> None:
 | 
				
			||||||
def test_transparent_dispose(
 | 
					def test_transparent_dispose(
 | 
				
			||||||
    loading_strategy: GifImagePlugin.LoadingStrategy,
 | 
					    loading_strategy: GifImagePlugin.LoadingStrategy,
 | 
				
			||||||
    expected_colors: tuple[tuple[int | tuple[int, int, int, int], ...]],
 | 
					    expected_colors: tuple[tuple[int | tuple[int, int, int, int], ...]],
 | 
				
			||||||
 | 
					    monkeypatch: pytest.MonkeyPatch,
 | 
				
			||||||
) -> None:
 | 
					) -> None:
 | 
				
			||||||
    GifImagePlugin.LOADING_STRATEGY = loading_strategy
 | 
					    monkeypatch.setattr(GifImagePlugin, "LOADING_STRATEGY", loading_strategy)
 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
    with Image.open("Tests/images/transparent_dispose.gif") as img:
 | 
					    with Image.open("Tests/images/transparent_dispose.gif") as img:
 | 
				
			||||||
        for frame in range(3):
 | 
					        for frame in range(3):
 | 
				
			||||||
            img.seek(frame)
 | 
					            img.seek(frame)
 | 
				
			||||||
            for x in range(3):
 | 
					            for x in range(3):
 | 
				
			||||||
                color = img.getpixel((x, 0))
 | 
					                color = img.getpixel((x, 0))
 | 
				
			||||||
                assert color == expected_colors[frame][x]
 | 
					                assert color == expected_colors[frame][x]
 | 
				
			||||||
    finally:
 | 
					 | 
				
			||||||
        GifImagePlugin.LOADING_STRATEGY = GifImagePlugin.LoadingStrategy.RGB_AFTER_FIRST
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_dispose_previous() -> None:
 | 
					def test_dispose_previous() -> None:
 | 
				
			||||||
    with Image.open("Tests/images/dispose_prev.gif") as img:
 | 
					    with Image.open("Tests/images/dispose_prev.gif") as img:
 | 
				
			||||||
 | 
					        assert isinstance(img, GifImagePlugin.GifImageFile)
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            while True:
 | 
					            while True:
 | 
				
			||||||
                img.seek(img.tell() + 1)
 | 
					                img.seek(img.tell() + 1)
 | 
				
			||||||
| 
						 | 
					@ -597,15 +609,16 @@ def test_previous_frame_loaded() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_save_dispose(tmp_path: Path) -> None:
 | 
					def test_save_dispose(tmp_path: Path) -> None:
 | 
				
			||||||
    out = str(tmp_path / "temp.gif")
 | 
					    out = tmp_path / "temp.gif"
 | 
				
			||||||
    im_list = [
 | 
					    im_list = [
 | 
				
			||||||
        Image.new("L", (100, 100), "#000"),
 | 
					        Image.new("L", (100, 100), "#000"),
 | 
				
			||||||
        Image.new("L", (100, 100), "#111"),
 | 
					        Image.new("L", (100, 100), "#111"),
 | 
				
			||||||
        Image.new("L", (100, 100), "#222"),
 | 
					        Image.new("L", (100, 100), "#222"),
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
    for method in range(0, 4):
 | 
					    for method in range(4):
 | 
				
			||||||
        im_list[0].save(out, save_all=True, append_images=im_list[1:], disposal=method)
 | 
					        im_list[0].save(out, save_all=True, append_images=im_list[1:], disposal=method)
 | 
				
			||||||
        with Image.open(out) as img:
 | 
					        with Image.open(out) as img:
 | 
				
			||||||
 | 
					            assert isinstance(img, GifImagePlugin.GifImageFile)
 | 
				
			||||||
            for _ in range(2):
 | 
					            for _ in range(2):
 | 
				
			||||||
                img.seek(img.tell() + 1)
 | 
					                img.seek(img.tell() + 1)
 | 
				
			||||||
                assert img.disposal_method == method
 | 
					                assert img.disposal_method == method
 | 
				
			||||||
| 
						 | 
					@ -619,13 +632,14 @@ def test_save_dispose(tmp_path: Path) -> None:
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open(out) as img:
 | 
					    with Image.open(out) as img:
 | 
				
			||||||
 | 
					        assert isinstance(img, GifImagePlugin.GifImageFile)
 | 
				
			||||||
        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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_dispose2_palette(tmp_path: Path) -> None:
 | 
					def test_dispose2_palette(tmp_path: Path) -> None:
 | 
				
			||||||
    out = str(tmp_path / "temp.gif")
 | 
					    out = tmp_path / "temp.gif"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Four colors: white, gray, black, red
 | 
					    # Four colors: white, gray, black, red
 | 
				
			||||||
    circles = [(255, 255, 255), (153, 153, 153), (0, 0, 0), (255, 0, 0)]
 | 
					    circles = [(255, 255, 255), (153, 153, 153), (0, 0, 0), (255, 0, 0)]
 | 
				
			||||||
| 
						 | 
					@ -659,7 +673,7 @@ def test_dispose2_palette(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_dispose2_diff(tmp_path: Path) -> None:
 | 
					def test_dispose2_diff(tmp_path: Path) -> None:
 | 
				
			||||||
    out = str(tmp_path / "temp.gif")
 | 
					    out = tmp_path / "temp.gif"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # 4 frames: red/blue, red/red, blue/blue, red/blue
 | 
					    # 4 frames: red/blue, red/red, blue/blue, red/blue
 | 
				
			||||||
    circles = [
 | 
					    circles = [
 | 
				
			||||||
| 
						 | 
					@ -701,7 +715,7 @@ def test_dispose2_diff(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_dispose2_background(tmp_path: Path) -> None:
 | 
					def test_dispose2_background(tmp_path: Path) -> None:
 | 
				
			||||||
    out = str(tmp_path / "temp.gif")
 | 
					    out = tmp_path / "temp.gif"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    im_list = []
 | 
					    im_list = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -727,7 +741,7 @@ def test_dispose2_background(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_dispose2_background_frame(tmp_path: Path) -> None:
 | 
					def test_dispose2_background_frame(tmp_path: Path) -> None:
 | 
				
			||||||
    out = str(tmp_path / "temp.gif")
 | 
					    out = tmp_path / "temp.gif"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    im_list = [Image.new("RGBA", (1, 20))]
 | 
					    im_list = [Image.new("RGBA", (1, 20))]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -741,11 +755,12 @@ def test_dispose2_background_frame(tmp_path: Path) -> None:
 | 
				
			||||||
    im_list[0].save(out, save_all=True, append_images=im_list[1:], disposal=2)
 | 
					    im_list[0].save(out, save_all=True, append_images=im_list[1:], disposal=2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open(out) as im:
 | 
					    with Image.open(out) as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, GifImagePlugin.GifImageFile)
 | 
				
			||||||
        assert im.n_frames == 3
 | 
					        assert im.n_frames == 3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_dispose2_previous_frame(tmp_path: Path) -> None:
 | 
					def test_dispose2_previous_frame(tmp_path: Path) -> None:
 | 
				
			||||||
    out = str(tmp_path / "temp.gif")
 | 
					    out = tmp_path / "temp.gif"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    im = Image.new("P", (100, 100))
 | 
					    im = Image.new("P", (100, 100))
 | 
				
			||||||
    im.info["transparency"] = 0
 | 
					    im.info["transparency"] = 0
 | 
				
			||||||
| 
						 | 
					@ -763,8 +778,23 @@ def test_dispose2_previous_frame(tmp_path: Path) -> None:
 | 
				
			||||||
        assert im.getpixel((0, 0)) == (0, 0, 0, 255)
 | 
					        assert im.getpixel((0, 0)) == (0, 0, 0, 255)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_dispose2_without_transparency(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					    out = tmp_path / "temp.gif"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    im = Image.new("P", (100, 100))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    im2 = Image.new("P", (100, 100), (0, 0, 0))
 | 
				
			||||||
 | 
					    im2.putpixel((50, 50), (255, 0, 0))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    im.save(out, save_all=True, append_images=[im2], disposal=2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    with Image.open(out) as reloaded:
 | 
				
			||||||
 | 
					        reloaded.seek(1)
 | 
				
			||||||
 | 
					        assert reloaded.tile[0].extents == (0, 0, 100, 100)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_transparency_in_second_frame(tmp_path: Path) -> None:
 | 
					def test_transparency_in_second_frame(tmp_path: Path) -> None:
 | 
				
			||||||
    out = str(tmp_path / "temp.gif")
 | 
					    out = tmp_path / "temp.gif"
 | 
				
			||||||
    with Image.open("Tests/images/different_transparency.gif") as im:
 | 
					    with Image.open("Tests/images/different_transparency.gif") as im:
 | 
				
			||||||
        assert im.info["transparency"] == 0
 | 
					        assert im.info["transparency"] == 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -794,7 +824,7 @@ def test_no_transparency_in_second_frame() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_remapped_transparency(tmp_path: Path) -> None:
 | 
					def test_remapped_transparency(tmp_path: Path) -> None:
 | 
				
			||||||
    out = str(tmp_path / "temp.gif")
 | 
					    out = tmp_path / "temp.gif"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    im = Image.new("P", (1, 2))
 | 
					    im = Image.new("P", (1, 2))
 | 
				
			||||||
    im2 = im.copy()
 | 
					    im2 = im.copy()
 | 
				
			||||||
| 
						 | 
					@ -812,7 +842,7 @@ def test_remapped_transparency(tmp_path: Path) -> None:
 | 
				
			||||||
def test_duration(tmp_path: Path) -> None:
 | 
					def test_duration(tmp_path: Path) -> None:
 | 
				
			||||||
    duration = 1000
 | 
					    duration = 1000
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    out = str(tmp_path / "temp.gif")
 | 
					    out = tmp_path / "temp.gif"
 | 
				
			||||||
    im = Image.new("L", (100, 100), "#000")
 | 
					    im = Image.new("L", (100, 100), "#000")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Check that the argument has priority over the info settings
 | 
					    # Check that the argument has priority over the info settings
 | 
				
			||||||
| 
						 | 
					@ -826,7 +856,7 @@ def test_duration(tmp_path: Path) -> None:
 | 
				
			||||||
def test_multiple_duration(tmp_path: Path) -> None:
 | 
					def test_multiple_duration(tmp_path: Path) -> None:
 | 
				
			||||||
    duration_list = [1000, 2000, 3000]
 | 
					    duration_list = [1000, 2000, 3000]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    out = str(tmp_path / "temp.gif")
 | 
					    out = tmp_path / "temp.gif"
 | 
				
			||||||
    im_list = [
 | 
					    im_list = [
 | 
				
			||||||
        Image.new("L", (100, 100), "#000"),
 | 
					        Image.new("L", (100, 100), "#000"),
 | 
				
			||||||
        Image.new("L", (100, 100), "#111"),
 | 
					        Image.new("L", (100, 100), "#111"),
 | 
				
			||||||
| 
						 | 
					@ -861,7 +891,7 @@ def test_multiple_duration(tmp_path: Path) -> None:
 | 
				
			||||||
def test_roundtrip_info_duration(tmp_path: Path) -> None:
 | 
					def test_roundtrip_info_duration(tmp_path: Path) -> None:
 | 
				
			||||||
    duration_list = [100, 500, 500]
 | 
					    duration_list = [100, 500, 500]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    out = str(tmp_path / "temp.gif")
 | 
					    out = tmp_path / "temp.gif"
 | 
				
			||||||
    with Image.open("Tests/images/transparent_dispose.gif") as im:
 | 
					    with Image.open("Tests/images/transparent_dispose.gif") as im:
 | 
				
			||||||
        assert [
 | 
					        assert [
 | 
				
			||||||
            frame.info["duration"] for frame in ImageSequence.Iterator(im)
 | 
					            frame.info["duration"] for frame in ImageSequence.Iterator(im)
 | 
				
			||||||
| 
						 | 
					@ -876,7 +906,7 @@ def test_roundtrip_info_duration(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_roundtrip_info_duration_combined(tmp_path: Path) -> None:
 | 
					def test_roundtrip_info_duration_combined(tmp_path: Path) -> None:
 | 
				
			||||||
    out = str(tmp_path / "temp.gif")
 | 
					    out = tmp_path / "temp.gif"
 | 
				
			||||||
    with Image.open("Tests/images/duplicate_frame.gif") as im:
 | 
					    with Image.open("Tests/images/duplicate_frame.gif") as im:
 | 
				
			||||||
        assert [frame.info["duration"] for frame in ImageSequence.Iterator(im)] == [
 | 
					        assert [frame.info["duration"] for frame in ImageSequence.Iterator(im)] == [
 | 
				
			||||||
            1000,
 | 
					            1000,
 | 
				
			||||||
| 
						 | 
					@ -894,7 +924,7 @@ def test_roundtrip_info_duration_combined(tmp_path: Path) -> None:
 | 
				
			||||||
def test_identical_frames(tmp_path: Path) -> None:
 | 
					def test_identical_frames(tmp_path: Path) -> None:
 | 
				
			||||||
    duration_list = [1000, 1500, 2000, 4000]
 | 
					    duration_list = [1000, 1500, 2000, 4000]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    out = str(tmp_path / "temp.gif")
 | 
					    out = tmp_path / "temp.gif"
 | 
				
			||||||
    im_list = [
 | 
					    im_list = [
 | 
				
			||||||
        Image.new("L", (100, 100), "#000"),
 | 
					        Image.new("L", (100, 100), "#000"),
 | 
				
			||||||
        Image.new("L", (100, 100), "#000"),
 | 
					        Image.new("L", (100, 100), "#000"),
 | 
				
			||||||
| 
						 | 
					@ -907,6 +937,8 @@ def test_identical_frames(tmp_path: Path) -> None:
 | 
				
			||||||
        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 isinstance(reread, GifImagePlugin.GifImageFile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # 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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -927,7 +959,7 @@ def test_identical_frames(tmp_path: Path) -> None:
 | 
				
			||||||
def test_identical_frames_to_single_frame(
 | 
					def test_identical_frames_to_single_frame(
 | 
				
			||||||
    duration: int | list[int], tmp_path: Path
 | 
					    duration: int | list[int], tmp_path: Path
 | 
				
			||||||
) -> None:
 | 
					) -> None:
 | 
				
			||||||
    out = str(tmp_path / "temp.gif")
 | 
					    out = tmp_path / "temp.gif"
 | 
				
			||||||
    im_list = [
 | 
					    im_list = [
 | 
				
			||||||
        Image.new("L", (100, 100), "#000"),
 | 
					        Image.new("L", (100, 100), "#000"),
 | 
				
			||||||
        Image.new("L", (100, 100), "#000"),
 | 
					        Image.new("L", (100, 100), "#000"),
 | 
				
			||||||
| 
						 | 
					@ -936,6 +968,8 @@ def test_identical_frames_to_single_frame(
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    im_list[0].save(out, save_all=True, append_images=im_list[1:], duration=duration)
 | 
					    im_list[0].save(out, save_all=True, append_images=im_list[1:], duration=duration)
 | 
				
			||||||
    with Image.open(out) as reread:
 | 
					    with Image.open(out) as reread:
 | 
				
			||||||
 | 
					        assert isinstance(reread, GifImagePlugin.GifImageFile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Assert that all frames were combined
 | 
					        # Assert that all frames were combined
 | 
				
			||||||
        assert reread.n_frames == 1
 | 
					        assert reread.n_frames == 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -944,7 +978,7 @@ def test_identical_frames_to_single_frame(
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_loop_none(tmp_path: Path) -> None:
 | 
					def test_loop_none(tmp_path: Path) -> None:
 | 
				
			||||||
    out = str(tmp_path / "temp.gif")
 | 
					    out = tmp_path / "temp.gif"
 | 
				
			||||||
    im = Image.new("L", (100, 100), "#000")
 | 
					    im = Image.new("L", (100, 100), "#000")
 | 
				
			||||||
    im.save(out, loop=None)
 | 
					    im.save(out, loop=None)
 | 
				
			||||||
    with Image.open(out) as reread:
 | 
					    with Image.open(out) as reread:
 | 
				
			||||||
| 
						 | 
					@ -954,7 +988,7 @@ def test_loop_none(tmp_path: Path) -> None:
 | 
				
			||||||
def test_number_of_loops(tmp_path: Path) -> None:
 | 
					def test_number_of_loops(tmp_path: Path) -> None:
 | 
				
			||||||
    number_of_loops = 2
 | 
					    number_of_loops = 2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    out = str(tmp_path / "temp.gif")
 | 
					    out = tmp_path / "temp.gif"
 | 
				
			||||||
    im = Image.new("L", (100, 100), "#000")
 | 
					    im = Image.new("L", (100, 100), "#000")
 | 
				
			||||||
    im.save(out, loop=number_of_loops)
 | 
					    im.save(out, loop=number_of_loops)
 | 
				
			||||||
    with Image.open(out) as reread:
 | 
					    with Image.open(out) as reread:
 | 
				
			||||||
| 
						 | 
					@ -970,7 +1004,7 @@ def test_number_of_loops(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_background(tmp_path: Path) -> None:
 | 
					def test_background(tmp_path: Path) -> None:
 | 
				
			||||||
    out = str(tmp_path / "temp.gif")
 | 
					    out = tmp_path / "temp.gif"
 | 
				
			||||||
    im = Image.new("L", (100, 100), "#000")
 | 
					    im = Image.new("L", (100, 100), "#000")
 | 
				
			||||||
    im.info["background"] = 1
 | 
					    im.info["background"] = 1
 | 
				
			||||||
    im.save(out)
 | 
					    im.save(out)
 | 
				
			||||||
| 
						 | 
					@ -979,7 +1013,7 @@ def test_background(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_webp_background(tmp_path: Path) -> None:
 | 
					def test_webp_background(tmp_path: Path) -> None:
 | 
				
			||||||
    out = str(tmp_path / "temp.gif")
 | 
					    out = tmp_path / "temp.gif"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Test opaque WebP background
 | 
					    # Test opaque WebP background
 | 
				
			||||||
    if features.check("webp"):
 | 
					    if features.check("webp"):
 | 
				
			||||||
| 
						 | 
					@ -997,7 +1031,7 @@ def test_comment(tmp_path: Path) -> None:
 | 
				
			||||||
    with Image.open(TEST_GIF) as im:
 | 
					    with Image.open(TEST_GIF) as im:
 | 
				
			||||||
        assert im.info["comment"] == b"File written by Adobe Photoshop\xa8 4.0"
 | 
					        assert im.info["comment"] == b"File written by Adobe Photoshop\xa8 4.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    out = str(tmp_path / "temp.gif")
 | 
					    out = tmp_path / "temp.gif"
 | 
				
			||||||
    im = Image.new("L", (100, 100), "#000")
 | 
					    im = Image.new("L", (100, 100), "#000")
 | 
				
			||||||
    im.info["comment"] = b"Test comment text"
 | 
					    im.info["comment"] = b"Test comment text"
 | 
				
			||||||
    im.save(out)
 | 
					    im.save(out)
 | 
				
			||||||
| 
						 | 
					@ -1014,7 +1048,7 @@ def test_comment(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_comment_over_255(tmp_path: Path) -> None:
 | 
					def test_comment_over_255(tmp_path: Path) -> None:
 | 
				
			||||||
    out = str(tmp_path / "temp.gif")
 | 
					    out = tmp_path / "temp.gif"
 | 
				
			||||||
    im = Image.new("L", (100, 100), "#000")
 | 
					    im = Image.new("L", (100, 100), "#000")
 | 
				
			||||||
    comment = b"Test comment text"
 | 
					    comment = b"Test comment text"
 | 
				
			||||||
    while len(comment) < 256:
 | 
					    while len(comment) < 256:
 | 
				
			||||||
| 
						 | 
					@ -1040,7 +1074,7 @@ def test_read_multiple_comment_blocks() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_empty_string_comment(tmp_path: Path) -> None:
 | 
					def test_empty_string_comment(tmp_path: Path) -> None:
 | 
				
			||||||
    out = str(tmp_path / "temp.gif")
 | 
					    out = tmp_path / "temp.gif"
 | 
				
			||||||
    with Image.open("Tests/images/chi.gif") as im:
 | 
					    with Image.open("Tests/images/chi.gif") as im:
 | 
				
			||||||
        assert "comment" in im.info
 | 
					        assert "comment" in im.info
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1074,7 +1108,7 @@ def test_retain_comment_in_subsequent_frames(tmp_path: Path) -> None:
 | 
				
			||||||
        assert "comment" not in im.info
 | 
					        assert "comment" not in im.info
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Test that a saved image keeps the comment
 | 
					    # Test that a saved image keeps the comment
 | 
				
			||||||
    out = str(tmp_path / "temp.gif")
 | 
					    out = tmp_path / "temp.gif"
 | 
				
			||||||
    with Image.open("Tests/images/dispose_prev.gif") as im:
 | 
					    with Image.open("Tests/images/dispose_prev.gif") as im:
 | 
				
			||||||
        im.save(out, save_all=True, comment="Test")
 | 
					        im.save(out, save_all=True, comment="Test")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1084,7 +1118,7 @@ def test_retain_comment_in_subsequent_frames(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_version(tmp_path: Path) -> None:
 | 
					def test_version(tmp_path: Path) -> None:
 | 
				
			||||||
    out = str(tmp_path / "temp.gif")
 | 
					    out = tmp_path / "temp.gif"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def assert_version_after_save(im: Image.Image, version: bytes) -> None:
 | 
					    def assert_version_after_save(im: Image.Image, version: bytes) -> None:
 | 
				
			||||||
        im.save(out)
 | 
					        im.save(out)
 | 
				
			||||||
| 
						 | 
					@ -1114,7 +1148,7 @@ def test_version(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_append_images(tmp_path: Path) -> None:
 | 
					def test_append_images(tmp_path: Path) -> None:
 | 
				
			||||||
    out = str(tmp_path / "temp.gif")
 | 
					    out = tmp_path / "temp.gif"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Test appending single frame images
 | 
					    # Test appending single frame images
 | 
				
			||||||
    im = Image.new("RGB", (100, 100), "#f00")
 | 
					    im = Image.new("RGB", (100, 100), "#f00")
 | 
				
			||||||
| 
						 | 
					@ -1122,6 +1156,14 @@ def test_append_images(tmp_path: Path) -> None:
 | 
				
			||||||
    im.copy().save(out, save_all=True, append_images=ims)
 | 
					    im.copy().save(out, save_all=True, append_images=ims)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open(out) as reread:
 | 
					    with Image.open(out) as reread:
 | 
				
			||||||
 | 
					        assert isinstance(reread, GifImagePlugin.GifImageFile)
 | 
				
			||||||
 | 
					        assert reread.n_frames == 3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Test append_images without save_all
 | 
				
			||||||
 | 
					    im.copy().save(out, append_images=ims)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    with Image.open(out) as reread:
 | 
				
			||||||
 | 
					        assert isinstance(reread, GifImagePlugin.GifImageFile)
 | 
				
			||||||
        assert reread.n_frames == 3
 | 
					        assert reread.n_frames == 3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Tests appending using a generator
 | 
					    # Tests appending using a generator
 | 
				
			||||||
| 
						 | 
					@ -1131,6 +1173,7 @@ def test_append_images(tmp_path: Path) -> None:
 | 
				
			||||||
    im.save(out, save_all=True, append_images=im_generator(ims))
 | 
					    im.save(out, save_all=True, append_images=im_generator(ims))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open(out) as reread:
 | 
					    with Image.open(out) as reread:
 | 
				
			||||||
 | 
					        assert isinstance(reread, GifImagePlugin.GifImageFile)
 | 
				
			||||||
        assert reread.n_frames == 3
 | 
					        assert reread.n_frames == 3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Tests appending single and multiple frame images
 | 
					    # Tests appending single and multiple frame images
 | 
				
			||||||
| 
						 | 
					@ -1139,11 +1182,12 @@ def test_append_images(tmp_path: Path) -> None:
 | 
				
			||||||
            im.save(out, save_all=True, append_images=[im2])
 | 
					            im.save(out, save_all=True, append_images=[im2])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open(out) as reread:
 | 
					    with Image.open(out) as reread:
 | 
				
			||||||
 | 
					        assert isinstance(reread, GifImagePlugin.GifImageFile)
 | 
				
			||||||
        assert reread.n_frames == 10
 | 
					        assert reread.n_frames == 10
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_append_different_size_image(tmp_path: Path) -> None:
 | 
					def test_append_different_size_image(tmp_path: Path) -> None:
 | 
				
			||||||
    out = str(tmp_path / "temp.gif")
 | 
					    out = tmp_path / "temp.gif"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    im = Image.new("RGB", (100, 100))
 | 
					    im = Image.new("RGB", (100, 100))
 | 
				
			||||||
    bigger_im = Image.new("RGB", (200, 200), "#f00")
 | 
					    bigger_im = Image.new("RGB", (200, 200), "#f00")
 | 
				
			||||||
| 
						 | 
					@ -1170,7 +1214,7 @@ def test_transparent_optimize(tmp_path: Path) -> None:
 | 
				
			||||||
    im.frombytes(data)
 | 
					    im.frombytes(data)
 | 
				
			||||||
    im.putpalette(palette)
 | 
					    im.putpalette(palette)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    out = str(tmp_path / "temp.gif")
 | 
					    out = tmp_path / "temp.gif"
 | 
				
			||||||
    im.save(out, transparency=im.getpixel((252, 0)))
 | 
					    im.save(out, transparency=im.getpixel((252, 0)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open(out) as reloaded:
 | 
					    with Image.open(out) as reloaded:
 | 
				
			||||||
| 
						 | 
					@ -1178,7 +1222,7 @@ def test_transparent_optimize(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_removed_transparency(tmp_path: Path) -> None:
 | 
					def test_removed_transparency(tmp_path: Path) -> None:
 | 
				
			||||||
    out = str(tmp_path / "temp.gif")
 | 
					    out = tmp_path / "temp.gif"
 | 
				
			||||||
    im = Image.new("RGB", (256, 1))
 | 
					    im = Image.new("RGB", (256, 1))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for x in range(256):
 | 
					    for x in range(256):
 | 
				
			||||||
| 
						 | 
					@ -1193,7 +1237,7 @@ def test_removed_transparency(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_rgb_transparency(tmp_path: Path) -> None:
 | 
					def test_rgb_transparency(tmp_path: Path) -> None:
 | 
				
			||||||
    out = str(tmp_path / "temp.gif")
 | 
					    out = tmp_path / "temp.gif"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Single frame
 | 
					    # Single frame
 | 
				
			||||||
    im = Image.new("RGB", (1, 1))
 | 
					    im = Image.new("RGB", (1, 1))
 | 
				
			||||||
| 
						 | 
					@ -1215,7 +1259,7 @@ def test_rgb_transparency(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_rgba_transparency(tmp_path: Path) -> None:
 | 
					def test_rgba_transparency(tmp_path: Path) -> None:
 | 
				
			||||||
    out = str(tmp_path / "temp.gif")
 | 
					    out = tmp_path / "temp.gif"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    im = hopper("P")
 | 
					    im = hopper("P")
 | 
				
			||||||
    im.save(out, save_all=True, append_images=[Image.new("RGBA", im.size)])
 | 
					    im.save(out, save_all=True, append_images=[Image.new("RGBA", im.size)])
 | 
				
			||||||
| 
						 | 
					@ -1225,25 +1269,26 @@ def test_rgba_transparency(tmp_path: Path) -> None:
 | 
				
			||||||
        assert_image_equal(hopper("P").convert("RGB"), reloaded)
 | 
					        assert_image_equal(hopper("P").convert("RGB"), reloaded)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_background_outside_palettte(tmp_path: Path) -> None:
 | 
					def test_background_outside_palettte() -> None:
 | 
				
			||||||
    with Image.open("Tests/images/background_outside_palette.gif") as im:
 | 
					    with Image.open("Tests/images/background_outside_palette.gif") as im:
 | 
				
			||||||
        im.seek(1)
 | 
					        im.seek(1)
 | 
				
			||||||
        assert im.info["background"] == 255
 | 
					        assert im.info["background"] == 255
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_bbox(tmp_path: Path) -> None:
 | 
					def test_bbox(tmp_path: Path) -> None:
 | 
				
			||||||
    out = str(tmp_path / "temp.gif")
 | 
					    out = tmp_path / "temp.gif"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    im = Image.new("RGB", (100, 100), "#fff")
 | 
					    im = Image.new("RGB", (100, 100), "#fff")
 | 
				
			||||||
    ims = [Image.new("RGB", (100, 100), "#000")]
 | 
					    ims = [Image.new("RGB", (100, 100), "#000")]
 | 
				
			||||||
    im.save(out, save_all=True, append_images=ims)
 | 
					    im.save(out, save_all=True, append_images=ims)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open(out) as reread:
 | 
					    with Image.open(out) as reread:
 | 
				
			||||||
 | 
					        assert isinstance(reread, GifImagePlugin.GifImageFile)
 | 
				
			||||||
        assert reread.n_frames == 2
 | 
					        assert reread.n_frames == 2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_bbox_alpha(tmp_path: Path) -> None:
 | 
					def test_bbox_alpha(tmp_path: Path) -> None:
 | 
				
			||||||
    out = str(tmp_path / "temp.gif")
 | 
					    out = tmp_path / "temp.gif"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    im = Image.new("RGBA", (1, 2), (255, 0, 0, 255))
 | 
					    im = Image.new("RGBA", (1, 2), (255, 0, 0, 255))
 | 
				
			||||||
    im.putpixel((0, 1), (255, 0, 0, 0))
 | 
					    im.putpixel((0, 1), (255, 0, 0, 0))
 | 
				
			||||||
| 
						 | 
					@ -1251,6 +1296,7 @@ def test_bbox_alpha(tmp_path: Path) -> None:
 | 
				
			||||||
    im.save(out, save_all=True, append_images=[im2])
 | 
					    im.save(out, save_all=True, append_images=[im2])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open(out) as reread:
 | 
					    with Image.open(out) as reread:
 | 
				
			||||||
 | 
					        assert isinstance(reread, GifImagePlugin.GifImageFile)
 | 
				
			||||||
        assert reread.n_frames == 2
 | 
					        assert reread.n_frames == 2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1262,7 +1308,7 @@ def test_palette_save_L(tmp_path: Path) -> None:
 | 
				
			||||||
    palette = im.getpalette()
 | 
					    palette = im.getpalette()
 | 
				
			||||||
    assert palette is not None
 | 
					    assert palette is not None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    out = str(tmp_path / "temp.gif")
 | 
					    out = tmp_path / "temp.gif"
 | 
				
			||||||
    im_l.save(out, palette=bytes(palette))
 | 
					    im_l.save(out, palette=bytes(palette))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open(out) as reloaded:
 | 
					    with Image.open(out) as reloaded:
 | 
				
			||||||
| 
						 | 
					@ -1273,7 +1319,7 @@ def test_palette_save_P(tmp_path: Path) -> None:
 | 
				
			||||||
    im = Image.new("P", (1, 2))
 | 
					    im = Image.new("P", (1, 2))
 | 
				
			||||||
    im.putpixel((0, 1), 1)
 | 
					    im.putpixel((0, 1), 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    out = str(tmp_path / "temp.gif")
 | 
					    out = tmp_path / "temp.gif"
 | 
				
			||||||
    im.save(out, palette=bytes((1, 2, 3, 4, 5, 6)))
 | 
					    im.save(out, palette=bytes((1, 2, 3, 4, 5, 6)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open(out) as reloaded:
 | 
					    with Image.open(out) as reloaded:
 | 
				
			||||||
| 
						 | 
					@ -1289,7 +1335,7 @@ def test_palette_save_duplicate_entries(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    im.putpalette((0, 0, 0, 0, 0, 0))
 | 
					    im.putpalette((0, 0, 0, 0, 0, 0))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    out = str(tmp_path / "temp.gif")
 | 
					    out = tmp_path / "temp.gif"
 | 
				
			||||||
    im.save(out, palette=[0, 0, 0, 0, 0, 0, 1, 1, 1])
 | 
					    im.save(out, palette=[0, 0, 0, 0, 0, 0, 1, 1, 1])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open(out) as reloaded:
 | 
					    with Image.open(out) as reloaded:
 | 
				
			||||||
| 
						 | 
					@ -1304,7 +1350,7 @@ def test_palette_save_all_P(tmp_path: Path) -> None:
 | 
				
			||||||
        frame.putpalette(color)
 | 
					        frame.putpalette(color)
 | 
				
			||||||
        frames.append(frame)
 | 
					        frames.append(frame)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    out = str(tmp_path / "temp.gif")
 | 
					    out = tmp_path / "temp.gif"
 | 
				
			||||||
    frames[0].save(
 | 
					    frames[0].save(
 | 
				
			||||||
        out, save_all=True, palette=[255, 0, 0, 0, 255, 0], append_images=frames[1:]
 | 
					        out, save_all=True, palette=[255, 0, 0, 0, 255, 0], append_images=frames[1:]
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
| 
						 | 
					@ -1312,6 +1358,7 @@ def test_palette_save_all_P(tmp_path: Path) -> None:
 | 
				
			||||||
    with Image.open(out) as im:
 | 
					    with Image.open(out) as im:
 | 
				
			||||||
        # Assert that the frames are correct, and each frame has the same palette
 | 
					        # Assert that the frames are correct, and each frame has the same palette
 | 
				
			||||||
        assert_image_equal(im.convert("RGB"), frames[0].convert("RGB"))
 | 
					        assert_image_equal(im.convert("RGB"), frames[0].convert("RGB"))
 | 
				
			||||||
 | 
					        assert im.palette is not None
 | 
				
			||||||
        assert im.palette.palette == im.global_palette.palette
 | 
					        assert im.palette.palette == im.global_palette.palette
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        im.seek(1)
 | 
					        im.seek(1)
 | 
				
			||||||
| 
						 | 
					@ -1326,7 +1373,7 @@ def test_palette_save_ImagePalette(tmp_path: Path) -> None:
 | 
				
			||||||
    im = hopper("P")
 | 
					    im = hopper("P")
 | 
				
			||||||
    palette = ImagePalette.ImagePalette("RGB", list(range(256))[::-1] * 3)
 | 
					    palette = ImagePalette.ImagePalette("RGB", list(range(256))[::-1] * 3)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    out = str(tmp_path / "temp.gif")
 | 
					    out = tmp_path / "temp.gif"
 | 
				
			||||||
    im.save(out, palette=palette)
 | 
					    im.save(out, palette=palette)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open(out) as reloaded:
 | 
					    with Image.open(out) as reloaded:
 | 
				
			||||||
| 
						 | 
					@ -1339,24 +1386,24 @@ def test_save_I(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    im = hopper("I")
 | 
					    im = hopper("I")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    out = str(tmp_path / "temp.gif")
 | 
					    out = tmp_path / "temp.gif"
 | 
				
			||||||
    im.save(out)
 | 
					    im.save(out)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open(out) as reloaded:
 | 
					    with Image.open(out) as reloaded:
 | 
				
			||||||
        assert_image_equal(reloaded.convert("L"), im.convert("L"))
 | 
					        assert_image_equal(reloaded.convert("L"), im.convert("L"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_getdata() -> None:
 | 
					def test_getdata(monkeypatch: pytest.MonkeyPatch) -> None:
 | 
				
			||||||
    # Test getheader/getdata against legacy values.
 | 
					    # Test getheader/getdata against legacy values.
 | 
				
			||||||
    # Create a 'P' image with holes in the palette.
 | 
					    # Create a 'P' image with holes in the palette.
 | 
				
			||||||
    im = Image._wedge().resize((16, 16), Image.Resampling.NEAREST)
 | 
					    im = Image.linear_gradient(mode="L").resize((16, 16), Image.Resampling.NEAREST)
 | 
				
			||||||
    im.putpalette(ImagePalette.ImagePalette("RGB"))
 | 
					    im.putpalette(ImagePalette.ImagePalette("RGB"))
 | 
				
			||||||
    im.info = {"background": 0}
 | 
					    im.info = {"background": 0}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    passed_palette = bytes(255 - i // 3 for i in range(768))
 | 
					    passed_palette = bytes(255 - i // 3 for i in range(768))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    GifImagePlugin._FORCE_OPTIMIZE = True
 | 
					    monkeypatch.setattr(GifImagePlugin, "_FORCE_OPTIMIZE", True)
 | 
				
			||||||
    try:
 | 
					
 | 
				
			||||||
    h = GifImagePlugin.getheader(im, passed_palette)
 | 
					    h = GifImagePlugin.getheader(im, passed_palette)
 | 
				
			||||||
    d = GifImagePlugin.getdata(im)
 | 
					    d = GifImagePlugin.getdata(im)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1370,8 +1417,6 @@ def test_getdata() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    assert h == h_target
 | 
					    assert h == h_target
 | 
				
			||||||
    assert d == d_target
 | 
					    assert d == d_target
 | 
				
			||||||
    finally:
 | 
					 | 
				
			||||||
        GifImagePlugin._FORCE_OPTIMIZE = False
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_lzw_bits() -> None:
 | 
					def test_lzw_bits() -> None:
 | 
				
			||||||
| 
						 | 
					@ -1397,11 +1442,13 @@ def test_lzw_bits() -> None:
 | 
				
			||||||
    ),
 | 
					    ),
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
def test_extents(
 | 
					def test_extents(
 | 
				
			||||||
    test_file: str, loading_strategy: GifImagePlugin.LoadingStrategy
 | 
					    test_file: str,
 | 
				
			||||||
 | 
					    loading_strategy: GifImagePlugin.LoadingStrategy,
 | 
				
			||||||
 | 
					    monkeypatch: pytest.MonkeyPatch,
 | 
				
			||||||
) -> None:
 | 
					) -> None:
 | 
				
			||||||
    GifImagePlugin.LOADING_STRATEGY = loading_strategy
 | 
					    monkeypatch.setattr(GifImagePlugin, "LOADING_STRATEGY", loading_strategy)
 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
    with Image.open("Tests/images/" + test_file) as im:
 | 
					    with Image.open("Tests/images/" + test_file) as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, GifImagePlugin.GifImageFile)
 | 
				
			||||||
        assert im.size == (100, 100)
 | 
					        assert im.size == (100, 100)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Check that n_frames does not change the size
 | 
					        # Check that n_frames does not change the size
 | 
				
			||||||
| 
						 | 
					@ -1413,8 +1460,6 @@ def test_extents(
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        im.load()
 | 
					        im.load()
 | 
				
			||||||
        assert im.im.size == (150, 150)
 | 
					        assert im.im.size == (150, 150)
 | 
				
			||||||
    finally:
 | 
					 | 
				
			||||||
        GifImagePlugin.LOADING_STRATEGY = GifImagePlugin.LoadingStrategy.RGB_AFTER_FIRST
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_missing_background() -> None:
 | 
					def test_missing_background() -> None:
 | 
				
			||||||
| 
						 | 
					@ -1426,7 +1471,7 @@ def test_missing_background() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_saving_rgba(tmp_path: Path) -> None:
 | 
					def test_saving_rgba(tmp_path: Path) -> None:
 | 
				
			||||||
    out = str(tmp_path / "temp.gif")
 | 
					    out = tmp_path / "temp.gif"
 | 
				
			||||||
    with Image.open("Tests/images/transparent.png") as im:
 | 
					    with Image.open("Tests/images/transparent.png") as im:
 | 
				
			||||||
        im.save(out)
 | 
					        im.save(out)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1435,8 +1480,9 @@ def test_saving_rgba(tmp_path: Path) -> None:
 | 
				
			||||||
        assert reloaded_rgba.load()[0, 0][3] == 0
 | 
					        assert reloaded_rgba.load()[0, 0][3] == 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_optimizing_p_rgba(tmp_path: Path) -> None:
 | 
					@pytest.mark.parametrize("params", ({}, {"disposal": 2, "optimize": False}))
 | 
				
			||||||
    out = str(tmp_path / "temp.gif")
 | 
					def test_p_rgba(tmp_path: Path, params: dict[str, Any]) -> None:
 | 
				
			||||||
 | 
					    out = tmp_path / "temp.gif"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    im1 = Image.new("P", (100, 100))
 | 
					    im1 = Image.new("P", (100, 100))
 | 
				
			||||||
    d = ImageDraw.Draw(im1)
 | 
					    d = ImageDraw.Draw(im1)
 | 
				
			||||||
| 
						 | 
					@ -1447,7 +1493,8 @@ def test_optimizing_p_rgba(tmp_path: Path) -> None:
 | 
				
			||||||
    im2 = Image.new("P", (100, 100))
 | 
					    im2 = Image.new("P", (100, 100))
 | 
				
			||||||
    im2.putpalette(data, "RGBA")
 | 
					    im2.putpalette(data, "RGBA")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    im1.save(out, save_all=True, append_images=[im2])
 | 
					    im1.save(out, save_all=True, append_images=[im2], **params)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open(out) as reloaded:
 | 
					    with Image.open(out) as reloaded:
 | 
				
			||||||
 | 
					        assert isinstance(reloaded, GifImagePlugin.GifImageFile)
 | 
				
			||||||
        assert reloaded.n_frames == 2
 | 
					        assert reloaded.n_frames == 2
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,7 @@
 | 
				
			||||||
from __future__ import annotations
 | 
					from __future__ import annotations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from io import BytesIO
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import pytest
 | 
					import pytest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from PIL.GimpPaletteFile import GimpPaletteFile
 | 
					from PIL.GimpPaletteFile import GimpPaletteFile
 | 
				
			||||||
| 
						 | 
					@ -14,17 +16,20 @@ def test_sanity() -> None:
 | 
				
			||||||
            GimpPaletteFile(fp)
 | 
					            GimpPaletteFile(fp)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with open("Tests/images/bad_palette_file.gpl", "rb") as fp:
 | 
					    with open("Tests/images/bad_palette_file.gpl", "rb") as fp:
 | 
				
			||||||
        with pytest.raises(SyntaxError):
 | 
					        with pytest.raises(SyntaxError, match="bad palette file"):
 | 
				
			||||||
            GimpPaletteFile(fp)
 | 
					            GimpPaletteFile(fp)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with open("Tests/images/bad_palette_entry.gpl", "rb") as fp:
 | 
					    with open("Tests/images/bad_palette_entry.gpl", "rb") as fp:
 | 
				
			||||||
        with pytest.raises(ValueError):
 | 
					        with pytest.raises(ValueError, match="bad palette entry"):
 | 
				
			||||||
            GimpPaletteFile(fp)
 | 
					            GimpPaletteFile(fp)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_get_palette() -> None:
 | 
					@pytest.mark.parametrize(
 | 
				
			||||||
 | 
					    "filename, size", (("custom_gimp_palette.gpl", 8), ("full_gimp_palette.gpl", 256))
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					def test_get_palette(filename: str, size: int) -> None:
 | 
				
			||||||
    # Arrange
 | 
					    # Arrange
 | 
				
			||||||
    with open("Tests/images/custom_gimp_palette.gpl", "rb") as fp:
 | 
					    with open("Tests/images/" + filename, "rb") as fp:
 | 
				
			||||||
        palette_file = GimpPaletteFile(fp)
 | 
					        palette_file = GimpPaletteFile(fp)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Act
 | 
					    # Act
 | 
				
			||||||
| 
						 | 
					@ -32,3 +37,36 @@ def test_get_palette() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Assert
 | 
					    # Assert
 | 
				
			||||||
    assert mode == "RGB"
 | 
					    assert mode == "RGB"
 | 
				
			||||||
 | 
					    assert len(palette) / 3 == size
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_frombytes() -> None:
 | 
				
			||||||
 | 
					    # Test that __init__ stops reading after 260 lines
 | 
				
			||||||
 | 
					    with open("Tests/images/custom_gimp_palette.gpl", "rb") as fp:
 | 
				
			||||||
 | 
					        custom_data = fp.read()
 | 
				
			||||||
 | 
					    custom_data += b"#\n" * 300 + b"  0   0   0     Index 12"
 | 
				
			||||||
 | 
					    b = BytesIO(custom_data)
 | 
				
			||||||
 | 
					    palette = GimpPaletteFile(b)
 | 
				
			||||||
 | 
					    assert len(palette.palette) / 3 == 8
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Test that __init__ only reads 256 entries
 | 
				
			||||||
 | 
					    with open("Tests/images/full_gimp_palette.gpl", "rb") as fp:
 | 
				
			||||||
 | 
					        full_data = fp.read()
 | 
				
			||||||
 | 
					    data = full_data.replace(b"#\n", b"") + b"  0   0   0     Index 256"
 | 
				
			||||||
 | 
					    b = BytesIO(data)
 | 
				
			||||||
 | 
					    palette = GimpPaletteFile(b)
 | 
				
			||||||
 | 
					    assert len(palette.palette) / 3 == 256
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Test that frombytes() can read beyond that
 | 
				
			||||||
 | 
					    palette = GimpPaletteFile.frombytes(data)
 | 
				
			||||||
 | 
					    assert len(palette.palette) / 3 == 257
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Test that __init__ raises an error if a comment is too long
 | 
				
			||||||
 | 
					    data = full_data[:-1] + b"a" * 100
 | 
				
			||||||
 | 
					    b = BytesIO(data)
 | 
				
			||||||
 | 
					    with pytest.raises(SyntaxError, match="bad palette file"):
 | 
				
			||||||
 | 
					        palette = GimpPaletteFile(b)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Test that frombytes() can read the data regardless
 | 
				
			||||||
 | 
					    palette = GimpPaletteFile.frombytes(data)
 | 
				
			||||||
 | 
					    assert len(palette.palette) / 3 == 256
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -43,7 +43,7 @@ def test_load() -> None:
 | 
				
			||||||
def test_save(tmp_path: Path) -> None:
 | 
					def test_save(tmp_path: Path) -> None:
 | 
				
			||||||
    # Arrange
 | 
					    # Arrange
 | 
				
			||||||
    im = hopper()
 | 
					    im = hopper()
 | 
				
			||||||
    tmpfile = str(tmp_path / "temp.grib")
 | 
					    tmpfile = tmp_path / "temp.grib"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Act / Assert: stub cannot save without an implemented handler
 | 
					    # Act / Assert: stub cannot save without an implemented handler
 | 
				
			||||||
    with pytest.raises(OSError):
 | 
					    with pytest.raises(OSError):
 | 
				
			||||||
| 
						 | 
					@ -79,8 +79,8 @@ def test_handler(tmp_path: Path) -> None:
 | 
				
			||||||
        im.load()
 | 
					        im.load()
 | 
				
			||||||
        assert handler.is_loaded()
 | 
					        assert handler.is_loaded()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        temp_file = str(tmp_path / "temp.grib")
 | 
					        temp_file = tmp_path / "temp.grib"
 | 
				
			||||||
        im.save(temp_file)
 | 
					        im.save(temp_file)
 | 
				
			||||||
        assert handler.saved
 | 
					        assert handler.saved
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    GribStubImagePlugin._handler = None
 | 
					    GribStubImagePlugin.register_handler(None)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -43,7 +43,7 @@ def test_save() -> None:
 | 
				
			||||||
    # Arrange
 | 
					    # Arrange
 | 
				
			||||||
    with Image.open(TEST_FILE) as im:
 | 
					    with Image.open(TEST_FILE) as im:
 | 
				
			||||||
        dummy_fp = BytesIO()
 | 
					        dummy_fp = BytesIO()
 | 
				
			||||||
        dummy_filename = "dummy.filename"
 | 
					        dummy_filename = "dummy.h5"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Act / Assert: stub cannot save without an implemented handler
 | 
					        # Act / Assert: stub cannot save without an implemented handler
 | 
				
			||||||
        with pytest.raises(OSError):
 | 
					        with pytest.raises(OSError):
 | 
				
			||||||
| 
						 | 
					@ -81,8 +81,8 @@ def test_handler(tmp_path: Path) -> None:
 | 
				
			||||||
        im.load()
 | 
					        im.load()
 | 
				
			||||||
        assert handler.is_loaded()
 | 
					        assert handler.is_loaded()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        temp_file = str(tmp_path / "temp.h5")
 | 
					        temp_file = tmp_path / "temp.h5"
 | 
				
			||||||
        im.save(temp_file)
 | 
					        im.save(temp_file)
 | 
				
			||||||
        assert handler.saved
 | 
					        assert handler.saved
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Hdf5StubImagePlugin._handler = None
 | 
					    Hdf5StubImagePlugin.register_handler(None)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -32,14 +32,18 @@ def test_sanity() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_load() -> None:
 | 
					def test_load() -> None:
 | 
				
			||||||
    with Image.open(TEST_FILE) as im:
 | 
					    with Image.open(TEST_FILE) as im:
 | 
				
			||||||
        assert im.load()[0, 0] == (0, 0, 0, 0)
 | 
					        px = im.load()
 | 
				
			||||||
 | 
					        assert px is not None
 | 
				
			||||||
 | 
					        assert px[0, 0] == (0, 0, 0, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Test again now that it has already been loaded once
 | 
					        # Test again now that it has already been loaded once
 | 
				
			||||||
        assert im.load()[0, 0] == (0, 0, 0, 0)
 | 
					        px = im.load()
 | 
				
			||||||
 | 
					        assert px is not None
 | 
				
			||||||
 | 
					        assert px[0, 0] == (0, 0, 0, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_save(tmp_path: Path) -> None:
 | 
					def test_save(tmp_path: Path) -> None:
 | 
				
			||||||
    temp_file = str(tmp_path / "temp.icns")
 | 
					    temp_file = tmp_path / "temp.icns"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open(TEST_FILE) as im:
 | 
					    with Image.open(TEST_FILE) as im:
 | 
				
			||||||
        im.save(temp_file)
 | 
					        im.save(temp_file)
 | 
				
			||||||
| 
						 | 
					@ -56,7 +60,7 @@ def test_save(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_save_append_images(tmp_path: Path) -> None:
 | 
					def test_save_append_images(tmp_path: Path) -> None:
 | 
				
			||||||
    temp_file = str(tmp_path / "temp.icns")
 | 
					    temp_file = tmp_path / "temp.icns"
 | 
				
			||||||
    provided_im = Image.new("RGBA", (32, 32), (255, 0, 0, 128))
 | 
					    provided_im = Image.new("RGBA", (32, 32), (255, 0, 0, 128))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open(TEST_FILE) as im:
 | 
					    with Image.open(TEST_FILE) as im:
 | 
				
			||||||
| 
						 | 
					@ -65,6 +69,7 @@ def test_save_append_images(tmp_path: Path) -> None:
 | 
				
			||||||
        assert_image_similar_tofile(im, temp_file, 1)
 | 
					        assert_image_similar_tofile(im, temp_file, 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        with Image.open(temp_file) as reread:
 | 
					        with Image.open(temp_file) as reread:
 | 
				
			||||||
 | 
					            assert isinstance(reread, IcnsImagePlugin.IcnsImageFile)
 | 
				
			||||||
            reread.size = (16, 16)
 | 
					            reread.size = (16, 16)
 | 
				
			||||||
            reread.load(2)
 | 
					            reread.load(2)
 | 
				
			||||||
            assert_image_equal(reread, provided_im)
 | 
					            assert_image_equal(reread, provided_im)
 | 
				
			||||||
| 
						 | 
					@ -86,6 +91,7 @@ def test_sizes() -> None:
 | 
				
			||||||
    # Check that we can load all of the sizes, and that the final pixel
 | 
					    # Check that we can load all of the sizes, and that the final pixel
 | 
				
			||||||
    # dimensions are as expected
 | 
					    # dimensions are as expected
 | 
				
			||||||
    with Image.open(TEST_FILE) as im:
 | 
					    with Image.open(TEST_FILE) as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, IcnsImagePlugin.IcnsImageFile)
 | 
				
			||||||
        for w, h, r in im.info["sizes"]:
 | 
					        for w, h, r in im.info["sizes"]:
 | 
				
			||||||
            wr = w * r
 | 
					            wr = w * r
 | 
				
			||||||
            hr = h * r
 | 
					            hr = h * r
 | 
				
			||||||
| 
						 | 
					@ -114,6 +120,7 @@ def test_older_icon() -> None:
 | 
				
			||||||
            wr = w * r
 | 
					            wr = w * r
 | 
				
			||||||
            hr = h * r
 | 
					            hr = h * r
 | 
				
			||||||
            with Image.open("Tests/images/pillow2.icns") as im2:
 | 
					            with Image.open("Tests/images/pillow2.icns") as im2:
 | 
				
			||||||
 | 
					                assert isinstance(im2, IcnsImagePlugin.IcnsImageFile)
 | 
				
			||||||
                im2.size = (w, h)
 | 
					                im2.size = (w, h)
 | 
				
			||||||
                im2.load(r)
 | 
					                im2.load(r)
 | 
				
			||||||
                assert im2.mode == "RGBA"
 | 
					                assert im2.mode == "RGBA"
 | 
				
			||||||
| 
						 | 
					@ -131,6 +138,7 @@ def test_jp2_icon() -> None:
 | 
				
			||||||
            wr = w * r
 | 
					            wr = w * r
 | 
				
			||||||
            hr = h * r
 | 
					            hr = h * r
 | 
				
			||||||
            with Image.open("Tests/images/pillow3.icns") as im2:
 | 
					            with Image.open("Tests/images/pillow3.icns") as im2:
 | 
				
			||||||
 | 
					                assert isinstance(im2, IcnsImagePlugin.IcnsImageFile)
 | 
				
			||||||
                im2.size = (w, h)
 | 
					                im2.size = (w, h)
 | 
				
			||||||
                im2.load(r)
 | 
					                im2.load(r)
 | 
				
			||||||
                assert im2.mode == "RGBA"
 | 
					                assert im2.mode == "RGBA"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -24,7 +24,9 @@ def test_sanity() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_load() -> None:
 | 
					def test_load() -> None:
 | 
				
			||||||
    with Image.open(TEST_ICO_FILE) as im:
 | 
					    with Image.open(TEST_ICO_FILE) as im:
 | 
				
			||||||
        assert im.load()[0, 0] == (1, 1, 9, 255)
 | 
					        px = im.load()
 | 
				
			||||||
 | 
					        assert px is not None
 | 
				
			||||||
 | 
					        assert px[0, 0] == (1, 1, 9, 255)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_mask() -> None:
 | 
					def test_mask() -> None:
 | 
				
			||||||
| 
						 | 
					@ -39,7 +41,7 @@ def test_black_and_white() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_palette(tmp_path: Path) -> None:
 | 
					def test_palette(tmp_path: Path) -> None:
 | 
				
			||||||
    temp_file = str(tmp_path / "temp.ico")
 | 
					    temp_file = tmp_path / "temp.ico"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    im = Image.new("P", (16, 16))
 | 
					    im = Image.new("P", (16, 16))
 | 
				
			||||||
    im.save(temp_file)
 | 
					    im.save(temp_file)
 | 
				
			||||||
| 
						 | 
					@ -75,6 +77,7 @@ def test_save_to_bytes() -> None:
 | 
				
			||||||
    # The other one
 | 
					    # The other one
 | 
				
			||||||
    output.seek(0)
 | 
					    output.seek(0)
 | 
				
			||||||
    with Image.open(output) as reloaded:
 | 
					    with Image.open(output) as reloaded:
 | 
				
			||||||
 | 
					        assert isinstance(reloaded, IcoImagePlugin.IcoImageFile)
 | 
				
			||||||
        reloaded.size = (32, 32)
 | 
					        reloaded.size = (32, 32)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        assert im.mode == reloaded.mode
 | 
					        assert im.mode == reloaded.mode
 | 
				
			||||||
| 
						 | 
					@ -86,12 +89,13 @@ def test_save_to_bytes() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_getpixel(tmp_path: Path) -> None:
 | 
					def test_getpixel(tmp_path: Path) -> None:
 | 
				
			||||||
    temp_file = str(tmp_path / "temp.ico")
 | 
					    temp_file = tmp_path / "temp.ico"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    im = hopper()
 | 
					    im = hopper()
 | 
				
			||||||
    im.save(temp_file, "ico", sizes=[(32, 32), (64, 64)])
 | 
					    im.save(temp_file, "ico", sizes=[(32, 32), (64, 64)])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open(temp_file) as reloaded:
 | 
					    with Image.open(temp_file) as reloaded:
 | 
				
			||||||
 | 
					        assert isinstance(reloaded, IcoImagePlugin.IcoImageFile)
 | 
				
			||||||
        reloaded.load()
 | 
					        reloaded.load()
 | 
				
			||||||
        reloaded.size = (32, 32)
 | 
					        reloaded.size = (32, 32)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -99,8 +103,8 @@ def test_getpixel(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_no_duplicates(tmp_path: Path) -> None:
 | 
					def test_no_duplicates(tmp_path: Path) -> None:
 | 
				
			||||||
    temp_file = str(tmp_path / "temp.ico")
 | 
					    temp_file = tmp_path / "temp.ico"
 | 
				
			||||||
    temp_file2 = str(tmp_path / "temp2.ico")
 | 
					    temp_file2 = tmp_path / "temp2.ico"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    im = hopper()
 | 
					    im = hopper()
 | 
				
			||||||
    sizes = [(32, 32), (64, 64)]
 | 
					    sizes = [(32, 32), (64, 64)]
 | 
				
			||||||
| 
						 | 
					@ -113,8 +117,8 @@ def test_no_duplicates(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_different_bit_depths(tmp_path: Path) -> None:
 | 
					def test_different_bit_depths(tmp_path: Path) -> None:
 | 
				
			||||||
    temp_file = str(tmp_path / "temp.ico")
 | 
					    temp_file = tmp_path / "temp.ico"
 | 
				
			||||||
    temp_file2 = str(tmp_path / "temp2.ico")
 | 
					    temp_file2 = tmp_path / "temp2.ico"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    im = hopper()
 | 
					    im = hopper()
 | 
				
			||||||
    im.save(temp_file, "ico", bitmap_format="bmp", sizes=[(128, 128)])
 | 
					    im.save(temp_file, "ico", bitmap_format="bmp", sizes=[(128, 128)])
 | 
				
			||||||
| 
						 | 
					@ -130,8 +134,8 @@ def test_different_bit_depths(tmp_path: Path) -> None:
 | 
				
			||||||
    assert os.path.getsize(temp_file) != os.path.getsize(temp_file2)
 | 
					    assert os.path.getsize(temp_file) != os.path.getsize(temp_file2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Test that only matching sizes of different bit depths are saved
 | 
					    # Test that only matching sizes of different bit depths are saved
 | 
				
			||||||
    temp_file3 = str(tmp_path / "temp3.ico")
 | 
					    temp_file3 = tmp_path / "temp3.ico"
 | 
				
			||||||
    temp_file4 = str(tmp_path / "temp4.ico")
 | 
					    temp_file4 = tmp_path / "temp4.ico"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    im.save(temp_file3, "ico", bitmap_format="bmp", sizes=[(128, 128)])
 | 
					    im.save(temp_file3, "ico", bitmap_format="bmp", sizes=[(128, 128)])
 | 
				
			||||||
    im.save(
 | 
					    im.save(
 | 
				
			||||||
| 
						 | 
					@ -165,6 +169,7 @@ def test_save_to_bytes_bmp(mode: str) -> None:
 | 
				
			||||||
    # The other one
 | 
					    # The other one
 | 
				
			||||||
    output.seek(0)
 | 
					    output.seek(0)
 | 
				
			||||||
    with Image.open(output) as reloaded:
 | 
					    with Image.open(output) as reloaded:
 | 
				
			||||||
 | 
					        assert isinstance(reloaded, IcoImagePlugin.IcoImageFile)
 | 
				
			||||||
        reloaded.size = (32, 32)
 | 
					        reloaded.size = (32, 32)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        assert "RGBA" == reloaded.mode
 | 
					        assert "RGBA" == reloaded.mode
 | 
				
			||||||
| 
						 | 
					@ -176,6 +181,7 @@ def test_save_to_bytes_bmp(mode: str) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_incorrect_size() -> None:
 | 
					def test_incorrect_size() -> None:
 | 
				
			||||||
    with Image.open(TEST_ICO_FILE) as im:
 | 
					    with Image.open(TEST_ICO_FILE) as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, IcoImagePlugin.IcoImageFile)
 | 
				
			||||||
        with pytest.raises(ValueError):
 | 
					        with pytest.raises(ValueError):
 | 
				
			||||||
            im.size = (1, 1)
 | 
					            im.size = (1, 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -184,7 +190,7 @@ def test_save_256x256(tmp_path: Path) -> None:
 | 
				
			||||||
    """Issue #2264 https://github.com/python-pillow/Pillow/issues/2264"""
 | 
					    """Issue #2264 https://github.com/python-pillow/Pillow/issues/2264"""
 | 
				
			||||||
    # Arrange
 | 
					    # Arrange
 | 
				
			||||||
    with Image.open("Tests/images/hopper_256x256.ico") as im:
 | 
					    with Image.open("Tests/images/hopper_256x256.ico") as im:
 | 
				
			||||||
        outfile = str(tmp_path / "temp_saved_hopper_256x256.ico")
 | 
					        outfile = tmp_path / "temp_saved_hopper_256x256.ico"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Act
 | 
					        # Act
 | 
				
			||||||
        im.save(outfile)
 | 
					        im.save(outfile)
 | 
				
			||||||
| 
						 | 
					@ -200,7 +206,7 @@ def test_only_save_relevant_sizes(tmp_path: Path) -> None:
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    # Arrange
 | 
					    # Arrange
 | 
				
			||||||
    with Image.open("Tests/images/python.ico") as im:  # 16x16, 32x32, 48x48
 | 
					    with Image.open("Tests/images/python.ico") as im:  # 16x16, 32x32, 48x48
 | 
				
			||||||
        outfile = str(tmp_path / "temp_saved_python.ico")
 | 
					        outfile = tmp_path / "temp_saved_python.ico"
 | 
				
			||||||
        # Act
 | 
					        # Act
 | 
				
			||||||
        im.save(outfile)
 | 
					        im.save(outfile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -213,10 +219,11 @@ def test_save_append_images(tmp_path: Path) -> None:
 | 
				
			||||||
    # append_images should be used for scaled down versions of the image
 | 
					    # append_images should be used for scaled down versions of the image
 | 
				
			||||||
    im = hopper("RGBA")
 | 
					    im = hopper("RGBA")
 | 
				
			||||||
    provided_im = Image.new("RGBA", (32, 32), (255, 0, 0))
 | 
					    provided_im = Image.new("RGBA", (32, 32), (255, 0, 0))
 | 
				
			||||||
    outfile = str(tmp_path / "temp_saved_multi_icon.ico")
 | 
					    outfile = tmp_path / "temp_saved_multi_icon.ico"
 | 
				
			||||||
    im.save(outfile, sizes=[(32, 32), (128, 128)], append_images=[provided_im])
 | 
					    im.save(outfile, sizes=[(32, 32), (128, 128)], append_images=[provided_im])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open(outfile) as reread:
 | 
					    with Image.open(outfile) as reread:
 | 
				
			||||||
 | 
					        assert isinstance(reread, IcoImagePlugin.IcoImageFile)
 | 
				
			||||||
        assert_image_equal(reread, hopper("RGBA"))
 | 
					        assert_image_equal(reread, hopper("RGBA"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        reread.size = (32, 32)
 | 
					        reread.size = (32, 32)
 | 
				
			||||||
| 
						 | 
					@ -233,7 +240,7 @@ def test_unexpected_size() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_draw_reloaded(tmp_path: Path) -> None:
 | 
					def test_draw_reloaded(tmp_path: Path) -> None:
 | 
				
			||||||
    with Image.open(TEST_ICO_FILE) as im:
 | 
					    with Image.open(TEST_ICO_FILE) as im:
 | 
				
			||||||
        outfile = str(tmp_path / "temp_saved_hopper_draw.ico")
 | 
					        outfile = tmp_path / "temp_saved_hopper_draw.ico"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        draw = ImageDraw.Draw(im)
 | 
					        draw = ImageDraw.Draw(im)
 | 
				
			||||||
        draw.line((0, 0) + im.size, "#f00")
 | 
					        draw.line((0, 0) + im.size, "#f00")
 | 
				
			||||||
| 
						 | 
					@ -243,17 +250,15 @@ def test_draw_reloaded(tmp_path: Path) -> None:
 | 
				
			||||||
        assert_image_equal_tofile(im, "Tests/images/hopper_draw.ico")
 | 
					        assert_image_equal_tofile(im, "Tests/images/hopper_draw.ico")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_truncated_mask() -> None:
 | 
					def test_truncated_mask(monkeypatch: pytest.MonkeyPatch) -> None:
 | 
				
			||||||
    # 1 bpp
 | 
					    # 1 bpp
 | 
				
			||||||
    with open("Tests/images/hopper_mask.ico", "rb") as fp:
 | 
					    with open("Tests/images/hopper_mask.ico", "rb") as fp:
 | 
				
			||||||
        data = fp.read()
 | 
					        data = fp.read()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ImageFile.LOAD_TRUNCATED_IMAGES = True
 | 
					    monkeypatch.setattr(ImageFile, "LOAD_TRUNCATED_IMAGES", True)
 | 
				
			||||||
    data = data[:-3]
 | 
					    data = data[:-3]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
    with Image.open(io.BytesIO(data)) as im:
 | 
					    with Image.open(io.BytesIO(data)) as im:
 | 
				
			||||||
            with Image.open("Tests/images/hopper_mask.png") as expected:
 | 
					 | 
				
			||||||
        assert im.mode == "1"
 | 
					        assert im.mode == "1"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # 32 bpp
 | 
					    # 32 bpp
 | 
				
			||||||
| 
						 | 
					@ -265,5 +270,3 @@ def test_truncated_mask() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open(io.BytesIO(data)) as im:
 | 
					    with Image.open(io.BytesIO(data)) as im:
 | 
				
			||||||
        assert im.mode == "RGB"
 | 
					        assert im.mode == "RGB"
 | 
				
			||||||
    finally:
 | 
					 | 
				
			||||||
        ImageFile.LOAD_TRUNCATED_IMAGES = False
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -23,7 +23,7 @@ def test_sanity() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_name_limit(tmp_path: Path) -> None:
 | 
					def test_name_limit(tmp_path: Path) -> None:
 | 
				
			||||||
    out = str(tmp_path / ("name_limit_test" * 7 + ".im"))
 | 
					    out = tmp_path / ("name_limit_test" * 7 + ".im")
 | 
				
			||||||
    with Image.open(TEST_IM) as im:
 | 
					    with Image.open(TEST_IM) as im:
 | 
				
			||||||
        im.save(out)
 | 
					        im.save(out)
 | 
				
			||||||
    assert filecmp.cmp(out, "Tests/images/hopper_long_name.im")
 | 
					    assert filecmp.cmp(out, "Tests/images/hopper_long_name.im")
 | 
				
			||||||
| 
						 | 
					@ -31,12 +31,12 @@ def test_name_limit(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@pytest.mark.skipif(is_pypy(), reason="Requires CPython")
 | 
					@pytest.mark.skipif(is_pypy(), reason="Requires CPython")
 | 
				
			||||||
def test_unclosed_file() -> None:
 | 
					def test_unclosed_file() -> None:
 | 
				
			||||||
    def open() -> None:
 | 
					    def open_test_image() -> None:
 | 
				
			||||||
        im = Image.open(TEST_IM)
 | 
					        im = Image.open(TEST_IM)
 | 
				
			||||||
        im.load()
 | 
					        im.load()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with pytest.warns(ResourceWarning):
 | 
					    with pytest.warns(ResourceWarning):
 | 
				
			||||||
        open()
 | 
					        open_test_image()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_closed_file() -> None:
 | 
					def test_closed_file() -> None:
 | 
				
			||||||
| 
						 | 
					@ -68,12 +68,14 @@ def test_tell() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_n_frames() -> None:
 | 
					def test_n_frames() -> None:
 | 
				
			||||||
    with Image.open(TEST_IM) as im:
 | 
					    with Image.open(TEST_IM) as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, ImImagePlugin.ImImageFile)
 | 
				
			||||||
        assert im.n_frames == 1
 | 
					        assert im.n_frames == 1
 | 
				
			||||||
        assert not im.is_animated
 | 
					        assert not im.is_animated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_eoferror() -> None:
 | 
					def test_eoferror() -> None:
 | 
				
			||||||
    with Image.open(TEST_IM) as im:
 | 
					    with Image.open(TEST_IM) as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, ImImagePlugin.ImImageFile)
 | 
				
			||||||
        n_frames = im.n_frames
 | 
					        n_frames = im.n_frames
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Test seeking past the last frame
 | 
					        # Test seeking past the last frame
 | 
				
			||||||
| 
						 | 
					@ -87,7 +89,7 @@ def test_eoferror() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@pytest.mark.parametrize("mode", ("RGB", "P", "PA"))
 | 
					@pytest.mark.parametrize("mode", ("RGB", "P", "PA"))
 | 
				
			||||||
def test_roundtrip(mode: str, tmp_path: Path) -> None:
 | 
					def test_roundtrip(mode: str, tmp_path: Path) -> None:
 | 
				
			||||||
    out = str(tmp_path / "temp.im")
 | 
					    out = tmp_path / "temp.im"
 | 
				
			||||||
    im = hopper(mode)
 | 
					    im = hopper(mode)
 | 
				
			||||||
    im.save(out)
 | 
					    im.save(out)
 | 
				
			||||||
    assert_image_equal_tofile(im, out)
 | 
					    assert_image_equal_tofile(im, out)
 | 
				
			||||||
| 
						 | 
					@ -98,7 +100,7 @@ def test_small_palette(tmp_path: Path) -> None:
 | 
				
			||||||
    colors = [0, 1, 2]
 | 
					    colors = [0, 1, 2]
 | 
				
			||||||
    im.putpalette(colors)
 | 
					    im.putpalette(colors)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    out = str(tmp_path / "temp.im")
 | 
					    out = tmp_path / "temp.im"
 | 
				
			||||||
    im.save(out)
 | 
					    im.save(out)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open(out) as reloaded:
 | 
					    with Image.open(out) as reloaded:
 | 
				
			||||||
| 
						 | 
					@ -106,7 +108,7 @@ def test_small_palette(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_save_unsupported_mode(tmp_path: Path) -> None:
 | 
					def test_save_unsupported_mode(tmp_path: Path) -> None:
 | 
				
			||||||
    out = str(tmp_path / "temp.im")
 | 
					    out = tmp_path / "temp.im"
 | 
				
			||||||
    im = hopper("HSV")
 | 
					    im = hopper("HSV")
 | 
				
			||||||
    with pytest.raises(ValueError):
 | 
					    with pytest.raises(ValueError):
 | 
				
			||||||
        im.save(out)
 | 
					        im.save(out)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -58,10 +58,7 @@ def test_getiptcinfo_fotostation() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Assert
 | 
					    # Assert
 | 
				
			||||||
    assert iptc is not None
 | 
					    assert iptc is not None
 | 
				
			||||||
    for tag in iptc.keys():
 | 
					    assert 240 in (tag[0] for tag in iptc.keys()), "FotoStation tag not found"
 | 
				
			||||||
        if tag[0] == 240:
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
    pytest.fail("FotoStation tag not found")
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_getiptcinfo_zero_padding() -> None:
 | 
					def test_getiptcinfo_zero_padding() -> None:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -83,7 +83,7 @@ class TestFileJpeg:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @pytest.mark.parametrize("size", ((1, 0), (0, 1), (0, 0)))
 | 
					    @pytest.mark.parametrize("size", ((1, 0), (0, 1), (0, 0)))
 | 
				
			||||||
    def test_zero(self, size: tuple[int, int], tmp_path: Path) -> None:
 | 
					    def test_zero(self, size: tuple[int, int], tmp_path: Path) -> None:
 | 
				
			||||||
        f = str(tmp_path / "temp.jpg")
 | 
					        f = tmp_path / "temp.jpg"
 | 
				
			||||||
        im = Image.new("RGB", size)
 | 
					        im = Image.new("RGB", size)
 | 
				
			||||||
        with pytest.raises(ValueError):
 | 
					        with pytest.raises(ValueError):
 | 
				
			||||||
            im.save(f)
 | 
					            im.save(f)
 | 
				
			||||||
| 
						 | 
					@ -91,6 +91,7 @@ class TestFileJpeg:
 | 
				
			||||||
    def test_app(self) -> None:
 | 
					    def test_app(self) -> None:
 | 
				
			||||||
        # Test APP/COM reader (@PIL135)
 | 
					        # Test APP/COM reader (@PIL135)
 | 
				
			||||||
        with Image.open(TEST_FILE) as im:
 | 
					        with Image.open(TEST_FILE) as im:
 | 
				
			||||||
 | 
					            assert isinstance(im, JpegImagePlugin.JpegImageFile)
 | 
				
			||||||
            assert im.applist[0] == ("APP0", b"JFIF\x00\x01\x01\x01\x00`\x00`\x00\x00")
 | 
					            assert im.applist[0] == ("APP0", b"JFIF\x00\x01\x01\x01\x00`\x00`\x00\x00")
 | 
				
			||||||
            assert im.applist[1] == (
 | 
					            assert im.applist[1] == (
 | 
				
			||||||
                "COM",
 | 
					                "COM",
 | 
				
			||||||
| 
						 | 
					@ -181,6 +182,10 @@ class TestFileJpeg:
 | 
				
			||||||
        assert test(100, 200) == (100, 200)
 | 
					        assert test(100, 200) == (100, 200)
 | 
				
			||||||
        assert test(0) is None  # square pixels
 | 
					        assert test(0) is None  # square pixels
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_dpi_jfif_cm(self) -> None:
 | 
				
			||||||
 | 
					        with Image.open("Tests/images/jfif_unit_cm.jpg") as im:
 | 
				
			||||||
 | 
					            assert im.info["dpi"] == (2.54, 5.08)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @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"
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
| 
						 | 
					@ -190,7 +195,7 @@ class TestFileJpeg:
 | 
				
			||||||
            icc_profile = im1.info["icc_profile"]
 | 
					            icc_profile = im1.info["icc_profile"]
 | 
				
			||||||
            assert len(icc_profile) == 3144
 | 
					            assert len(icc_profile) == 3144
 | 
				
			||||||
            # Roundtrip via physical file.
 | 
					            # Roundtrip via physical file.
 | 
				
			||||||
            f = str(tmp_path / "temp.jpg")
 | 
					            f = tmp_path / "temp.jpg"
 | 
				
			||||||
            im1.save(f, icc_profile=icc_profile)
 | 
					            im1.save(f, icc_profile=icc_profile)
 | 
				
			||||||
        with Image.open(f) as im2:
 | 
					        with Image.open(f) as im2:
 | 
				
			||||||
            assert im2.info.get("icc_profile") == icc_profile
 | 
					            assert im2.info.get("icc_profile") == icc_profile
 | 
				
			||||||
| 
						 | 
					@ -234,7 +239,7 @@ class TestFileJpeg:
 | 
				
			||||||
        # Sometimes the meta data on the icc_profile block is bigger than
 | 
					        # Sometimes the meta data on the icc_profile block is bigger than
 | 
				
			||||||
        # Image.MAXBLOCK or the image size.
 | 
					        # Image.MAXBLOCK or the image size.
 | 
				
			||||||
        with Image.open("Tests/images/icc_profile_big.jpg") as im:
 | 
					        with Image.open("Tests/images/icc_profile_big.jpg") as im:
 | 
				
			||||||
            f = str(tmp_path / "temp.jpg")
 | 
					            f = tmp_path / "temp.jpg"
 | 
				
			||||||
            icc_profile = im.info["icc_profile"]
 | 
					            icc_profile = im.info["icc_profile"]
 | 
				
			||||||
            # Should not raise OSError for image with icc larger than image size.
 | 
					            # Should not raise OSError for image with icc larger than image size.
 | 
				
			||||||
            im.save(
 | 
					            im.save(
 | 
				
			||||||
| 
						 | 
					@ -246,11 +251,11 @@ class TestFileJpeg:
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        with Image.open("Tests/images/flower2.jpg") as im:
 | 
					        with Image.open("Tests/images/flower2.jpg") as im:
 | 
				
			||||||
            f = str(tmp_path / "temp2.jpg")
 | 
					            f = tmp_path / "temp2.jpg"
 | 
				
			||||||
            im.save(f, progressive=True, quality=94, icc_profile=b" " * 53955)
 | 
					            im.save(f, progressive=True, quality=94, icc_profile=b" " * 53955)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        with Image.open("Tests/images/flower2.jpg") as im:
 | 
					        with Image.open("Tests/images/flower2.jpg") as im:
 | 
				
			||||||
            f = str(tmp_path / "temp3.jpg")
 | 
					            f = tmp_path / "temp3.jpg"
 | 
				
			||||||
            im.save(f, progressive=True, quality=94, exif=b" " * 43668)
 | 
					            im.save(f, progressive=True, quality=94, exif=b" " * 43668)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_optimize(self) -> None:
 | 
					    def test_optimize(self) -> None:
 | 
				
			||||||
| 
						 | 
					@ -264,7 +269,7 @@ class TestFileJpeg:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_optimize_large_buffer(self, tmp_path: Path) -> None:
 | 
					    def test_optimize_large_buffer(self, tmp_path: Path) -> None:
 | 
				
			||||||
        # https://github.com/python-pillow/Pillow/issues/148
 | 
					        # https://github.com/python-pillow/Pillow/issues/148
 | 
				
			||||||
        f = str(tmp_path / "temp.jpg")
 | 
					        f = tmp_path / "temp.jpg"
 | 
				
			||||||
        # this requires ~ 1.5x Image.MAXBLOCK
 | 
					        # this requires ~ 1.5x Image.MAXBLOCK
 | 
				
			||||||
        im = Image.new("RGB", (4096, 4096), 0xFF3333)
 | 
					        im = Image.new("RGB", (4096, 4096), 0xFF3333)
 | 
				
			||||||
        im.save(f, format="JPEG", optimize=True)
 | 
					        im.save(f, format="JPEG", optimize=True)
 | 
				
			||||||
| 
						 | 
					@ -277,17 +282,20 @@ class TestFileJpeg:
 | 
				
			||||||
        assert not im2.info.get("progressive")
 | 
					        assert not im2.info.get("progressive")
 | 
				
			||||||
        assert im3.info.get("progressive")
 | 
					        assert im3.info.get("progressive")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if features.check_feature("mozjpeg"):
 | 
				
			||||||
 | 
					            assert_image_similar(im1, im3, 9.39)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
            assert_image_equal(im1, im3)
 | 
					            assert_image_equal(im1, im3)
 | 
				
			||||||
        assert im1_bytes >= im3_bytes
 | 
					        assert im1_bytes >= im3_bytes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_progressive_large_buffer(self, tmp_path: Path) -> None:
 | 
					    def test_progressive_large_buffer(self, tmp_path: Path) -> None:
 | 
				
			||||||
        f = str(tmp_path / "temp.jpg")
 | 
					        f = tmp_path / "temp.jpg"
 | 
				
			||||||
        # this requires ~ 1.5x Image.MAXBLOCK
 | 
					        # this requires ~ 1.5x Image.MAXBLOCK
 | 
				
			||||||
        im = Image.new("RGB", (4096, 4096), 0xFF3333)
 | 
					        im = Image.new("RGB", (4096, 4096), 0xFF3333)
 | 
				
			||||||
        im.save(f, format="JPEG", progressive=True)
 | 
					        im.save(f, format="JPEG", progressive=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_progressive_large_buffer_highest_quality(self, tmp_path: Path) -> None:
 | 
					    def test_progressive_large_buffer_highest_quality(self, tmp_path: Path) -> None:
 | 
				
			||||||
        f = str(tmp_path / "temp.jpg")
 | 
					        f = tmp_path / "temp.jpg"
 | 
				
			||||||
        im = self.gen_random_image((255, 255))
 | 
					        im = self.gen_random_image((255, 255))
 | 
				
			||||||
        # this requires more bytes than pixels in the image
 | 
					        # this requires more bytes than pixels in the image
 | 
				
			||||||
        im.save(f, format="JPEG", progressive=True, quality=100)
 | 
					        im.save(f, format="JPEG", progressive=True, quality=100)
 | 
				
			||||||
| 
						 | 
					@ -300,7 +308,7 @@ class TestFileJpeg:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_large_exif(self, tmp_path: Path) -> None:
 | 
					    def test_large_exif(self, tmp_path: Path) -> None:
 | 
				
			||||||
        # https://github.com/python-pillow/Pillow/issues/148
 | 
					        # https://github.com/python-pillow/Pillow/issues/148
 | 
				
			||||||
        f = str(tmp_path / "temp.jpg")
 | 
					        f = tmp_path / "temp.jpg"
 | 
				
			||||||
        im = hopper()
 | 
					        im = hopper()
 | 
				
			||||||
        im.save(f, "JPEG", quality=90, exif=b"1" * 65533)
 | 
					        im.save(f, "JPEG", quality=90, exif=b"1" * 65533)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -309,6 +317,8 @@ class TestFileJpeg:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_exif_typeerror(self) -> None:
 | 
					    def test_exif_typeerror(self) -> None:
 | 
				
			||||||
        with Image.open("Tests/images/exif_typeerror.jpg") as im:
 | 
					        with Image.open("Tests/images/exif_typeerror.jpg") as im:
 | 
				
			||||||
 | 
					            assert isinstance(im, JpegImagePlugin.JpegImageFile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # Should not raise a TypeError
 | 
					            # Should not raise a TypeError
 | 
				
			||||||
            im._getexif()
 | 
					            im._getexif()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -328,7 +338,7 @@ class TestFileJpeg:
 | 
				
			||||||
            assert exif[gps_index] == expected_exif_gps
 | 
					            assert exif[gps_index] == expected_exif_gps
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Writing
 | 
					        # Writing
 | 
				
			||||||
        f = str(tmp_path / "temp.jpg")
 | 
					        f = tmp_path / "temp.jpg"
 | 
				
			||||||
        exif = Image.Exif()
 | 
					        exif = Image.Exif()
 | 
				
			||||||
        exif[gps_index] = expected_exif_gps
 | 
					        exif[gps_index] = expected_exif_gps
 | 
				
			||||||
        hopper().save(f, exif=exif)
 | 
					        hopper().save(f, exif=exif)
 | 
				
			||||||
| 
						 | 
					@ -349,7 +359,6 @@ class TestFileJpeg:
 | 
				
			||||||
            assert exif.get_ifd(0x8825) == {}
 | 
					            assert exif.get_ifd(0x8825) == {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            transposed = ImageOps.exif_transpose(im)
 | 
					            transposed = ImageOps.exif_transpose(im)
 | 
				
			||||||
        assert transposed is not None
 | 
					 | 
				
			||||||
        exif = transposed.getexif()
 | 
					        exif = transposed.getexif()
 | 
				
			||||||
        assert exif.get_ifd(0x8825) == {}
 | 
					        assert exif.get_ifd(0x8825) == {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -420,6 +429,10 @@ class TestFileJpeg:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        im2 = self.roundtrip(hopper(), progressive=1)
 | 
					        im2 = self.roundtrip(hopper(), progressive=1)
 | 
				
			||||||
        im3 = self.roundtrip(hopper(), progression=1)  # compatibility
 | 
					        im3 = self.roundtrip(hopper(), progression=1)  # compatibility
 | 
				
			||||||
 | 
					        if features.check_feature("mozjpeg"):
 | 
				
			||||||
 | 
					            assert_image_similar(im1, im2, 9.39)
 | 
				
			||||||
 | 
					            assert_image_similar(im1, im3, 9.39)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
            assert_image_equal(im1, im2)
 | 
					            assert_image_equal(im1, im2)
 | 
				
			||||||
            assert_image_equal(im1, im3)
 | 
					            assert_image_equal(im1, im3)
 | 
				
			||||||
        assert im2.info.get("progressive")
 | 
					        assert im2.info.get("progressive")
 | 
				
			||||||
| 
						 | 
					@ -490,20 +503,21 @@ class TestFileJpeg:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_mp(self) -> None:
 | 
					    def test_mp(self) -> None:
 | 
				
			||||||
        with Image.open("Tests/images/pil_sample_rgb.jpg") as im:
 | 
					        with Image.open("Tests/images/pil_sample_rgb.jpg") as im:
 | 
				
			||||||
 | 
					            assert isinstance(im, JpegImagePlugin.JpegImageFile)
 | 
				
			||||||
            assert im._getmp() is None
 | 
					            assert im._getmp() is None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_quality_keep(self, tmp_path: Path) -> None:
 | 
					    def test_quality_keep(self, tmp_path: Path) -> None:
 | 
				
			||||||
        # RGB
 | 
					        # RGB
 | 
				
			||||||
        with Image.open("Tests/images/hopper.jpg") as im:
 | 
					        with Image.open("Tests/images/hopper.jpg") as im:
 | 
				
			||||||
            f = str(tmp_path / "temp.jpg")
 | 
					            f = tmp_path / "temp.jpg"
 | 
				
			||||||
            im.save(f, quality="keep")
 | 
					            im.save(f, quality="keep")
 | 
				
			||||||
        # Grayscale
 | 
					        # Grayscale
 | 
				
			||||||
        with Image.open("Tests/images/hopper_gray.jpg") as im:
 | 
					        with Image.open("Tests/images/hopper_gray.jpg") as im:
 | 
				
			||||||
            f = str(tmp_path / "temp.jpg")
 | 
					            f = tmp_path / "temp.jpg"
 | 
				
			||||||
            im.save(f, quality="keep")
 | 
					            im.save(f, quality="keep")
 | 
				
			||||||
        # CMYK
 | 
					        # CMYK
 | 
				
			||||||
        with Image.open("Tests/images/pil_sample_cmyk.jpg") as im:
 | 
					        with Image.open("Tests/images/pil_sample_cmyk.jpg") as im:
 | 
				
			||||||
            f = str(tmp_path / "temp.jpg")
 | 
					            f = tmp_path / "temp.jpg"
 | 
				
			||||||
            im.save(f, quality="keep")
 | 
					            im.save(f, quality="keep")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_junk_jpeg_header(self) -> None:
 | 
					    def test_junk_jpeg_header(self) -> None:
 | 
				
			||||||
| 
						 | 
					@ -520,12 +534,13 @@ class TestFileJpeg:
 | 
				
			||||||
    @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"
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    def test_truncated_jpeg_should_read_all_the_data(self) -> None:
 | 
					    def test_truncated_jpeg_should_read_all_the_data(
 | 
				
			||||||
 | 
					        self, monkeypatch: pytest.MonkeyPatch
 | 
				
			||||||
 | 
					    ) -> None:
 | 
				
			||||||
        filename = "Tests/images/truncated_jpeg.jpg"
 | 
					        filename = "Tests/images/truncated_jpeg.jpg"
 | 
				
			||||||
        ImageFile.LOAD_TRUNCATED_IMAGES = True
 | 
					        monkeypatch.setattr(ImageFile, "LOAD_TRUNCATED_IMAGES", True)
 | 
				
			||||||
        with Image.open(filename) as im:
 | 
					        with Image.open(filename) as im:
 | 
				
			||||||
            im.load()
 | 
					            im.load()
 | 
				
			||||||
            ImageFile.LOAD_TRUNCATED_IMAGES = False
 | 
					 | 
				
			||||||
            assert im.getbbox() is not None
 | 
					            assert im.getbbox() is not None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_truncated_jpeg_throws_oserror(self) -> None:
 | 
					    def test_truncated_jpeg_throws_oserror(self) -> None:
 | 
				
			||||||
| 
						 | 
					@ -547,12 +562,14 @@ class TestFileJpeg:
 | 
				
			||||||
            with Image.open(test_file) as im:
 | 
					            with Image.open(test_file) as im:
 | 
				
			||||||
                im.save(b, "JPEG", qtables=[[n] * 64] * n)
 | 
					                im.save(b, "JPEG", qtables=[[n] * 64] * n)
 | 
				
			||||||
            with Image.open(b) as im:
 | 
					            with Image.open(b) as im:
 | 
				
			||||||
 | 
					                assert isinstance(im, JpegImagePlugin.JpegImageFile)
 | 
				
			||||||
                assert len(im.quantization) == n
 | 
					                assert len(im.quantization) == n
 | 
				
			||||||
                reloaded = self.roundtrip(im, qtables="keep")
 | 
					                reloaded = self.roundtrip(im, qtables="keep")
 | 
				
			||||||
                assert im.quantization == reloaded.quantization
 | 
					                assert im.quantization == reloaded.quantization
 | 
				
			||||||
                assert max(reloaded.quantization[0]) <= 255
 | 
					                assert max(reloaded.quantization[0]) <= 255
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        with Image.open("Tests/images/hopper.jpg") as im:
 | 
					        with Image.open("Tests/images/hopper.jpg") as im:
 | 
				
			||||||
 | 
					            assert isinstance(im, JpegImagePlugin.JpegImageFile)
 | 
				
			||||||
            qtables = im.quantization
 | 
					            qtables = im.quantization
 | 
				
			||||||
            reloaded = self.roundtrip(im, qtables=qtables, subsampling=0)
 | 
					            reloaded = self.roundtrip(im, qtables=qtables, subsampling=0)
 | 
				
			||||||
            assert im.quantization == reloaded.quantization
 | 
					            assert im.quantization == reloaded.quantization
 | 
				
			||||||
| 
						 | 
					@ -652,6 +669,7 @@ class TestFileJpeg:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_load_16bit_qtables(self) -> None:
 | 
					    def test_load_16bit_qtables(self) -> None:
 | 
				
			||||||
        with Image.open("Tests/images/hopper_16bit_qtables.jpg") as im:
 | 
					        with Image.open("Tests/images/hopper_16bit_qtables.jpg") as im:
 | 
				
			||||||
 | 
					            assert isinstance(im, JpegImagePlugin.JpegImageFile)
 | 
				
			||||||
            assert len(im.quantization) == 2
 | 
					            assert len(im.quantization) == 2
 | 
				
			||||||
            assert len(im.quantization[0]) == 64
 | 
					            assert len(im.quantization[0]) == 64
 | 
				
			||||||
            assert max(im.quantization[0]) > 255
 | 
					            assert max(im.quantization[0]) > 255
 | 
				
			||||||
| 
						 | 
					@ -694,6 +712,7 @@ class TestFileJpeg:
 | 
				
			||||||
    @pytest.mark.skipif(not djpeg_available(), reason="djpeg not available")
 | 
					    @pytest.mark.skipif(not djpeg_available(), reason="djpeg not available")
 | 
				
			||||||
    def test_load_djpeg(self) -> None:
 | 
					    def test_load_djpeg(self) -> None:
 | 
				
			||||||
        with Image.open(TEST_FILE) as img:
 | 
					        with Image.open(TEST_FILE) as img:
 | 
				
			||||||
 | 
					            assert isinstance(img, JpegImagePlugin.JpegImageFile)
 | 
				
			||||||
            img.load_djpeg()
 | 
					            img.load_djpeg()
 | 
				
			||||||
            assert_image_similar_tofile(img, TEST_FILE, 5)
 | 
					            assert_image_similar_tofile(img, TEST_FILE, 5)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -715,7 +734,7 @@ class TestFileJpeg:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_MAXBLOCK_scaling(self, tmp_path: Path) -> None:
 | 
					    def test_MAXBLOCK_scaling(self, tmp_path: Path) -> None:
 | 
				
			||||||
        im = self.gen_random_image((512, 512))
 | 
					        im = self.gen_random_image((512, 512))
 | 
				
			||||||
        f = str(tmp_path / "temp.jpeg")
 | 
					        f = tmp_path / "temp.jpeg"
 | 
				
			||||||
        im.save(f, quality=100, optimize=True)
 | 
					        im.save(f, quality=100, optimize=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        with Image.open(f) as reloaded:
 | 
					        with Image.open(f) as reloaded:
 | 
				
			||||||
| 
						 | 
					@ -751,7 +770,7 @@ class TestFileJpeg:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_save_tiff_with_dpi(self, tmp_path: Path) -> None:
 | 
					    def test_save_tiff_with_dpi(self, tmp_path: Path) -> None:
 | 
				
			||||||
        # Arrange
 | 
					        # Arrange
 | 
				
			||||||
        outfile = str(tmp_path / "temp.tif")
 | 
					        outfile = 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"])
 | 
				
			||||||
| 
						 | 
					@ -762,7 +781,7 @@ class TestFileJpeg:
 | 
				
			||||||
                assert im.info["dpi"] == reloaded.info["dpi"]
 | 
					                assert im.info["dpi"] == reloaded.info["dpi"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_save_dpi_rounding(self, tmp_path: Path) -> None:
 | 
					    def test_save_dpi_rounding(self, tmp_path: Path) -> None:
 | 
				
			||||||
        outfile = str(tmp_path / "temp.jpg")
 | 
					        outfile = tmp_path / "temp.jpg"
 | 
				
			||||||
        with Image.open("Tests/images/hopper.jpg") as im:
 | 
					        with Image.open("Tests/images/hopper.jpg") as im:
 | 
				
			||||||
            im.save(outfile, dpi=(72.2, 72.2))
 | 
					            im.save(outfile, dpi=(72.2, 72.2))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -848,7 +867,7 @@ class TestFileJpeg:
 | 
				
			||||||
            exif = im.getexif()
 | 
					            exif = im.getexif()
 | 
				
			||||||
            assert exif[282] == 180
 | 
					            assert exif[282] == 180
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            out = str(tmp_path / "out.jpg")
 | 
					            out = tmp_path / "out.jpg"
 | 
				
			||||||
            with warnings.catch_warnings():
 | 
					            with warnings.catch_warnings():
 | 
				
			||||||
                warnings.simplefilter("error")
 | 
					                warnings.simplefilter("error")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -898,6 +917,7 @@ class TestFileJpeg:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_photoshop_malformed_and_multiple(self) -> None:
 | 
					    def test_photoshop_malformed_and_multiple(self) -> None:
 | 
				
			||||||
        with Image.open("Tests/images/app13-multiple.jpg") as im:
 | 
					        with Image.open("Tests/images/app13-multiple.jpg") as im:
 | 
				
			||||||
 | 
					            assert isinstance(im, JpegImagePlugin.JpegImageFile)
 | 
				
			||||||
            assert "photoshop" in im.info
 | 
					            assert "photoshop" in im.info
 | 
				
			||||||
            assert 24 == len(im.info["photoshop"])
 | 
					            assert 24 == len(im.info["photoshop"])
 | 
				
			||||||
            apps_13_lengths = [len(v) for k, v in im.applist if k == "APP13"]
 | 
					            apps_13_lengths = [len(v) for k, v in im.applist if k == "APP13"]
 | 
				
			||||||
| 
						 | 
					@ -923,7 +943,7 @@ class TestFileJpeg:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_jpeg_magic_number(self, monkeypatch: pytest.MonkeyPatch) -> None:
 | 
					    def test_jpeg_magic_number(self, monkeypatch: pytest.MonkeyPatch) -> None:
 | 
				
			||||||
        size = 4097
 | 
					        size = 4097
 | 
				
			||||||
        buffer = BytesIO(b"\xFF" * size)  # Many xFF bytes
 | 
					        buffer = BytesIO(b"\xff" * size)  # Many xff bytes
 | 
				
			||||||
        max_pos = 0
 | 
					        max_pos = 0
 | 
				
			||||||
        orig_read = buffer.read
 | 
					        orig_read = buffer.read
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -994,14 +1014,19 @@ class TestFileJpeg:
 | 
				
			||||||
                assert im.getxmp() == {"xmpmeta": None}
 | 
					                assert im.getxmp() == {"xmpmeta": None}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_save_xmp(self, tmp_path: Path) -> None:
 | 
					    def test_save_xmp(self, tmp_path: Path) -> None:
 | 
				
			||||||
        f = str(tmp_path / "temp.jpg")
 | 
					        f = tmp_path / "temp.jpg"
 | 
				
			||||||
        im = hopper()
 | 
					        im = hopper()
 | 
				
			||||||
        im.save(f, xmp=b"XMP test")
 | 
					        im.save(f, xmp=b"XMP test")
 | 
				
			||||||
        with Image.open(f) as reloaded:
 | 
					        with Image.open(f) as reloaded:
 | 
				
			||||||
            assert reloaded.info["xmp"] == b"XMP test"
 | 
					            assert reloaded.info["xmp"] == b"XMP test"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        im.info["xmp"] = b"1" * 65504
 | 
					            # Check that XMP is not saved from image info
 | 
				
			||||||
        im.save(f)
 | 
					            reloaded.save(f)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        with Image.open(f) as reloaded:
 | 
				
			||||||
 | 
					            assert "xmp" not in reloaded.info
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        im.save(f, xmp=b"1" * 65504)
 | 
				
			||||||
        with Image.open(f) as reloaded:
 | 
					        with Image.open(f) as reloaded:
 | 
				
			||||||
            assert reloaded.info["xmp"] == b"1" * 65504
 | 
					            assert reloaded.info["xmp"] == b"1" * 65504
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1009,7 +1034,7 @@ class TestFileJpeg:
 | 
				
			||||||
            im.save(f, xmp=b"1" * 65505)
 | 
					            im.save(f, xmp=b"1" * 65505)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @pytest.mark.timeout(timeout=1)
 | 
					    @pytest.mark.timeout(timeout=1)
 | 
				
			||||||
    def test_eof(self) -> None:
 | 
					    def test_eof(self, monkeypatch: pytest.MonkeyPatch) -> None:
 | 
				
			||||||
        # Even though this decoder never says that it is finished
 | 
					        # Even though this decoder never says that it is finished
 | 
				
			||||||
        # the image should still end when there is no new data
 | 
					        # the image should still end when there is no new data
 | 
				
			||||||
        class InfiniteMockPyDecoder(ImageFile.PyDecoder):
 | 
					        class InfiniteMockPyDecoder(ImageFile.PyDecoder):
 | 
				
			||||||
| 
						 | 
					@ -1022,11 +1047,10 @@ class TestFileJpeg:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        with Image.open(TEST_FILE) as im:
 | 
					        with Image.open(TEST_FILE) as im:
 | 
				
			||||||
            im.tile = [
 | 
					            im.tile = [
 | 
				
			||||||
                ("INFINITE", (0, 0, 128, 128), 0, ("RGB", 0, 1)),
 | 
					                ImageFile._Tile("INFINITE", (0, 0, 128, 128), 0, ("RGB", 0, 1)),
 | 
				
			||||||
            ]
 | 
					            ]
 | 
				
			||||||
            ImageFile.LOAD_TRUNCATED_IMAGES = True
 | 
					            monkeypatch.setattr(ImageFile, "LOAD_TRUNCATED_IMAGES", True)
 | 
				
			||||||
            im.load()
 | 
					            im.load()
 | 
				
			||||||
            ImageFile.LOAD_TRUNCATED_IMAGES = False
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_separate_tables(self) -> None:
 | 
					    def test_separate_tables(self) -> None:
 | 
				
			||||||
        im = hopper()
 | 
					        im = hopper()
 | 
				
			||||||
| 
						 | 
					@ -1069,6 +1093,7 @@ class TestFileJpeg:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_deprecation(self) -> None:
 | 
					    def test_deprecation(self) -> None:
 | 
				
			||||||
        with Image.open(TEST_FILE) as im:
 | 
					        with Image.open(TEST_FILE) as im:
 | 
				
			||||||
 | 
					            assert isinstance(im, JpegImagePlugin.JpegImageFile)
 | 
				
			||||||
            with pytest.warns(DeprecationWarning):
 | 
					            with pytest.warns(DeprecationWarning):
 | 
				
			||||||
                assert im.huffman_ac == {}
 | 
					                assert im.huffman_ac == {}
 | 
				
			||||||
            with pytest.warns(DeprecationWarning):
 | 
					            with pytest.warns(DeprecationWarning):
 | 
				
			||||||
| 
						 | 
					@ -1079,7 +1104,7 @@ class TestFileJpeg:
 | 
				
			||||||
@skip_unless_feature("jpg")
 | 
					@skip_unless_feature("jpg")
 | 
				
			||||||
class TestFileCloseW32:
 | 
					class TestFileCloseW32:
 | 
				
			||||||
    def test_fd_leak(self, tmp_path: Path) -> None:
 | 
					    def test_fd_leak(self, tmp_path: Path) -> None:
 | 
				
			||||||
        tmpfile = str(tmp_path / "temp.jpg")
 | 
					        tmpfile = tmp_path / "temp.jpg"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        with Image.open("Tests/images/hopper.jpg") as im:
 | 
					        with Image.open("Tests/images/hopper.jpg") as im:
 | 
				
			||||||
            im.save(tmpfile)
 | 
					            im.save(tmpfile)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -63,6 +63,7 @@ def test_sanity() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open("Tests/images/test-card-lossless.jp2") as im:
 | 
					    with Image.open("Tests/images/test-card-lossless.jp2") as im:
 | 
				
			||||||
        px = im.load()
 | 
					        px = im.load()
 | 
				
			||||||
 | 
					        assert px is not None
 | 
				
			||||||
        assert px[0, 0] == (0, 0, 0)
 | 
					        assert px[0, 0] == (0, 0, 0)
 | 
				
			||||||
        assert im.mode == "RGB"
 | 
					        assert im.mode == "RGB"
 | 
				
			||||||
        assert im.size == (640, 480)
 | 
					        assert im.size == (640, 480)
 | 
				
			||||||
| 
						 | 
					@ -98,7 +99,7 @@ def test_bytesio(card: ImageFile.ImageFile) -> None:
 | 
				
			||||||
def test_lossless(card: ImageFile.ImageFile, tmp_path: Path) -> None:
 | 
					def test_lossless(card: ImageFile.ImageFile, tmp_path: Path) -> None:
 | 
				
			||||||
    with Image.open("Tests/images/test-card-lossless.jp2") as im:
 | 
					    with Image.open("Tests/images/test-card-lossless.jp2") as im:
 | 
				
			||||||
        im.load()
 | 
					        im.load()
 | 
				
			||||||
        outfile = str(tmp_path / "temp_test-card.png")
 | 
					        outfile = tmp_path / "temp_test-card.png"
 | 
				
			||||||
        im.save(outfile)
 | 
					        im.save(outfile)
 | 
				
			||||||
    assert_image_similar(im, card, 1.0e-3)
 | 
					    assert_image_similar(im, card, 1.0e-3)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -181,14 +182,11 @@ def test_load_dpi() -> None:
 | 
				
			||||||
        assert "dpi" not in im.info
 | 
					        assert "dpi" not in im.info
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_restricted_icc_profile() -> None:
 | 
					def test_restricted_icc_profile(monkeypatch: pytest.MonkeyPatch) -> None:
 | 
				
			||||||
    ImageFile.LOAD_TRUNCATED_IMAGES = True
 | 
					    monkeypatch.setattr(ImageFile, "LOAD_TRUNCATED_IMAGES", True)
 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
    # JPEG2000 image with a restricted ICC profile and a known colorspace
 | 
					    # JPEG2000 image with a restricted ICC profile and a known colorspace
 | 
				
			||||||
    with Image.open("Tests/images/balloon_eciRGBv2_aware.jp2") as im:
 | 
					    with Image.open("Tests/images/balloon_eciRGBv2_aware.jp2") as im:
 | 
				
			||||||
        assert im.mode == "RGB"
 | 
					        assert im.mode == "RGB"
 | 
				
			||||||
    finally:
 | 
					 | 
				
			||||||
        ImageFile.LOAD_TRUNCATED_IMAGES = False
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@pytest.mark.skipif(
 | 
					@pytest.mark.skipif(
 | 
				
			||||||
| 
						 | 
					@ -215,7 +213,7 @@ def test_header_errors() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_layers_type(card: ImageFile.ImageFile, tmp_path: Path) -> None:
 | 
					def test_layers_type(card: ImageFile.ImageFile, tmp_path: Path) -> None:
 | 
				
			||||||
    outfile = str(tmp_path / "temp_layers.jp2")
 | 
					    outfile = tmp_path / "temp_layers.jp2"
 | 
				
			||||||
    for quality_layers in [[100, 50, 10], (100, 50, 10), None]:
 | 
					    for quality_layers in [[100, 50, 10], (100, 50, 10), None]:
 | 
				
			||||||
        card.save(outfile, quality_layers=quality_layers)
 | 
					        card.save(outfile, quality_layers=quality_layers)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -230,12 +228,14 @@ def test_layers(card: ImageFile.ImageFile) -> None:
 | 
				
			||||||
    out.seek(0)
 | 
					    out.seek(0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open(out) as im:
 | 
					    with Image.open(out) as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, Jpeg2KImagePlugin.Jpeg2KImageFile)
 | 
				
			||||||
        im.layers = 1
 | 
					        im.layers = 1
 | 
				
			||||||
        im.load()
 | 
					        im.load()
 | 
				
			||||||
        assert_image_similar(im, card, 13)
 | 
					        assert_image_similar(im, card, 13)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    out.seek(0)
 | 
					    out.seek(0)
 | 
				
			||||||
    with Image.open(out) as im:
 | 
					    with Image.open(out) as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, Jpeg2KImagePlugin.Jpeg2KImageFile)
 | 
				
			||||||
        im.layers = 3
 | 
					        im.layers = 3
 | 
				
			||||||
        im.load()
 | 
					        im.load()
 | 
				
			||||||
        assert_image_similar(im, card, 0.4)
 | 
					        assert_image_similar(im, card, 0.4)
 | 
				
			||||||
| 
						 | 
					@ -291,7 +291,7 @@ def test_mct(card: ImageFile.ImageFile) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_sgnd(tmp_path: Path) -> None:
 | 
					def test_sgnd(tmp_path: Path) -> None:
 | 
				
			||||||
    outfile = str(tmp_path / "temp.jp2")
 | 
					    outfile = tmp_path / "temp.jp2"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    im = Image.new("L", (1, 1))
 | 
					    im = Image.new("L", (1, 1))
 | 
				
			||||||
    im.save(outfile)
 | 
					    im.save(outfile)
 | 
				
			||||||
| 
						 | 
					@ -315,6 +315,18 @@ def test_rgba(ext: str) -> None:
 | 
				
			||||||
        assert im.mode == "RGBA"
 | 
					        assert im.mode == "RGBA"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_grayscale_four_channels() -> None:
 | 
				
			||||||
 | 
					    with open("Tests/images/rgb_trns_ycbc.jp2", "rb") as fp:
 | 
				
			||||||
 | 
					        data = fp.read()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Change color space to OPJ_CLRSPC_GRAY
 | 
				
			||||||
 | 
					    data = data[:76] + b"\x11" + data[77:]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    with Image.open(BytesIO(data)) as im:
 | 
				
			||||||
 | 
					        im.load()
 | 
				
			||||||
 | 
					        assert im.mode == "RGBA"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@pytest.mark.skipif(
 | 
					@pytest.mark.skipif(
 | 
				
			||||||
    not os.path.exists(EXTRA_DIR), reason="Extra image files not installed"
 | 
					    not os.path.exists(EXTRA_DIR), reason="Extra image files not installed"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
| 
						 | 
					@ -325,6 +337,18 @@ def test_cmyk() -> None:
 | 
				
			||||||
        assert im.getpixel((0, 0)) == (185, 134, 0, 0)
 | 
					        assert im.getpixel((0, 0)) == (185, 134, 0, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@pytest.mark.skipif(
 | 
				
			||||||
 | 
					    not os.path.exists(EXTRA_DIR), reason="Extra image files not installed"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					@skip_unless_feature_version("jpg_2000", "2.5.3")
 | 
				
			||||||
 | 
					def test_cmyk_save() -> None:
 | 
				
			||||||
 | 
					    with Image.open(f"{EXTRA_DIR}/issue205.jp2") as jp2:
 | 
				
			||||||
 | 
					        assert jp2.mode == "CMYK"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        im = roundtrip(jp2)
 | 
				
			||||||
 | 
					        assert_image_equal(im, jp2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@pytest.mark.parametrize("ext", (".j2k", ".jp2"))
 | 
					@pytest.mark.parametrize("ext", (".j2k", ".jp2"))
 | 
				
			||||||
def test_16bit_monochrome_has_correct_mode(ext: str) -> None:
 | 
					def test_16bit_monochrome_has_correct_mode(ext: str) -> None:
 | 
				
			||||||
    with Image.open("Tests/images/16bit.cropped" + ext) as im:
 | 
					    with Image.open("Tests/images/16bit.cropped" + ext) as im:
 | 
				
			||||||
| 
						 | 
					@ -412,6 +436,7 @@ def test_subsampling_decode(name: str) -> None:
 | 
				
			||||||
def test_pclr() -> None:
 | 
					def test_pclr() -> None:
 | 
				
			||||||
    with Image.open(f"{EXTRA_DIR}/issue104_jpxstream.jp2") as im:
 | 
					    with Image.open(f"{EXTRA_DIR}/issue104_jpxstream.jp2") as im:
 | 
				
			||||||
        assert im.mode == "P"
 | 
					        assert im.mode == "P"
 | 
				
			||||||
 | 
					        assert im.palette is not None
 | 
				
			||||||
        assert len(im.palette.colors) == 256
 | 
					        assert len(im.palette.colors) == 256
 | 
				
			||||||
        assert im.palette.colors[(255, 255, 255)] == 0
 | 
					        assert im.palette.colors[(255, 255, 255)] == 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -419,12 +444,14 @@ def test_pclr() -> None:
 | 
				
			||||||
        f"{EXTRA_DIR}/147af3f1083de4393666b7d99b01b58b_signal_sigsegv_130c531_6155_5136.jp2"
 | 
					        f"{EXTRA_DIR}/147af3f1083de4393666b7d99b01b58b_signal_sigsegv_130c531_6155_5136.jp2"
 | 
				
			||||||
    ) as im:
 | 
					    ) as im:
 | 
				
			||||||
        assert im.mode == "P"
 | 
					        assert im.mode == "P"
 | 
				
			||||||
 | 
					        assert im.palette is not None
 | 
				
			||||||
        assert len(im.palette.colors) == 139
 | 
					        assert len(im.palette.colors) == 139
 | 
				
			||||||
        assert im.palette.colors[(0, 0, 0, 0)] == 0
 | 
					        assert im.palette.colors[(0, 0, 0, 0)] == 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_comment() -> None:
 | 
					def test_comment() -> None:
 | 
				
			||||||
    with Image.open("Tests/images/comment.jp2") as im:
 | 
					    for path in ("Tests/images/9bit.j2k", "Tests/images/comment.jp2"):
 | 
				
			||||||
 | 
					        with Image.open(path) as im:
 | 
				
			||||||
            assert im.info["comment"] == b"Created by OpenJPEG version 2.5.0"
 | 
					            assert im.info["comment"] == b"Created by OpenJPEG version 2.5.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Test an image that is truncated partway through a codestream
 | 
					    # Test an image that is truncated partway through a codestream
 | 
				
			||||||
| 
						 | 
					@ -479,8 +506,7 @@ def test_plt_marker(card: ImageFile.ImageFile) -> None:
 | 
				
			||||||
    out.seek(0)
 | 
					    out.seek(0)
 | 
				
			||||||
    while True:
 | 
					    while True:
 | 
				
			||||||
        marker = out.read(2)
 | 
					        marker = out.read(2)
 | 
				
			||||||
        if not marker:
 | 
					        assert marker, "End of stream without PLT"
 | 
				
			||||||
            pytest.fail("End of stream without PLT")
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        jp2_boxid = _binary.i16be(marker)
 | 
					        jp2_boxid = _binary.i16be(marker)
 | 
				
			||||||
        if jp2_boxid == 0xFF4F:
 | 
					        if jp2_boxid == 0xFF4F:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -36,14 +36,11 @@ class LibTiffTestCase:
 | 
				
			||||||
        im.load()
 | 
					        im.load()
 | 
				
			||||||
        im.getdata()
 | 
					        im.getdata()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try:
 | 
					        assert isinstance(im, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
        assert im._compression == "group4"
 | 
					        assert im._compression == "group4"
 | 
				
			||||||
        except AttributeError:
 | 
					 | 
				
			||||||
            print("No _compression")
 | 
					 | 
				
			||||||
            print(dir(im))
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # can we write it back out, in a different form.
 | 
					        # can we write it back out, in a different form.
 | 
				
			||||||
        out = str(tmp_path / "temp.png")
 | 
					        out = tmp_path / "temp.png"
 | 
				
			||||||
        im.save(out)
 | 
					        im.save(out)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        out_bytes = io.BytesIO()
 | 
					        out_bytes = io.BytesIO()
 | 
				
			||||||
| 
						 | 
					@ -127,7 +124,7 @@ class TestFileLibTiff(LibTiffTestCase):
 | 
				
			||||||
        """Checking to see that the saved image is the same as what we wrote"""
 | 
					        """Checking to see that the saved image is the same as what we wrote"""
 | 
				
			||||||
        test_file = "Tests/images/hopper_g4_500.tif"
 | 
					        test_file = "Tests/images/hopper_g4_500.tif"
 | 
				
			||||||
        with Image.open(test_file) as orig:
 | 
					        with Image.open(test_file) as orig:
 | 
				
			||||||
            out = str(tmp_path / "temp.tif")
 | 
					            out = tmp_path / "temp.tif"
 | 
				
			||||||
            rot = orig.transpose(Image.Transpose.ROTATE_90)
 | 
					            rot = orig.transpose(Image.Transpose.ROTATE_90)
 | 
				
			||||||
            assert rot.size == (500, 500)
 | 
					            assert rot.size == (500, 500)
 | 
				
			||||||
            rot.save(out)
 | 
					            rot.save(out)
 | 
				
			||||||
| 
						 | 
					@ -155,8 +152,9 @@ class TestFileLibTiff(LibTiffTestCase):
 | 
				
			||||||
    @pytest.mark.parametrize("legacy_api", (False, True))
 | 
					    @pytest.mark.parametrize("legacy_api", (False, True))
 | 
				
			||||||
    def test_write_metadata(self, legacy_api: bool, tmp_path: Path) -> None:
 | 
					    def test_write_metadata(self, legacy_api: bool, tmp_path: Path) -> None:
 | 
				
			||||||
        """Test metadata writing through libtiff"""
 | 
					        """Test metadata writing through libtiff"""
 | 
				
			||||||
        f = str(tmp_path / "temp.tiff")
 | 
					        f = tmp_path / "temp.tiff"
 | 
				
			||||||
        with Image.open("Tests/images/hopper_g4.tif") as img:
 | 
					        with Image.open("Tests/images/hopper_g4.tif") as img:
 | 
				
			||||||
 | 
					            assert isinstance(img, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
            img.save(f, tiffinfo=img.tag)
 | 
					            img.save(f, tiffinfo=img.tag)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if legacy_api:
 | 
					            if legacy_api:
 | 
				
			||||||
| 
						 | 
					@ -174,6 +172,7 @@ class TestFileLibTiff(LibTiffTestCase):
 | 
				
			||||||
        ]
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        with Image.open(f) as loaded:
 | 
					        with Image.open(f) as loaded:
 | 
				
			||||||
 | 
					            assert isinstance(loaded, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
            if legacy_api:
 | 
					            if legacy_api:
 | 
				
			||||||
                reloaded = loaded.tag.named()
 | 
					                reloaded = loaded.tag.named()
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
| 
						 | 
					@ -216,6 +215,7 @@ class TestFileLibTiff(LibTiffTestCase):
 | 
				
			||||||
        # Exclude ones that have special meaning
 | 
					        # Exclude ones that have special meaning
 | 
				
			||||||
        # that we're already testing them
 | 
					        # that we're already testing them
 | 
				
			||||||
        with Image.open("Tests/images/hopper_g4.tif") as im:
 | 
					        with Image.open("Tests/images/hopper_g4.tif") as im:
 | 
				
			||||||
 | 
					            assert isinstance(im, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
            for tag in im.tag_v2:
 | 
					            for tag in im.tag_v2:
 | 
				
			||||||
                try:
 | 
					                try:
 | 
				
			||||||
                    del core_items[tag]
 | 
					                    del core_items[tag]
 | 
				
			||||||
| 
						 | 
					@ -251,7 +251,7 @@ class TestFileLibTiff(LibTiffTestCase):
 | 
				
			||||||
            # Extra samples really doesn't make sense in this application.
 | 
					            # Extra samples really doesn't make sense in this application.
 | 
				
			||||||
            del new_ifd[338]
 | 
					            del new_ifd[338]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            out = str(tmp_path / "temp.tif")
 | 
					            out = tmp_path / "temp.tif"
 | 
				
			||||||
            monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", True)
 | 
					            monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            im.save(out, tiffinfo=new_ifd)
 | 
					            im.save(out, tiffinfo=new_ifd)
 | 
				
			||||||
| 
						 | 
					@ -313,14 +313,15 @@ class TestFileLibTiff(LibTiffTestCase):
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        def check_tags(
 | 
					        def check_tags(
 | 
				
			||||||
            tiffinfo: TiffImagePlugin.ImageFileDirectory_v2 | dict[int, str]
 | 
					            tiffinfo: TiffImagePlugin.ImageFileDirectory_v2 | dict[int, str],
 | 
				
			||||||
        ) -> None:
 | 
					        ) -> None:
 | 
				
			||||||
            im = hopper()
 | 
					            im = hopper()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            out = str(tmp_path / "temp.tif")
 | 
					            out = tmp_path / "temp.tif"
 | 
				
			||||||
            im.save(out, tiffinfo=tiffinfo)
 | 
					            im.save(out, tiffinfo=tiffinfo)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            with Image.open(out) as reloaded:
 | 
					            with Image.open(out) as reloaded:
 | 
				
			||||||
 | 
					                assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
                for tag, value in tiffinfo.items():
 | 
					                for tag, value in tiffinfo.items():
 | 
				
			||||||
                    reloaded_value = reloaded.tag_v2[tag]
 | 
					                    reloaded_value = reloaded.tag_v2[tag]
 | 
				
			||||||
                    if (
 | 
					                    if (
 | 
				
			||||||
| 
						 | 
					@ -351,14 +352,16 @@ class TestFileLibTiff(LibTiffTestCase):
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_osubfiletype(self, tmp_path: Path) -> None:
 | 
					    def test_osubfiletype(self, tmp_path: Path) -> None:
 | 
				
			||||||
        outfile = str(tmp_path / "temp.tif")
 | 
					        outfile = tmp_path / "temp.tif"
 | 
				
			||||||
        with Image.open("Tests/images/g4_orientation_6.tif") as im:
 | 
					        with Image.open("Tests/images/g4_orientation_6.tif") as im:
 | 
				
			||||||
 | 
					            assert isinstance(im, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
            im.tag_v2[OSUBFILETYPE] = 1
 | 
					            im.tag_v2[OSUBFILETYPE] = 1
 | 
				
			||||||
            im.save(outfile)
 | 
					            im.save(outfile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_subifd(self, tmp_path: Path) -> None:
 | 
					    def test_subifd(self, tmp_path: Path) -> None:
 | 
				
			||||||
        outfile = str(tmp_path / "temp.tif")
 | 
					        outfile = tmp_path / "temp.tif"
 | 
				
			||||||
        with Image.open("Tests/images/g4_orientation_6.tif") as im:
 | 
					        with Image.open("Tests/images/g4_orientation_6.tif") as im:
 | 
				
			||||||
 | 
					            assert isinstance(im, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
            im.tag_v2[SUBIFD] = 10000
 | 
					            im.tag_v2[SUBIFD] = 10000
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # Should not segfault
 | 
					            # Should not segfault
 | 
				
			||||||
| 
						 | 
					@ -369,17 +372,18 @@ class TestFileLibTiff(LibTiffTestCase):
 | 
				
			||||||
    ) -> None:
 | 
					    ) -> None:
 | 
				
			||||||
        monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", True)
 | 
					        monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        out = str(tmp_path / "temp.tif")
 | 
					        out = tmp_path / "temp.tif"
 | 
				
			||||||
        hopper().save(out, tiffinfo={700: b"xmlpacket tag"})
 | 
					        hopper().save(out, tiffinfo={700: b"xmlpacket tag"})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        with Image.open(out) as reloaded:
 | 
					        with Image.open(out) as reloaded:
 | 
				
			||||||
 | 
					            assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
            if 700 in reloaded.tag_v2:
 | 
					            if 700 in reloaded.tag_v2:
 | 
				
			||||||
                assert reloaded.tag_v2[700] == b"xmlpacket tag"
 | 
					                assert reloaded.tag_v2[700] == b"xmlpacket tag"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_int_dpi(self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
 | 
					    def test_int_dpi(self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
 | 
				
			||||||
        # issue #1765
 | 
					        # issue #1765
 | 
				
			||||||
        im = hopper("RGB")
 | 
					        im = hopper("RGB")
 | 
				
			||||||
        out = str(tmp_path / "temp.tif")
 | 
					        out = tmp_path / "temp.tif"
 | 
				
			||||||
        monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", True)
 | 
					        monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", True)
 | 
				
			||||||
        im.save(out, dpi=(72, 72))
 | 
					        im.save(out, dpi=(72, 72))
 | 
				
			||||||
        with Image.open(out) as reloaded:
 | 
					        with Image.open(out) as reloaded:
 | 
				
			||||||
| 
						 | 
					@ -387,7 +391,7 @@ class TestFileLibTiff(LibTiffTestCase):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_g3_compression(self, tmp_path: Path) -> None:
 | 
					    def test_g3_compression(self, tmp_path: Path) -> None:
 | 
				
			||||||
        with Image.open("Tests/images/hopper_g4_500.tif") as i:
 | 
					        with Image.open("Tests/images/hopper_g4_500.tif") as i:
 | 
				
			||||||
            out = str(tmp_path / "temp.tif")
 | 
					            out = tmp_path / "temp.tif"
 | 
				
			||||||
            i.save(out, compression="group3")
 | 
					            i.save(out, compression="group3")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            with Image.open(out) as reread:
 | 
					            with Image.open(out) as reread:
 | 
				
			||||||
| 
						 | 
					@ -404,7 +408,7 @@ class TestFileLibTiff(LibTiffTestCase):
 | 
				
			||||||
            assert b[0] == ord(b"\xe0")
 | 
					            assert b[0] == ord(b"\xe0")
 | 
				
			||||||
            assert b[1] == ord(b"\x01")
 | 
					            assert b[1] == ord(b"\x01")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            out = str(tmp_path / "temp.tif")
 | 
					            out = tmp_path / "temp.tif"
 | 
				
			||||||
            # out = "temp.le.tif"
 | 
					            # out = "temp.le.tif"
 | 
				
			||||||
            im.save(out)
 | 
					            im.save(out)
 | 
				
			||||||
        with Image.open(out) as reread:
 | 
					        with Image.open(out) as reread:
 | 
				
			||||||
| 
						 | 
					@ -424,7 +428,7 @@ class TestFileLibTiff(LibTiffTestCase):
 | 
				
			||||||
            assert b[0] == ord(b"\x01")
 | 
					            assert b[0] == ord(b"\x01")
 | 
				
			||||||
            assert b[1] == ord(b"\xe0")
 | 
					            assert b[1] == ord(b"\xe0")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            out = str(tmp_path / "temp.tif")
 | 
					            out = tmp_path / "temp.tif"
 | 
				
			||||||
            im.save(out)
 | 
					            im.save(out)
 | 
				
			||||||
            with Image.open(out) as reread:
 | 
					            with Image.open(out) as reread:
 | 
				
			||||||
                assert reread.info["compression"] == im.info["compression"]
 | 
					                assert reread.info["compression"] == im.info["compression"]
 | 
				
			||||||
| 
						 | 
					@ -434,12 +438,15 @@ class TestFileLibTiff(LibTiffTestCase):
 | 
				
			||||||
        """Tests String data in info directory"""
 | 
					        """Tests String data in info directory"""
 | 
				
			||||||
        test_file = "Tests/images/hopper_g4_500.tif"
 | 
					        test_file = "Tests/images/hopper_g4_500.tif"
 | 
				
			||||||
        with Image.open(test_file) as orig:
 | 
					        with Image.open(test_file) as orig:
 | 
				
			||||||
            out = str(tmp_path / "temp.tif")
 | 
					            assert isinstance(orig, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            out = tmp_path / "temp.tif"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            orig.tag[269] = "temp.tif"
 | 
					            orig.tag[269] = "temp.tif"
 | 
				
			||||||
            orig.save(out)
 | 
					            orig.save(out)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        with Image.open(out) as reread:
 | 
					        with Image.open(out) as reread:
 | 
				
			||||||
 | 
					            assert isinstance(reread, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
            assert "temp.tif" == reread.tag_v2[269]
 | 
					            assert "temp.tif" == reread.tag_v2[269]
 | 
				
			||||||
            assert "temp.tif" == reread.tag[269][0]
 | 
					            assert "temp.tif" == reread.tag[269][0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -461,7 +468,7 @@ class TestFileLibTiff(LibTiffTestCase):
 | 
				
			||||||
    def test_blur(self, tmp_path: Path) -> None:
 | 
					    def test_blur(self, tmp_path: Path) -> None:
 | 
				
			||||||
        # test case from irc, how to do blur on b/w image
 | 
					        # test case from irc, how to do blur on b/w image
 | 
				
			||||||
        # and save to compressed tif.
 | 
					        # and save to compressed tif.
 | 
				
			||||||
        out = str(tmp_path / "temp.tif")
 | 
					        out = tmp_path / "temp.tif"
 | 
				
			||||||
        with Image.open("Tests/images/pport_g4.tif") as im:
 | 
					        with Image.open("Tests/images/pport_g4.tif") as im:
 | 
				
			||||||
            im = im.convert("L")
 | 
					            im = im.convert("L")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -474,7 +481,7 @@ class TestFileLibTiff(LibTiffTestCase):
 | 
				
			||||||
        # Test various tiff compressions and assert similar image content but reduced
 | 
					        # Test various tiff compressions and assert similar image content but reduced
 | 
				
			||||||
        # file sizes.
 | 
					        # file sizes.
 | 
				
			||||||
        im = hopper("RGB")
 | 
					        im = hopper("RGB")
 | 
				
			||||||
        out = str(tmp_path / "temp.tif")
 | 
					        out = tmp_path / "temp.tif"
 | 
				
			||||||
        im.save(out)
 | 
					        im.save(out)
 | 
				
			||||||
        size_raw = os.path.getsize(out)
 | 
					        size_raw = os.path.getsize(out)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -498,7 +505,7 @@ class TestFileLibTiff(LibTiffTestCase):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_tiff_jpeg_compression(self, tmp_path: Path) -> None:
 | 
					    def test_tiff_jpeg_compression(self, tmp_path: Path) -> None:
 | 
				
			||||||
        im = hopper("RGB")
 | 
					        im = hopper("RGB")
 | 
				
			||||||
        out = str(tmp_path / "temp.tif")
 | 
					        out = tmp_path / "temp.tif"
 | 
				
			||||||
        im.save(out, compression="tiff_jpeg")
 | 
					        im.save(out, compression="tiff_jpeg")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        with Image.open(out) as reloaded:
 | 
					        with Image.open(out) as reloaded:
 | 
				
			||||||
| 
						 | 
					@ -506,7 +513,7 @@ class TestFileLibTiff(LibTiffTestCase):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_tiff_deflate_compression(self, tmp_path: Path) -> None:
 | 
					    def test_tiff_deflate_compression(self, tmp_path: Path) -> None:
 | 
				
			||||||
        im = hopper("RGB")
 | 
					        im = hopper("RGB")
 | 
				
			||||||
        out = str(tmp_path / "temp.tif")
 | 
					        out = tmp_path / "temp.tif"
 | 
				
			||||||
        im.save(out, compression="tiff_deflate")
 | 
					        im.save(out, compression="tiff_deflate")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        with Image.open(out) as reloaded:
 | 
					        with Image.open(out) as reloaded:
 | 
				
			||||||
| 
						 | 
					@ -514,7 +521,7 @@ class TestFileLibTiff(LibTiffTestCase):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_quality(self, tmp_path: Path) -> None:
 | 
					    def test_quality(self, tmp_path: Path) -> None:
 | 
				
			||||||
        im = hopper("RGB")
 | 
					        im = hopper("RGB")
 | 
				
			||||||
        out = str(tmp_path / "temp.tif")
 | 
					        out = tmp_path / "temp.tif"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        with pytest.raises(ValueError):
 | 
					        with pytest.raises(ValueError):
 | 
				
			||||||
            im.save(out, compression="tiff_lzw", quality=50)
 | 
					            im.save(out, compression="tiff_lzw", quality=50)
 | 
				
			||||||
| 
						 | 
					@ -529,7 +536,7 @@ class TestFileLibTiff(LibTiffTestCase):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_cmyk_save(self, tmp_path: Path) -> None:
 | 
					    def test_cmyk_save(self, tmp_path: Path) -> None:
 | 
				
			||||||
        im = hopper("CMYK")
 | 
					        im = hopper("CMYK")
 | 
				
			||||||
        out = str(tmp_path / "temp.tif")
 | 
					        out = tmp_path / "temp.tif"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        im.save(out, compression="tiff_adobe_deflate")
 | 
					        im.save(out, compression="tiff_adobe_deflate")
 | 
				
			||||||
        assert_image_equal_tofile(im, out)
 | 
					        assert_image_equal_tofile(im, out)
 | 
				
			||||||
| 
						 | 
					@ -538,19 +545,20 @@ class TestFileLibTiff(LibTiffTestCase):
 | 
				
			||||||
    def test_palette_save(
 | 
					    def test_palette_save(
 | 
				
			||||||
        self, im: Image.Image, monkeypatch: pytest.MonkeyPatch, tmp_path: Path
 | 
					        self, im: Image.Image, monkeypatch: pytest.MonkeyPatch, tmp_path: Path
 | 
				
			||||||
    ) -> None:
 | 
					    ) -> None:
 | 
				
			||||||
        out = str(tmp_path / "temp.tif")
 | 
					        out = tmp_path / "temp.tif"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", True)
 | 
					        monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", True)
 | 
				
			||||||
        im.save(out)
 | 
					        im.save(out)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        with Image.open(out) as reloaded:
 | 
					        with Image.open(out) as reloaded:
 | 
				
			||||||
            # colormap/palette tag
 | 
					            # colormap/palette tag
 | 
				
			||||||
 | 
					            assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
            assert len(reloaded.tag_v2[320]) == 768
 | 
					            assert len(reloaded.tag_v2[320]) == 768
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @pytest.mark.parametrize("compression", ("tiff_ccitt", "group3", "group4"))
 | 
					    @pytest.mark.parametrize("compression", ("tiff_ccitt", "group3", "group4"))
 | 
				
			||||||
    def test_bw_compression_w_rgb(self, compression: str, tmp_path: Path) -> None:
 | 
					    def test_bw_compression_w_rgb(self, compression: str, tmp_path: Path) -> None:
 | 
				
			||||||
        im = hopper("RGB")
 | 
					        im = hopper("RGB")
 | 
				
			||||||
        out = str(tmp_path / "temp.tif")
 | 
					        out = tmp_path / "temp.tif"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        with pytest.raises(OSError):
 | 
					        with pytest.raises(OSError):
 | 
				
			||||||
            im.save(out, compression=compression)
 | 
					            im.save(out, compression=compression)
 | 
				
			||||||
| 
						 | 
					@ -576,6 +584,7 @@ class TestFileLibTiff(LibTiffTestCase):
 | 
				
			||||||
        with Image.open("Tests/images/multipage.tiff") as im:
 | 
					        with Image.open("Tests/images/multipage.tiff") as im:
 | 
				
			||||||
            # file is a multipage tiff,  10x10 green, 10x10 red, 20x20 blue
 | 
					            # file is a multipage tiff,  10x10 green, 10x10 red, 20x20 blue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            assert isinstance(im, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
            im.seek(0)
 | 
					            im.seek(0)
 | 
				
			||||||
            assert im.size == (10, 10)
 | 
					            assert im.size == (10, 10)
 | 
				
			||||||
            assert im.convert("RGB").getpixel((0, 0)) == (0, 128, 0)
 | 
					            assert im.convert("RGB").getpixel((0, 0)) == (0, 128, 0)
 | 
				
			||||||
| 
						 | 
					@ -595,6 +604,7 @@ class TestFileLibTiff(LibTiffTestCase):
 | 
				
			||||||
        # issue #862
 | 
					        # issue #862
 | 
				
			||||||
        monkeypatch.setattr(TiffImagePlugin, "READ_LIBTIFF", True)
 | 
					        monkeypatch.setattr(TiffImagePlugin, "READ_LIBTIFF", True)
 | 
				
			||||||
        with Image.open("Tests/images/multipage.tiff") as im:
 | 
					        with Image.open("Tests/images/multipage.tiff") as im:
 | 
				
			||||||
 | 
					            assert isinstance(im, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
            frames = im.n_frames
 | 
					            frames = im.n_frames
 | 
				
			||||||
            assert frames == 3
 | 
					            assert frames == 3
 | 
				
			||||||
            for _ in range(frames):
 | 
					            for _ in range(frames):
 | 
				
			||||||
| 
						 | 
					@ -614,6 +624,7 @@ class TestFileLibTiff(LibTiffTestCase):
 | 
				
			||||||
    def test__next(self, monkeypatch: pytest.MonkeyPatch) -> None:
 | 
					    def test__next(self, monkeypatch: pytest.MonkeyPatch) -> None:
 | 
				
			||||||
        monkeypatch.setattr(TiffImagePlugin, "READ_LIBTIFF", True)
 | 
					        monkeypatch.setattr(TiffImagePlugin, "READ_LIBTIFF", True)
 | 
				
			||||||
        with Image.open("Tests/images/hopper.tif") as im:
 | 
					        with Image.open("Tests/images/hopper.tif") as im:
 | 
				
			||||||
 | 
					            assert isinstance(im, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
            assert not im.tag.next
 | 
					            assert not im.tag.next
 | 
				
			||||||
            im.load()
 | 
					            im.load()
 | 
				
			||||||
            assert not im.tag.next
 | 
					            assert not im.tag.next
 | 
				
			||||||
| 
						 | 
					@ -690,25 +701,29 @@ class TestFileLibTiff(LibTiffTestCase):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_save_ycbcr(self, tmp_path: Path) -> None:
 | 
					    def test_save_ycbcr(self, tmp_path: Path) -> None:
 | 
				
			||||||
        im = hopper("YCbCr")
 | 
					        im = hopper("YCbCr")
 | 
				
			||||||
        outfile = str(tmp_path / "temp.tif")
 | 
					        outfile = tmp_path / "temp.tif"
 | 
				
			||||||
        im.save(outfile, compression="jpeg")
 | 
					        im.save(outfile, compression="jpeg")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        with Image.open(outfile) as reloaded:
 | 
					        with Image.open(outfile) as reloaded:
 | 
				
			||||||
 | 
					            assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
            assert reloaded.tag_v2[530] == (1, 1)
 | 
					            assert reloaded.tag_v2[530] == (1, 1)
 | 
				
			||||||
            assert reloaded.tag_v2[532] == (0, 255, 128, 255, 128, 255)
 | 
					            assert reloaded.tag_v2[532] == (0, 255, 128, 255, 128, 255)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_exif_ifd(self) -> None:
 | 
					    def test_exif_ifd(self) -> None:
 | 
				
			||||||
        out = io.BytesIO()
 | 
					        out = io.BytesIO()
 | 
				
			||||||
        with Image.open("Tests/images/tiff_adobe_deflate.tif") as im:
 | 
					        with Image.open("Tests/images/tiff_adobe_deflate.tif") as im:
 | 
				
			||||||
 | 
					            assert isinstance(im, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
            assert im.tag_v2[34665] == 125456
 | 
					            assert im.tag_v2[34665] == 125456
 | 
				
			||||||
            im.save(out, "TIFF")
 | 
					            im.save(out, "TIFF")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            with Image.open(out) as reloaded:
 | 
					            with Image.open(out) as reloaded:
 | 
				
			||||||
 | 
					                assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
                assert 34665 not in reloaded.tag_v2
 | 
					                assert 34665 not in reloaded.tag_v2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            im.save(out, "TIFF", tiffinfo={34665: 125456})
 | 
					            im.save(out, "TIFF", tiffinfo={34665: 125456})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        with Image.open(out) as reloaded:
 | 
					        with Image.open(out) as reloaded:
 | 
				
			||||||
 | 
					            assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
            if Image.core.libtiff_support_custom_tags:
 | 
					            if Image.core.libtiff_support_custom_tags:
 | 
				
			||||||
                assert reloaded.tag_v2[34665] == 125456
 | 
					                assert reloaded.tag_v2[34665] == 125456
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -717,7 +732,7 @@ class TestFileLibTiff(LibTiffTestCase):
 | 
				
			||||||
    ) -> None:
 | 
					    ) -> None:
 | 
				
			||||||
        # issue 1597
 | 
					        # issue 1597
 | 
				
			||||||
        with Image.open("Tests/images/rdf.tif") as im:
 | 
					        with Image.open("Tests/images/rdf.tif") as im:
 | 
				
			||||||
            out = str(tmp_path / "temp.tif")
 | 
					            out = tmp_path / "temp.tif"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", True)
 | 
					            monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", True)
 | 
				
			||||||
            # this shouldn't crash
 | 
					            # this shouldn't crash
 | 
				
			||||||
| 
						 | 
					@ -728,7 +743,7 @@ class TestFileLibTiff(LibTiffTestCase):
 | 
				
			||||||
        # Test TIFF with tag 297 (Page Number) having value of 0 0.
 | 
					        # Test TIFF with tag 297 (Page Number) having value of 0 0.
 | 
				
			||||||
        # The first number is the current page number.
 | 
					        # The first number is the current page number.
 | 
				
			||||||
        # The second is the total number of pages, zero means not available.
 | 
					        # The second is the total number of pages, zero means not available.
 | 
				
			||||||
        outfile = str(tmp_path / "temp.tif")
 | 
					        outfile = tmp_path / "temp.tif"
 | 
				
			||||||
        # Created by printing a page in Chrome to PDF, then:
 | 
					        # Created by printing a page in Chrome to PDF, then:
 | 
				
			||||||
        # /usr/bin/gs -q -sDEVICE=tiffg3 -sOutputFile=total-pages-zero.tif
 | 
					        # /usr/bin/gs -q -sDEVICE=tiffg3 -sOutputFile=total-pages-zero.tif
 | 
				
			||||||
        # -dNOPAUSE /tmp/test.pdf -c quit
 | 
					        # -dNOPAUSE /tmp/test.pdf -c quit
 | 
				
			||||||
| 
						 | 
					@ -740,7 +755,7 @@ class TestFileLibTiff(LibTiffTestCase):
 | 
				
			||||||
    def test_fd_duplication(self, tmp_path: Path) -> None:
 | 
					    def test_fd_duplication(self, tmp_path: Path) -> None:
 | 
				
			||||||
        # https://github.com/python-pillow/Pillow/issues/1651
 | 
					        # https://github.com/python-pillow/Pillow/issues/1651
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        tmpfile = str(tmp_path / "temp.tif")
 | 
					        tmpfile = tmp_path / "temp.tif"
 | 
				
			||||||
        with open(tmpfile, "wb") as f:
 | 
					        with open(tmpfile, "wb") as f:
 | 
				
			||||||
            with open("Tests/images/g4-multi.tiff", "rb") as src:
 | 
					            with open("Tests/images/g4-multi.tiff", "rb") as src:
 | 
				
			||||||
                f.write(src.read())
 | 
					                f.write(src.read())
 | 
				
			||||||
| 
						 | 
					@ -783,13 +798,14 @@ class TestFileLibTiff(LibTiffTestCase):
 | 
				
			||||||
        with Image.open("Tests/images/hopper.iccprofile.tif") as img:
 | 
					        with Image.open("Tests/images/hopper.iccprofile.tif") as img:
 | 
				
			||||||
            icc_profile = img.info["icc_profile"]
 | 
					            icc_profile = img.info["icc_profile"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            out = str(tmp_path / "temp.tif")
 | 
					            out = tmp_path / "temp.tif"
 | 
				
			||||||
            img.save(out, icc_profile=icc_profile)
 | 
					            img.save(out, icc_profile=icc_profile)
 | 
				
			||||||
        with Image.open(out) as reloaded:
 | 
					        with Image.open(out) as reloaded:
 | 
				
			||||||
            assert icc_profile == reloaded.info["icc_profile"]
 | 
					            assert icc_profile == reloaded.info["icc_profile"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_multipage_compression(self) -> None:
 | 
					    def test_multipage_compression(self) -> None:
 | 
				
			||||||
        with Image.open("Tests/images/compression.tif") as im:
 | 
					        with Image.open("Tests/images/compression.tif") as im:
 | 
				
			||||||
 | 
					            assert isinstance(im, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
            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)
 | 
				
			||||||
| 
						 | 
					@ -806,7 +822,7 @@ class TestFileLibTiff(LibTiffTestCase):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_save_tiff_with_jpegtables(self, tmp_path: Path) -> None:
 | 
					    def test_save_tiff_with_jpegtables(self, tmp_path: Path) -> None:
 | 
				
			||||||
        # Arrange
 | 
					        # Arrange
 | 
				
			||||||
        outfile = str(tmp_path / "temp.tif")
 | 
					        outfile = tmp_path / "temp.tif"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Created with ImageMagick: convert hopper.jpg hopper_jpg.tif
 | 
					        # Created with ImageMagick: convert hopper.jpg hopper_jpg.tif
 | 
				
			||||||
        # Contains JPEGTables (347) tag
 | 
					        # Contains JPEGTables (347) tag
 | 
				
			||||||
| 
						 | 
					@ -868,7 +884,7 @@ class TestFileLibTiff(LibTiffTestCase):
 | 
				
			||||||
        self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path
 | 
					        self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path
 | 
				
			||||||
    ) -> None:
 | 
					    ) -> None:
 | 
				
			||||||
        im = Image.new("F", (1, 1))
 | 
					        im = Image.new("F", (1, 1))
 | 
				
			||||||
        out = str(tmp_path / "temp.tif")
 | 
					        out = tmp_path / "temp.tif"
 | 
				
			||||||
        monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", True)
 | 
					        monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", True)
 | 
				
			||||||
        im.save(out)
 | 
					        im.save(out)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1012,7 +1028,7 @@ class TestFileLibTiff(LibTiffTestCase):
 | 
				
			||||||
    @pytest.mark.parametrize("compression", (None, "jpeg"))
 | 
					    @pytest.mark.parametrize("compression", (None, "jpeg"))
 | 
				
			||||||
    def test_block_tile_tags(self, compression: str | None, tmp_path: Path) -> None:
 | 
					    def test_block_tile_tags(self, compression: str | None, tmp_path: Path) -> None:
 | 
				
			||||||
        im = hopper()
 | 
					        im = hopper()
 | 
				
			||||||
        out = str(tmp_path / "temp.tif")
 | 
					        out = tmp_path / "temp.tif"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        tags = {
 | 
					        tags = {
 | 
				
			||||||
            TiffImagePlugin.TILEWIDTH: 256,
 | 
					            TiffImagePlugin.TILEWIDTH: 256,
 | 
				
			||||||
| 
						 | 
					@ -1030,6 +1046,17 @@ class TestFileLibTiff(LibTiffTestCase):
 | 
				
			||||||
        with Image.open("Tests/images/old-style-jpeg-compression.tif") as im:
 | 
					        with Image.open("Tests/images/old-style-jpeg-compression.tif") as im:
 | 
				
			||||||
            assert_image_equal_tofile(im, "Tests/images/old-style-jpeg-compression.png")
 | 
					            assert_image_equal_tofile(im, "Tests/images/old-style-jpeg-compression.png")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_old_style_jpeg_orientation(self) -> None:
 | 
				
			||||||
 | 
					        with open("Tests/images/old-style-jpeg-compression.tif", "rb") as fp:
 | 
				
			||||||
 | 
					            data = fp.read()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Set EXIF Orientation to 2
 | 
				
			||||||
 | 
					            data = data[:102] + b"\x02" + data[103:]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            with Image.open(io.BytesIO(data)) as im:
 | 
				
			||||||
 | 
					                im = im.transpose(Image.Transpose.FLIP_LEFT_RIGHT)
 | 
				
			||||||
 | 
					            assert_image_equal_tofile(im, "Tests/images/old-style-jpeg-compression.png")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_open_missing_samplesperpixel(self) -> None:
 | 
					    def test_open_missing_samplesperpixel(self) -> None:
 | 
				
			||||||
        with Image.open(
 | 
					        with Image.open(
 | 
				
			||||||
            "Tests/images/old-style-jpeg-compression-no-samplesperpixel.tif"
 | 
					            "Tests/images/old-style-jpeg-compression-no-samplesperpixel.tif"
 | 
				
			||||||
| 
						 | 
					@ -1083,6 +1110,7 @@ class TestFileLibTiff(LibTiffTestCase):
 | 
				
			||||||
        with Image.open("Tests/images/g4_orientation_1.tif") as base_im:
 | 
					        with Image.open("Tests/images/g4_orientation_1.tif") as base_im:
 | 
				
			||||||
            for i in range(2, 9):
 | 
					            for i in range(2, 9):
 | 
				
			||||||
                with Image.open("Tests/images/g4_orientation_" + str(i) + ".tif") as im:
 | 
					                with Image.open("Tests/images/g4_orientation_" + str(i) + ".tif") as im:
 | 
				
			||||||
 | 
					                    assert isinstance(im, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
                    assert 274 in im.tag_v2
 | 
					                    assert 274 in im.tag_v2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    im.load()
 | 
					                    im.load()
 | 
				
			||||||
| 
						 | 
					@ -1098,6 +1126,27 @@ class TestFileLibTiff(LibTiffTestCase):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    assert_image_similar(base_im, im, 0.7)
 | 
					                    assert_image_similar(base_im, im, 0.7)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @pytest.mark.parametrize(
 | 
				
			||||||
 | 
					        "test_file",
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            "Tests/images/old-style-jpeg-compression-no-samplesperpixel.tif",
 | 
				
			||||||
 | 
					            "Tests/images/old-style-jpeg-compression.tif",
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    def test_buffering(self, test_file: str) -> None:
 | 
				
			||||||
 | 
					        # load exif first
 | 
				
			||||||
 | 
					        with open(test_file, "rb", buffering=1048576) as f:
 | 
				
			||||||
 | 
					            with Image.open(f) as im:
 | 
				
			||||||
 | 
					                exif = dict(im.getexif())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # load image before exif
 | 
				
			||||||
 | 
					        with open(test_file, "rb", buffering=1048576) as f:
 | 
				
			||||||
 | 
					            with Image.open(f) as im2:
 | 
				
			||||||
 | 
					                im2.load()
 | 
				
			||||||
 | 
					                exif_after_load = dict(im2.getexif())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assert exif == exif_after_load
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @pytest.mark.valgrind_known_error(reason="Backtrace in Python Core")
 | 
					    @pytest.mark.valgrind_known_error(reason="Backtrace in Python Core")
 | 
				
			||||||
    def test_sampleformat_not_corrupted(self) -> None:
 | 
					    def test_sampleformat_not_corrupted(self) -> None:
 | 
				
			||||||
        # Assert that a TIFF image with SampleFormat=UINT tag is not corrupted
 | 
					        # Assert that a TIFF image with SampleFormat=UINT tag is not corrupted
 | 
				
			||||||
| 
						 | 
					@ -1123,16 +1172,14 @@ class TestFileLibTiff(LibTiffTestCase):
 | 
				
			||||||
    def test_realloc_overflow(self, monkeypatch: pytest.MonkeyPatch) -> None:
 | 
					    def test_realloc_overflow(self, monkeypatch: pytest.MonkeyPatch) -> None:
 | 
				
			||||||
        monkeypatch.setattr(TiffImagePlugin, "READ_LIBTIFF", True)
 | 
					        monkeypatch.setattr(TiffImagePlugin, "READ_LIBTIFF", True)
 | 
				
			||||||
        with Image.open("Tests/images/tiff_overflow_rows_per_strip.tif") as im:
 | 
					        with Image.open("Tests/images/tiff_overflow_rows_per_strip.tif") as im:
 | 
				
			||||||
            with pytest.raises(OSError) as e:
 | 
					 | 
				
			||||||
                im.load()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            # Assert that the error code is IMAGING_CODEC_MEMORY
 | 
					            # Assert that the error code is IMAGING_CODEC_MEMORY
 | 
				
			||||||
            assert str(e.value) == "-9"
 | 
					            with pytest.raises(OSError, match="decoder error -9"):
 | 
				
			||||||
 | 
					                im.load()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @pytest.mark.parametrize("compression", ("tiff_adobe_deflate", "jpeg"))
 | 
					    @pytest.mark.parametrize("compression", ("tiff_adobe_deflate", "jpeg"))
 | 
				
			||||||
    def test_save_multistrip(self, compression: str, tmp_path: Path) -> None:
 | 
					    def test_save_multistrip(self, compression: str, tmp_path: Path) -> None:
 | 
				
			||||||
        im = hopper("RGB").resize((256, 256))
 | 
					        im = hopper("RGB").resize((256, 256))
 | 
				
			||||||
        out = str(tmp_path / "temp.tif")
 | 
					        out = tmp_path / "temp.tif"
 | 
				
			||||||
        im.save(out, compression=compression)
 | 
					        im.save(out, compression=compression)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        with Image.open(out) as im:
 | 
					        with Image.open(out) as im:
 | 
				
			||||||
| 
						 | 
					@ -1141,13 +1188,14 @@ class TestFileLibTiff(LibTiffTestCase):
 | 
				
			||||||
            assert len(im.tag_v2[STRIPOFFSETS]) > 1
 | 
					            assert len(im.tag_v2[STRIPOFFSETS]) > 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @pytest.mark.parametrize("argument", (True, False))
 | 
					    @pytest.mark.parametrize("argument", (True, False))
 | 
				
			||||||
    def test_save_single_strip(self, argument: bool, tmp_path: Path) -> None:
 | 
					    def test_save_single_strip(
 | 
				
			||||||
 | 
					        self, argument: bool, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
 | 
				
			||||||
 | 
					    ) -> None:
 | 
				
			||||||
        im = hopper("RGB").resize((256, 256))
 | 
					        im = hopper("RGB").resize((256, 256))
 | 
				
			||||||
        out = str(tmp_path / "temp.tif")
 | 
					        out = tmp_path / "temp.tif"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if not argument:
 | 
					        if not argument:
 | 
				
			||||||
            TiffImagePlugin.STRIP_SIZE = 2**18
 | 
					            monkeypatch.setattr(TiffImagePlugin, "STRIP_SIZE", 2**18)
 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
        arguments: dict[str, str | int] = {"compression": "tiff_adobe_deflate"}
 | 
					        arguments: dict[str, str | int] = {"compression": "tiff_adobe_deflate"}
 | 
				
			||||||
        if argument:
 | 
					        if argument:
 | 
				
			||||||
            arguments["strip_size"] = 2**18
 | 
					            arguments["strip_size"] = 2**18
 | 
				
			||||||
| 
						 | 
					@ -1156,19 +1204,17 @@ class TestFileLibTiff(LibTiffTestCase):
 | 
				
			||||||
        with Image.open(out) as im:
 | 
					        with Image.open(out) as im:
 | 
				
			||||||
            assert isinstance(im, TiffImagePlugin.TiffImageFile)
 | 
					            assert isinstance(im, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
            assert len(im.tag_v2[STRIPOFFSETS]) == 1
 | 
					            assert len(im.tag_v2[STRIPOFFSETS]) == 1
 | 
				
			||||||
        finally:
 | 
					 | 
				
			||||||
            TiffImagePlugin.STRIP_SIZE = 65536
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @pytest.mark.parametrize("compression", ("tiff_adobe_deflate", None))
 | 
					    @pytest.mark.parametrize("compression", ("tiff_adobe_deflate", None))
 | 
				
			||||||
    def test_save_zero(self, compression: str | None, tmp_path: Path) -> None:
 | 
					    def test_save_zero(self, compression: str | None, tmp_path: Path) -> None:
 | 
				
			||||||
        im = Image.new("RGB", (0, 0))
 | 
					        im = Image.new("RGB", (0, 0))
 | 
				
			||||||
        out = str(tmp_path / "temp.tif")
 | 
					        out = 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: Path) -> None:
 | 
					    def test_save_many_compressed(self, tmp_path: Path) -> None:
 | 
				
			||||||
        im = hopper()
 | 
					        im = hopper()
 | 
				
			||||||
        out = str(tmp_path / "temp.tif")
 | 
					        out = tmp_path / "temp.tif"
 | 
				
			||||||
        for _ in range(10000):
 | 
					        for _ in range(10000):
 | 
				
			||||||
            im.save(out, compression="jpeg")
 | 
					            im.save(out, compression="jpeg")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -30,11 +30,13 @@ def test_sanity() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_n_frames() -> None:
 | 
					def test_n_frames() -> None:
 | 
				
			||||||
    with Image.open(TEST_FILE) as im:
 | 
					    with Image.open(TEST_FILE) as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, MicImagePlugin.MicImageFile)
 | 
				
			||||||
        assert im.n_frames == 1
 | 
					        assert im.n_frames == 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_is_animated() -> None:
 | 
					def test_is_animated() -> None:
 | 
				
			||||||
    with Image.open(TEST_FILE) as im:
 | 
					    with Image.open(TEST_FILE) as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, MicImagePlugin.MicImageFile)
 | 
				
			||||||
        assert not im.is_animated
 | 
					        assert not im.is_animated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -55,10 +57,11 @@ def test_seek() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_close() -> None:
 | 
					def test_close() -> None:
 | 
				
			||||||
    with Image.open(TEST_FILE) as im:
 | 
					    with Image.open(TEST_FILE) as im:
 | 
				
			||||||
        pass
 | 
					        assert isinstance(im, MicImagePlugin.MicImageFile)
 | 
				
			||||||
    assert im.ole.fp.closed
 | 
					    assert im.ole.fp.closed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    im = Image.open(TEST_FILE)
 | 
					    im = Image.open(TEST_FILE)
 | 
				
			||||||
 | 
					    assert isinstance(im, MicImagePlugin.MicImageFile)
 | 
				
			||||||
    im.close()
 | 
					    im.close()
 | 
				
			||||||
    assert im.ole.fp.closed
 | 
					    assert im.ole.fp.closed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,7 +6,7 @@ from typing import Any
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import pytest
 | 
					import pytest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from PIL import Image, ImageFile, MpoImagePlugin
 | 
					from PIL import Image, ImageFile, JpegImagePlugin, MpoImagePlugin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .helper import (
 | 
					from .helper import (
 | 
				
			||||||
    assert_image_equal,
 | 
					    assert_image_equal,
 | 
				
			||||||
| 
						 | 
					@ -29,21 +29,26 @@ def roundtrip(im: Image.Image, **options: Any) -> ImageFile.ImageFile:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@pytest.mark.parametrize("test_file", test_files)
 | 
					@pytest.mark.parametrize("test_file", test_files)
 | 
				
			||||||
def test_sanity(test_file: str) -> None:
 | 
					def test_sanity(test_file: str) -> None:
 | 
				
			||||||
    with Image.open(test_file) as im:
 | 
					    def check(im: ImageFile.ImageFile) -> None:
 | 
				
			||||||
        im.load()
 | 
					        im.load()
 | 
				
			||||||
        assert im.mode == "RGB"
 | 
					        assert im.mode == "RGB"
 | 
				
			||||||
        assert im.size == (640, 480)
 | 
					        assert im.size == (640, 480)
 | 
				
			||||||
        assert im.format == "MPO"
 | 
					        assert im.format == "MPO"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    with Image.open(test_file) as im:
 | 
				
			||||||
 | 
					        check(im)
 | 
				
			||||||
 | 
					    with MpoImagePlugin.MpoImageFile(test_file) as im:
 | 
				
			||||||
 | 
					        check(im)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@pytest.mark.skipif(is_pypy(), reason="Requires CPython")
 | 
					@pytest.mark.skipif(is_pypy(), reason="Requires CPython")
 | 
				
			||||||
def test_unclosed_file() -> None:
 | 
					def test_unclosed_file() -> None:
 | 
				
			||||||
    def open() -> None:
 | 
					    def open_test_image() -> None:
 | 
				
			||||||
        im = Image.open(test_files[0])
 | 
					        im = Image.open(test_files[0])
 | 
				
			||||||
        im.load()
 | 
					        im.load()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with pytest.warns(ResourceWarning):
 | 
					    with pytest.warns(ResourceWarning):
 | 
				
			||||||
        open()
 | 
					        open_test_image()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_closed_file() -> None:
 | 
					def test_closed_file() -> None:
 | 
				
			||||||
| 
						 | 
					@ -75,10 +80,11 @@ def test_context_manager() -> None:
 | 
				
			||||||
def test_app(test_file: str) -> None:
 | 
					def test_app(test_file: str) -> None:
 | 
				
			||||||
    # Test APP/COM reader (@PIL135)
 | 
					    # Test APP/COM reader (@PIL135)
 | 
				
			||||||
    with Image.open(test_file) as im:
 | 
					    with Image.open(test_file) as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, MpoImagePlugin.MpoImageFile)
 | 
				
			||||||
        assert im.applist[0][0] == "APP1"
 | 
					        assert im.applist[0][0] == "APP1"
 | 
				
			||||||
        assert im.applist[1][0] == "APP2"
 | 
					        assert im.applist[1][0] == "APP2"
 | 
				
			||||||
        assert (
 | 
					        assert im.applist[1][1].startswith(
 | 
				
			||||||
            im.applist[1][1][:16] == b"MPF\x00MM\x00*\x00\x00\x00\x08\x00\x03\xb0\x00"
 | 
					            b"MPF\x00MM\x00*\x00\x00\x00\x08\x00\x03\xb0\x00"
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        assert len(im.applist) == 2
 | 
					        assert len(im.applist) == 2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -215,12 +221,14 @@ def test_seek(test_file: str) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_n_frames() -> None:
 | 
					def test_n_frames() -> None:
 | 
				
			||||||
    with Image.open("Tests/images/sugarshack.mpo") as im:
 | 
					    with Image.open("Tests/images/sugarshack.mpo") as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, MpoImagePlugin.MpoImageFile)
 | 
				
			||||||
        assert im.n_frames == 2
 | 
					        assert im.n_frames == 2
 | 
				
			||||||
        assert im.is_animated
 | 
					        assert im.is_animated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_eoferror() -> None:
 | 
					def test_eoferror() -> None:
 | 
				
			||||||
    with Image.open("Tests/images/sugarshack.mpo") as im:
 | 
					    with Image.open("Tests/images/sugarshack.mpo") as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, MpoImagePlugin.MpoImageFile)
 | 
				
			||||||
        n_frames = im.n_frames
 | 
					        n_frames = im.n_frames
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Test seeking past the last frame
 | 
					        # Test seeking past the last frame
 | 
				
			||||||
| 
						 | 
					@ -234,6 +242,8 @@ def test_eoferror() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_adopt_jpeg() -> None:
 | 
					def test_adopt_jpeg() -> None:
 | 
				
			||||||
    with Image.open("Tests/images/hopper.jpg") as im:
 | 
					    with Image.open("Tests/images/hopper.jpg") as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, JpegImagePlugin.JpegImageFile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        with pytest.raises(ValueError):
 | 
					        with pytest.raises(ValueError):
 | 
				
			||||||
            MpoImagePlugin.MpoImageFile.adopt(im)
 | 
					            MpoImagePlugin.MpoImageFile.adopt(im)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -297,3 +307,15 @@ def test_save_all() -> None:
 | 
				
			||||||
    # Test that a single frame image will not be saved as an MPO
 | 
					    # Test that a single frame image will not be saved as an MPO
 | 
				
			||||||
    jpg = roundtrip(im, save_all=True)
 | 
					    jpg = roundtrip(im, save_all=True)
 | 
				
			||||||
    assert "mp" not in jpg.info
 | 
					    assert "mp" not in jpg.info
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_save_xmp() -> None:
 | 
				
			||||||
 | 
					    im = Image.new("RGB", (1, 1))
 | 
				
			||||||
 | 
					    im2 = Image.new("RGB", (1, 1), "#f00")
 | 
				
			||||||
 | 
					    im2.encoderinfo = {"xmp": b"Second frame"}
 | 
				
			||||||
 | 
					    im_reloaded = roundtrip(im, xmp=b"First frame", save_all=True, append_images=[im2])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    assert im_reloaded.info["xmp"] == b"First frame"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    im_reloaded.seek(1)
 | 
				
			||||||
 | 
					    assert im_reloaded.info["xmp"] == b"Second frame"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,7 +15,7 @@ YA_EXTRA_DIR = "Tests/images/msp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_sanity(tmp_path: Path) -> None:
 | 
					def test_sanity(tmp_path: Path) -> None:
 | 
				
			||||||
    test_file = str(tmp_path / "temp.msp")
 | 
					    test_file = tmp_path / "temp.msp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    hopper("1").save(test_file)
 | 
					    hopper("1").save(test_file)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -84,7 +84,7 @@ def test_msp_v2() -> None:
 | 
				
			||||||
def test_cannot_save_wrong_mode(tmp_path: Path) -> None:
 | 
					def test_cannot_save_wrong_mode(tmp_path: Path) -> None:
 | 
				
			||||||
    # Arrange
 | 
					    # Arrange
 | 
				
			||||||
    im = hopper()
 | 
					    im = hopper()
 | 
				
			||||||
    filename = str(tmp_path / "temp.msp")
 | 
					    filename = tmp_path / "temp.msp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Act/Assert
 | 
					    # Act/Assert
 | 
				
			||||||
    with pytest.raises(OSError):
 | 
					    with pytest.raises(OSError):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,7 +14,7 @@ from .helper import assert_image_equal, hopper, magick_command
 | 
				
			||||||
def helper_save_as_palm(tmp_path: Path, mode: str) -> None:
 | 
					def helper_save_as_palm(tmp_path: Path, mode: str) -> None:
 | 
				
			||||||
    # Arrange
 | 
					    # Arrange
 | 
				
			||||||
    im = hopper(mode)
 | 
					    im = hopper(mode)
 | 
				
			||||||
    outfile = str(tmp_path / ("temp_" + mode + ".palm"))
 | 
					    outfile = tmp_path / ("temp_" + mode + ".palm")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Act
 | 
					    # Act
 | 
				
			||||||
    im.save(outfile)
 | 
					    im.save(outfile)
 | 
				
			||||||
| 
						 | 
					@ -25,7 +25,7 @@ def helper_save_as_palm(tmp_path: Path, mode: str) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def open_with_magick(magick: list[str], tmp_path: Path, f: str) -> Image.Image:
 | 
					def open_with_magick(magick: list[str], tmp_path: Path, f: str) -> Image.Image:
 | 
				
			||||||
    outfile = str(tmp_path / "temp.png")
 | 
					    outfile = tmp_path / "temp.png"
 | 
				
			||||||
    rc = subprocess.call(
 | 
					    rc = subprocess.call(
 | 
				
			||||||
        magick + [f, outfile], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT
 | 
					        magick + [f, outfile], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
| 
						 | 
					@ -43,6 +43,11 @@ def roundtrip(tmp_path: Path, mode: str) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    im.save(outfile)
 | 
					    im.save(outfile)
 | 
				
			||||||
    converted = open_with_magick(magick, tmp_path, outfile)
 | 
					    converted = open_with_magick(magick, tmp_path, outfile)
 | 
				
			||||||
 | 
					    if mode == "P":
 | 
				
			||||||
 | 
					        assert converted.mode == "P"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        im = im.convert("RGB")
 | 
				
			||||||
 | 
					        converted = converted.convert("RGB")
 | 
				
			||||||
    assert_image_equal(converted, im)
 | 
					    assert_image_equal(converted, im)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -55,7 +60,6 @@ def test_monochrome(tmp_path: Path) -> None:
 | 
				
			||||||
    roundtrip(tmp_path, mode)
 | 
					    roundtrip(tmp_path, mode)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@pytest.mark.xfail(reason="Palm P image is wrong")
 | 
					 | 
				
			||||||
def test_p_mode(tmp_path: Path) -> None:
 | 
					def test_p_mode(tmp_path: Path) -> None:
 | 
				
			||||||
    # Arrange
 | 
					    # Arrange
 | 
				
			||||||
    mode = "P"
 | 
					    mode = "P"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,6 @@
 | 
				
			||||||
from __future__ import annotations
 | 
					from __future__ import annotations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import io
 | 
				
			||||||
from pathlib import Path
 | 
					from pathlib import Path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import pytest
 | 
					import pytest
 | 
				
			||||||
| 
						 | 
					@ -10,7 +11,7 @@ from .helper import assert_image_equal, hopper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def _roundtrip(tmp_path: Path, im: Image.Image) -> None:
 | 
					def _roundtrip(tmp_path: Path, im: Image.Image) -> None:
 | 
				
			||||||
    f = str(tmp_path / "temp.pcx")
 | 
					    f = tmp_path / "temp.pcx"
 | 
				
			||||||
    im.save(f)
 | 
					    im.save(f)
 | 
				
			||||||
    with Image.open(f) as im2:
 | 
					    with Image.open(f) as im2:
 | 
				
			||||||
        assert im2.mode == im.mode
 | 
					        assert im2.mode == im.mode
 | 
				
			||||||
| 
						 | 
					@ -30,12 +31,34 @@ def test_sanity(tmp_path: Path) -> None:
 | 
				
			||||||
    _roundtrip(tmp_path, im)
 | 
					    _roundtrip(tmp_path, im)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Test an unsupported mode
 | 
					    # Test an unsupported mode
 | 
				
			||||||
    f = str(tmp_path / "temp.pcx")
 | 
					    f = tmp_path / "temp.pcx"
 | 
				
			||||||
    im = hopper("RGBA")
 | 
					    im = hopper("RGBA")
 | 
				
			||||||
    with pytest.raises(ValueError):
 | 
					    with pytest.raises(ValueError):
 | 
				
			||||||
        im.save(f)
 | 
					        im.save(f)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_bad_image_size() -> None:
 | 
				
			||||||
 | 
					    with open("Tests/images/pil184.pcx", "rb") as fp:
 | 
				
			||||||
 | 
					        data = fp.read()
 | 
				
			||||||
 | 
					    data = data[:4] + b"\xff\xff" + data[6:]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    b = io.BytesIO(data)
 | 
				
			||||||
 | 
					    with pytest.raises(SyntaxError, match="bad PCX image size"):
 | 
				
			||||||
 | 
					        with PcxImagePlugin.PcxImageFile(b):
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_unknown_mode() -> None:
 | 
				
			||||||
 | 
					    with open("Tests/images/pil184.pcx", "rb") as fp:
 | 
				
			||||||
 | 
					        data = fp.read()
 | 
				
			||||||
 | 
					    data = data[:3] + b"\xff" + data[4:]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    b = io.BytesIO(data)
 | 
				
			||||||
 | 
					    with pytest.raises(OSError, match="unknown PCX mode"):
 | 
				
			||||||
 | 
					        with Image.open(b):
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_invalid_file() -> None:
 | 
					def test_invalid_file() -> None:
 | 
				
			||||||
    invalid_file = "Tests/images/flower.jpg"
 | 
					    invalid_file = "Tests/images/flower.jpg"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -55,7 +55,7 @@ def test_save_alpha(tmp_path: Path, mode: str) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_p_alpha(tmp_path: Path) -> None:
 | 
					def test_p_alpha(tmp_path: Path) -> None:
 | 
				
			||||||
    # Arrange
 | 
					    # Arrange
 | 
				
			||||||
    outfile = str(tmp_path / "temp.pdf")
 | 
					    outfile = tmp_path / "temp.pdf"
 | 
				
			||||||
    with Image.open("Tests/images/pil123p.png") as im:
 | 
					    with Image.open("Tests/images/pil123p.png") as im:
 | 
				
			||||||
        assert im.mode == "P"
 | 
					        assert im.mode == "P"
 | 
				
			||||||
        assert isinstance(im.info["transparency"], bytes)
 | 
					        assert isinstance(im.info["transparency"], bytes)
 | 
				
			||||||
| 
						 | 
					@ -80,7 +80,7 @@ def test_monochrome(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_unsupported_mode(tmp_path: Path) -> None:
 | 
					def test_unsupported_mode(tmp_path: Path) -> None:
 | 
				
			||||||
    im = hopper("PA")
 | 
					    im = hopper("PA")
 | 
				
			||||||
    outfile = str(tmp_path / "temp_PA.pdf")
 | 
					    outfile = tmp_path / "temp_PA.pdf"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with pytest.raises(ValueError):
 | 
					    with pytest.raises(ValueError):
 | 
				
			||||||
        im.save(outfile)
 | 
					        im.save(outfile)
 | 
				
			||||||
| 
						 | 
					@ -89,7 +89,7 @@ def test_unsupported_mode(tmp_path: Path) -> None:
 | 
				
			||||||
def test_resolution(tmp_path: Path) -> None:
 | 
					def test_resolution(tmp_path: Path) -> None:
 | 
				
			||||||
    im = hopper()
 | 
					    im = hopper()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    outfile = str(tmp_path / "temp.pdf")
 | 
					    outfile = tmp_path / "temp.pdf"
 | 
				
			||||||
    im.save(outfile, resolution=150)
 | 
					    im.save(outfile, resolution=150)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with open(outfile, "rb") as fp:
 | 
					    with open(outfile, "rb") as fp:
 | 
				
			||||||
| 
						 | 
					@ -117,7 +117,7 @@ def test_resolution(tmp_path: Path) -> None:
 | 
				
			||||||
def test_dpi(params: dict[str, int | tuple[int, int]], tmp_path: Path) -> None:
 | 
					def test_dpi(params: dict[str, int | tuple[int, int]], tmp_path: Path) -> None:
 | 
				
			||||||
    im = hopper()
 | 
					    im = hopper()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    outfile = str(tmp_path / "temp.pdf")
 | 
					    outfile = tmp_path / "temp.pdf"
 | 
				
			||||||
    im.save(outfile, "PDF", **params)
 | 
					    im.save(outfile, "PDF", **params)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with open(outfile, "rb") as fp:
 | 
					    with open(outfile, "rb") as fp:
 | 
				
			||||||
| 
						 | 
					@ -144,7 +144,7 @@ def test_save_all(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # 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 = tmp_path / "temp.pdf"
 | 
				
			||||||
        im.save(outfile, save_all=True)
 | 
					        im.save(outfile, save_all=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        assert os.path.isfile(outfile)
 | 
					        assert os.path.isfile(outfile)
 | 
				
			||||||
| 
						 | 
					@ -177,7 +177,7 @@ def test_save_all(tmp_path: Path) -> None:
 | 
				
			||||||
def test_multiframe_normal_save(tmp_path: Path) -> None:
 | 
					def test_multiframe_normal_save(tmp_path: Path) -> None:
 | 
				
			||||||
    # 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 = tmp_path / "temp.pdf"
 | 
				
			||||||
        im.save(outfile)
 | 
					        im.save(outfile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    assert os.path.isfile(outfile)
 | 
					    assert os.path.isfile(outfile)
 | 
				
			||||||
| 
						 | 
					@ -264,7 +264,7 @@ def test_pdf_append(tmp_path: Path) -> None:
 | 
				
			||||||
        # append some info
 | 
					        # append some info
 | 
				
			||||||
        pdf.info.Title = "abc"
 | 
					        pdf.info.Title = "abc"
 | 
				
			||||||
        pdf.info.Author = "def"
 | 
					        pdf.info.Author = "def"
 | 
				
			||||||
        pdf.info.Subject = "ghi\uABCD"
 | 
					        pdf.info.Subject = "ghi\uabcd"
 | 
				
			||||||
        pdf.info.Keywords = "qw)e\\r(ty"
 | 
					        pdf.info.Keywords = "qw)e\\r(ty"
 | 
				
			||||||
        pdf.info.Creator = "hopper()"
 | 
					        pdf.info.Creator = "hopper()"
 | 
				
			||||||
        pdf.start_writing()
 | 
					        pdf.start_writing()
 | 
				
			||||||
| 
						 | 
					@ -292,7 +292,7 @@ def test_pdf_append(tmp_path: Path) -> None:
 | 
				
			||||||
        assert pdf.info.Title == "abc"
 | 
					        assert pdf.info.Title == "abc"
 | 
				
			||||||
        assert pdf.info.Producer == "PdfParser"
 | 
					        assert pdf.info.Producer == "PdfParser"
 | 
				
			||||||
        assert pdf.info.Keywords == "qw)e\\r(ty"
 | 
					        assert pdf.info.Keywords == "qw)e\\r(ty"
 | 
				
			||||||
        assert pdf.info.Subject == "ghi\uABCD"
 | 
					        assert pdf.info.Subject == "ghi\uabcd"
 | 
				
			||||||
        assert b"CreationDate" in pdf.info
 | 
					        assert b"CreationDate" in pdf.info
 | 
				
			||||||
        assert b"ModDate" in pdf.info
 | 
					        assert b"ModDate" in pdf.info
 | 
				
			||||||
        check_pdf_pages_consistency(pdf)
 | 
					        check_pdf_pages_consistency(pdf)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -68,7 +68,7 @@ def roundtrip(im: Image.Image, **options: Any) -> PngImagePlugin.PngImageFile:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@skip_unless_feature("zlib")
 | 
					@skip_unless_feature("zlib")
 | 
				
			||||||
class TestFilePng:
 | 
					class TestFilePng:
 | 
				
			||||||
    def get_chunks(self, filename: str) -> list[bytes]:
 | 
					    def get_chunks(self, filename: Path) -> list[bytes]:
 | 
				
			||||||
        chunks = []
 | 
					        chunks = []
 | 
				
			||||||
        with open(filename, "rb") as fp:
 | 
					        with open(filename, "rb") as fp:
 | 
				
			||||||
            fp.read(8)
 | 
					            fp.read(8)
 | 
				
			||||||
| 
						 | 
					@ -89,7 +89,7 @@ class TestFilePng:
 | 
				
			||||||
        assert version is not None
 | 
					        assert version is not None
 | 
				
			||||||
        assert re.search(r"\d+(\.\d+){1,3}(\.zlib\-ng)?$", version)
 | 
					        assert re.search(r"\d+(\.\d+){1,3}(\.zlib\-ng)?$", version)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        test_file = str(tmp_path / "temp.png")
 | 
					        test_file = tmp_path / "temp.png"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        hopper("RGB").save(test_file)
 | 
					        hopper("RGB").save(test_file)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -250,7 +250,7 @@ class TestFilePng:
 | 
				
			||||||
            # each palette entry
 | 
					            # each palette entry
 | 
				
			||||||
            assert len(im.info["transparency"]) == 256
 | 
					            assert len(im.info["transparency"]) == 256
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            test_file = str(tmp_path / "temp.png")
 | 
					            test_file = tmp_path / "temp.png"
 | 
				
			||||||
            im.save(test_file)
 | 
					            im.save(test_file)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # check if saved image contains same transparency
 | 
					        # check if saved image contains same transparency
 | 
				
			||||||
| 
						 | 
					@ -271,7 +271,7 @@ class TestFilePng:
 | 
				
			||||||
            assert im.info["transparency"] == 164
 | 
					            assert im.info["transparency"] == 164
 | 
				
			||||||
            assert im.getpixel((31, 31)) == 164
 | 
					            assert im.getpixel((31, 31)) == 164
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            test_file = str(tmp_path / "temp.png")
 | 
					            test_file = tmp_path / "temp.png"
 | 
				
			||||||
            im.save(test_file)
 | 
					            im.save(test_file)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # check if saved image contains same transparency
 | 
					        # check if saved image contains same transparency
 | 
				
			||||||
| 
						 | 
					@ -294,7 +294,7 @@ class TestFilePng:
 | 
				
			||||||
        assert im.getcolors() == [(100, (0, 0, 0, 0))]
 | 
					        assert im.getcolors() == [(100, (0, 0, 0, 0))]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        im = im.convert("P")
 | 
					        im = im.convert("P")
 | 
				
			||||||
        test_file = str(tmp_path / "temp.png")
 | 
					        test_file = tmp_path / "temp.png"
 | 
				
			||||||
        im.save(test_file)
 | 
					        im.save(test_file)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # check if saved image contains same transparency
 | 
					        # check if saved image contains same transparency
 | 
				
			||||||
| 
						 | 
					@ -315,7 +315,7 @@ class TestFilePng:
 | 
				
			||||||
                im_rgba = im.convert("RGBA")
 | 
					                im_rgba = im.convert("RGBA")
 | 
				
			||||||
            assert im_rgba.getchannel("A").getcolors()[0][0] == num_transparent
 | 
					            assert im_rgba.getchannel("A").getcolors()[0][0] == num_transparent
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            test_file = str(tmp_path / "temp.png")
 | 
					            test_file = tmp_path / "temp.png"
 | 
				
			||||||
            im.save(test_file)
 | 
					            im.save(test_file)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            with Image.open(test_file) as test_im:
 | 
					            with Image.open(test_file) as test_im:
 | 
				
			||||||
| 
						 | 
					@ -329,7 +329,7 @@ class TestFilePng:
 | 
				
			||||||
    def test_save_rgb_single_transparency(self, tmp_path: Path) -> None:
 | 
					    def test_save_rgb_single_transparency(self, tmp_path: Path) -> None:
 | 
				
			||||||
        in_file = "Tests/images/caption_6_33_22.png"
 | 
					        in_file = "Tests/images/caption_6_33_22.png"
 | 
				
			||||||
        with Image.open(in_file) as im:
 | 
					        with Image.open(in_file) as im:
 | 
				
			||||||
            test_file = str(tmp_path / "temp.png")
 | 
					            test_file = tmp_path / "temp.png"
 | 
				
			||||||
            im.save(test_file)
 | 
					            im.save(test_file)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_load_verify(self) -> None:
 | 
					    def test_load_verify(self) -> None:
 | 
				
			||||||
| 
						 | 
					@ -363,7 +363,7 @@ class TestFilePng:
 | 
				
			||||||
                with pytest.raises((OSError, SyntaxError)):
 | 
					                with pytest.raises((OSError, SyntaxError)):
 | 
				
			||||||
                    im.verify()
 | 
					                    im.verify()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_verify_ignores_crc_error(self) -> None:
 | 
					    def test_verify_ignores_crc_error(self, monkeypatch: pytest.MonkeyPatch) -> None:
 | 
				
			||||||
        # check ignores crc errors in ancillary chunks
 | 
					        # check ignores crc errors in ancillary chunks
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        chunk_data = chunk(b"tEXt", b"spam")
 | 
					        chunk_data = chunk(b"tEXt", b"spam")
 | 
				
			||||||
| 
						 | 
					@ -373,24 +373,20 @@ class TestFilePng:
 | 
				
			||||||
        with pytest.raises(SyntaxError):
 | 
					        with pytest.raises(SyntaxError):
 | 
				
			||||||
            PngImagePlugin.PngImageFile(BytesIO(image_data))
 | 
					            PngImagePlugin.PngImageFile(BytesIO(image_data))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ImageFile.LOAD_TRUNCATED_IMAGES = True
 | 
					        monkeypatch.setattr(ImageFile, "LOAD_TRUNCATED_IMAGES", True)
 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
        im = load(image_data)
 | 
					        im = load(image_data)
 | 
				
			||||||
        assert im is not None
 | 
					        assert im is not None
 | 
				
			||||||
        finally:
 | 
					 | 
				
			||||||
            ImageFile.LOAD_TRUNCATED_IMAGES = False
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_verify_not_ignores_crc_error_in_required_chunk(self) -> None:
 | 
					    def test_verify_not_ignores_crc_error_in_required_chunk(
 | 
				
			||||||
 | 
					        self, monkeypatch: pytest.MonkeyPatch
 | 
				
			||||||
 | 
					    ) -> None:
 | 
				
			||||||
        # check does not ignore crc errors in required chunks
 | 
					        # check does not ignore crc errors in required chunks
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        image_data = MAGIC + IHDR[:-1] + b"q" + TAIL
 | 
					        image_data = MAGIC + IHDR[:-1] + b"q" + TAIL
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ImageFile.LOAD_TRUNCATED_IMAGES = True
 | 
					        monkeypatch.setattr(ImageFile, "LOAD_TRUNCATED_IMAGES", True)
 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
        with pytest.raises(SyntaxError):
 | 
					        with pytest.raises(SyntaxError):
 | 
				
			||||||
            PngImagePlugin.PngImageFile(BytesIO(image_data))
 | 
					            PngImagePlugin.PngImageFile(BytesIO(image_data))
 | 
				
			||||||
        finally:
 | 
					 | 
				
			||||||
            ImageFile.LOAD_TRUNCATED_IMAGES = False
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_roundtrip_dpi(self) -> None:
 | 
					    def test_roundtrip_dpi(self) -> None:
 | 
				
			||||||
        # Check dpi roundtripping
 | 
					        # Check dpi roundtripping
 | 
				
			||||||
| 
						 | 
					@ -492,7 +488,7 @@ class TestFilePng:
 | 
				
			||||||
        im = hopper("P")
 | 
					        im = hopper("P")
 | 
				
			||||||
        im.info["transparency"] = 0
 | 
					        im.info["transparency"] = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        f = str(tmp_path / "temp.png")
 | 
					        f = tmp_path / "temp.png"
 | 
				
			||||||
        im.save(f)
 | 
					        im.save(f)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        with Image.open(f) as im2:
 | 
					        with Image.open(f) as im2:
 | 
				
			||||||
| 
						 | 
					@ -553,7 +549,7 @@ class TestFilePng:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_chunk_order(self, tmp_path: Path) -> None:
 | 
					    def test_chunk_order(self, tmp_path: Path) -> None:
 | 
				
			||||||
        with Image.open("Tests/images/icc_profile.png") as im:
 | 
					        with Image.open("Tests/images/icc_profile.png") as im:
 | 
				
			||||||
            test_file = str(tmp_path / "temp.png")
 | 
					            test_file = tmp_path / "temp.png"
 | 
				
			||||||
            im.convert("P").save(test_file, dpi=(100, 100))
 | 
					            im.convert("P").save(test_file, dpi=(100, 100))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        chunks = self.get_chunks(test_file)
 | 
					        chunks = self.get_chunks(test_file)
 | 
				
			||||||
| 
						 | 
					@ -580,6 +576,7 @@ class TestFilePng:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_read_private_chunks(self) -> None:
 | 
					    def test_read_private_chunks(self) -> None:
 | 
				
			||||||
        with Image.open("Tests/images/exif.png") as im:
 | 
					        with Image.open("Tests/images/exif.png") as im:
 | 
				
			||||||
 | 
					            assert isinstance(im, PngImagePlugin.PngImageFile)
 | 
				
			||||||
            assert im.private_chunks == [(b"orNT", b"\x01")]
 | 
					            assert im.private_chunks == [(b"orNT", b"\x01")]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_roundtrip_private_chunk(self) -> None:
 | 
					    def test_roundtrip_private_chunk(self) -> None:
 | 
				
			||||||
| 
						 | 
					@ -600,8 +597,9 @@ class TestFilePng:
 | 
				
			||||||
            (b"prIV", b"VALUE3", True),
 | 
					            (b"prIV", b"VALUE3", True),
 | 
				
			||||||
        ]
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_textual_chunks_after_idat(self) -> None:
 | 
					    def test_textual_chunks_after_idat(self, monkeypatch: pytest.MonkeyPatch) -> None:
 | 
				
			||||||
        with Image.open("Tests/images/hopper.png") as im:
 | 
					        with Image.open("Tests/images/hopper.png") as im:
 | 
				
			||||||
 | 
					            assert isinstance(im, PngImagePlugin.PngImageFile)
 | 
				
			||||||
            assert "comment" in im.text
 | 
					            assert "comment" in im.text
 | 
				
			||||||
            for k, v in {
 | 
					            for k, v in {
 | 
				
			||||||
                "date:create": "2014-09-04T09:37:08+03:00",
 | 
					                "date:create": "2014-09-04T09:37:08+03:00",
 | 
				
			||||||
| 
						 | 
					@ -611,22 +609,25 @@ class TestFilePng:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Raises a SyntaxError in load_end
 | 
					        # Raises a SyntaxError in load_end
 | 
				
			||||||
        with Image.open("Tests/images/broken_data_stream.png") as im:
 | 
					        with Image.open("Tests/images/broken_data_stream.png") as im:
 | 
				
			||||||
 | 
					            assert isinstance(im, PngImagePlugin.PngImageFile)
 | 
				
			||||||
            with pytest.raises(OSError):
 | 
					            with pytest.raises(OSError):
 | 
				
			||||||
                assert isinstance(im.text, dict)
 | 
					                assert isinstance(im.text, dict)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Raises a UnicodeDecodeError in load_end
 | 
					 | 
				
			||||||
        with Image.open("Tests/images/truncated_image.png") as im:
 | 
					 | 
				
			||||||
            # The file is truncated
 | 
					 | 
				
			||||||
            with pytest.raises(OSError):
 | 
					 | 
				
			||||||
                im.text()
 | 
					 | 
				
			||||||
            ImageFile.LOAD_TRUNCATED_IMAGES = True
 | 
					 | 
				
			||||||
            assert isinstance(im.text, dict)
 | 
					 | 
				
			||||||
            ImageFile.LOAD_TRUNCATED_IMAGES = False
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Raises an EOFError in load_end
 | 
					        # Raises an EOFError in load_end
 | 
				
			||||||
        with Image.open("Tests/images/hopper_idat_after_image_end.png") as im:
 | 
					        with Image.open("Tests/images/hopper_idat_after_image_end.png") as im:
 | 
				
			||||||
 | 
					            assert isinstance(im, PngImagePlugin.PngImageFile)
 | 
				
			||||||
            assert im.text == {"TXT": "VALUE", "ZIP": "VALUE"}
 | 
					            assert im.text == {"TXT": "VALUE", "ZIP": "VALUE"}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Raises a UnicodeDecodeError in load_end
 | 
				
			||||||
 | 
					        with Image.open("Tests/images/truncated_image.png") as im:
 | 
				
			||||||
 | 
					            assert isinstance(im, PngImagePlugin.PngImageFile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # The file is truncated
 | 
				
			||||||
 | 
					            with pytest.raises(OSError):
 | 
				
			||||||
 | 
					                im.text
 | 
				
			||||||
 | 
					            monkeypatch.setattr(ImageFile, "LOAD_TRUNCATED_IMAGES", True)
 | 
				
			||||||
 | 
					            assert isinstance(im.text, dict)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_unknown_compression_method(self) -> None:
 | 
					    def test_unknown_compression_method(self) -> None:
 | 
				
			||||||
        with pytest.raises(SyntaxError, match="Unknown compression method"):
 | 
					        with pytest.raises(SyntaxError, match="Unknown compression method"):
 | 
				
			||||||
            PngImagePlugin.PngImageFile("Tests/images/unknown_compression_method.png")
 | 
					            PngImagePlugin.PngImageFile("Tests/images/unknown_compression_method.png")
 | 
				
			||||||
| 
						 | 
					@ -651,21 +652,22 @@ class TestFilePng:
 | 
				
			||||||
    @pytest.mark.parametrize(
 | 
					    @pytest.mark.parametrize(
 | 
				
			||||||
        "cid", (b"IHDR", b"sRGB", b"pHYs", b"acTL", b"fcTL", b"fdAT")
 | 
					        "cid", (b"IHDR", b"sRGB", b"pHYs", b"acTL", b"fcTL", b"fdAT")
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    def test_truncated_chunks(self, cid: bytes) -> None:
 | 
					    def test_truncated_chunks(
 | 
				
			||||||
 | 
					        self, cid: bytes, monkeypatch: pytest.MonkeyPatch
 | 
				
			||||||
 | 
					    ) -> None:
 | 
				
			||||||
        fp = BytesIO()
 | 
					        fp = BytesIO()
 | 
				
			||||||
        with PngImagePlugin.PngStream(fp) as png:
 | 
					        with PngImagePlugin.PngStream(fp) as png:
 | 
				
			||||||
            with pytest.raises(ValueError):
 | 
					            with pytest.raises(ValueError):
 | 
				
			||||||
                png.call(cid, 0, 0)
 | 
					                png.call(cid, 0, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            ImageFile.LOAD_TRUNCATED_IMAGES = True
 | 
					            monkeypatch.setattr(ImageFile, "LOAD_TRUNCATED_IMAGES", True)
 | 
				
			||||||
            png.call(cid, 0, 0)
 | 
					            png.call(cid, 0, 0)
 | 
				
			||||||
            ImageFile.LOAD_TRUNCATED_IMAGES = False
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @pytest.mark.parametrize("save_all", (True, False))
 | 
					    @pytest.mark.parametrize("save_all", (True, False))
 | 
				
			||||||
    def test_specify_bits(self, save_all: bool, tmp_path: Path) -> None:
 | 
					    def test_specify_bits(self, save_all: bool, tmp_path: Path) -> None:
 | 
				
			||||||
        im = hopper("P")
 | 
					        im = hopper("P")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        out = str(tmp_path / "temp.png")
 | 
					        out = tmp_path / "temp.png"
 | 
				
			||||||
        im.save(out, bits=4, save_all=save_all)
 | 
					        im.save(out, bits=4, save_all=save_all)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        with Image.open(out) as reloaded:
 | 
					        with Image.open(out) as reloaded:
 | 
				
			||||||
| 
						 | 
					@ -675,8 +677,8 @@ class TestFilePng:
 | 
				
			||||||
        im = Image.new("P", (1, 1))
 | 
					        im = Image.new("P", (1, 1))
 | 
				
			||||||
        im.putpalette((1, 1, 1))
 | 
					        im.putpalette((1, 1, 1))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        out = str(tmp_path / "temp.png")
 | 
					        out = tmp_path / "temp.png"
 | 
				
			||||||
        im.save(str(tmp_path / "temp.png"))
 | 
					        im.save(out)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        with Image.open(out) as reloaded:
 | 
					        with Image.open(out) as reloaded:
 | 
				
			||||||
            assert len(reloaded.png.im_palette[1]) == 3
 | 
					            assert len(reloaded.png.im_palette[1]) == 3
 | 
				
			||||||
| 
						 | 
					@ -725,11 +727,12 @@ class TestFilePng:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_exif_save(self, tmp_path: Path) -> None:
 | 
					    def test_exif_save(self, tmp_path: Path) -> None:
 | 
				
			||||||
        # Test exif is not saved from info
 | 
					        # Test exif is not saved from info
 | 
				
			||||||
        test_file = str(tmp_path / "temp.png")
 | 
					        test_file = tmp_path / "temp.png"
 | 
				
			||||||
        with Image.open("Tests/images/exif.png") as im:
 | 
					        with Image.open("Tests/images/exif.png") as im:
 | 
				
			||||||
            im.save(test_file)
 | 
					            im.save(test_file)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        with Image.open(test_file) as reloaded:
 | 
					        with Image.open(test_file) as reloaded:
 | 
				
			||||||
 | 
					            assert isinstance(reloaded, PngImagePlugin.PngImageFile)
 | 
				
			||||||
            assert reloaded._getexif() is None
 | 
					            assert reloaded._getexif() is None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Test passing in exif
 | 
					        # Test passing in exif
 | 
				
			||||||
| 
						 | 
					@ -745,7 +748,7 @@ class TestFilePng:
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    def test_exif_from_jpg(self, tmp_path: Path) -> None:
 | 
					    def test_exif_from_jpg(self, tmp_path: Path) -> None:
 | 
				
			||||||
        with Image.open("Tests/images/pil_sample_rgb.jpg") as im:
 | 
					        with Image.open("Tests/images/pil_sample_rgb.jpg") as im:
 | 
				
			||||||
            test_file = str(tmp_path / "temp.png")
 | 
					            test_file = tmp_path / "temp.png"
 | 
				
			||||||
            im.save(test_file, exif=im.getexif())
 | 
					            im.save(test_file, exif=im.getexif())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        with Image.open(test_file) as reloaded:
 | 
					        with Image.open(test_file) as reloaded:
 | 
				
			||||||
| 
						 | 
					@ -754,7 +757,7 @@ class TestFilePng:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_exif_argument(self, tmp_path: Path) -> None:
 | 
					    def test_exif_argument(self, tmp_path: Path) -> None:
 | 
				
			||||||
        with Image.open(TEST_PNG_FILE) as im:
 | 
					        with Image.open(TEST_PNG_FILE) as im:
 | 
				
			||||||
            test_file = str(tmp_path / "temp.png")
 | 
					            test_file = tmp_path / "temp.png"
 | 
				
			||||||
            im.save(test_file, exif=b"exifstring")
 | 
					            im.save(test_file, exif=b"exifstring")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        with Image.open(test_file) as reloaded:
 | 
					        with Image.open(test_file) as reloaded:
 | 
				
			||||||
| 
						 | 
					@ -772,38 +775,31 @@ class TestFilePng:
 | 
				
			||||||
                im.seek(1)
 | 
					                im.seek(1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @pytest.mark.parametrize("buffer", (True, False))
 | 
					    @pytest.mark.parametrize("buffer", (True, False))
 | 
				
			||||||
    def test_save_stdout(self, buffer: bool) -> None:
 | 
					    def test_save_stdout(self, buffer: bool, monkeypatch: pytest.MonkeyPatch) -> None:
 | 
				
			||||||
        old_stdout = sys.stdout
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        class MyStdOut:
 | 
					        class MyStdOut:
 | 
				
			||||||
            buffer = BytesIO()
 | 
					            buffer = BytesIO()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        mystdout: MyStdOut | BytesIO = MyStdOut() if buffer else BytesIO()
 | 
					        mystdout: MyStdOut | BytesIO = MyStdOut() if buffer else BytesIO()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        sys.stdout = mystdout
 | 
					        monkeypatch.setattr(sys, "stdout", mystdout)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        with Image.open(TEST_PNG_FILE) as im:
 | 
					        with Image.open(TEST_PNG_FILE) as im:
 | 
				
			||||||
            im.save(sys.stdout, "PNG")
 | 
					            im.save(sys.stdout, "PNG")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Reset stdout
 | 
					 | 
				
			||||||
        sys.stdout = old_stdout
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if isinstance(mystdout, MyStdOut):
 | 
					        if isinstance(mystdout, MyStdOut):
 | 
				
			||||||
            mystdout = mystdout.buffer
 | 
					            mystdout = mystdout.buffer
 | 
				
			||||||
        with Image.open(mystdout) as reloaded:
 | 
					        with Image.open(mystdout) as reloaded:
 | 
				
			||||||
            assert_image_equal_tofile(reloaded, TEST_PNG_FILE)
 | 
					            assert_image_equal_tofile(reloaded, TEST_PNG_FILE)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_truncated_end_chunk(self) -> None:
 | 
					    def test_truncated_end_chunk(self, monkeypatch: pytest.MonkeyPatch) -> None:
 | 
				
			||||||
        with Image.open("Tests/images/truncated_end_chunk.png") as im:
 | 
					        with Image.open("Tests/images/truncated_end_chunk.png") as im:
 | 
				
			||||||
            with pytest.raises(OSError):
 | 
					            with pytest.raises(OSError):
 | 
				
			||||||
                im.load()
 | 
					                im.load()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ImageFile.LOAD_TRUNCATED_IMAGES = True
 | 
					        monkeypatch.setattr(ImageFile, "LOAD_TRUNCATED_IMAGES", True)
 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
        with Image.open("Tests/images/truncated_end_chunk.png") as im:
 | 
					        with Image.open("Tests/images/truncated_end_chunk.png") as im:
 | 
				
			||||||
            assert_image_equal_tofile(im, "Tests/images/hopper.png")
 | 
					            assert_image_equal_tofile(im, "Tests/images/hopper.png")
 | 
				
			||||||
        finally:
 | 
					 | 
				
			||||||
            ImageFile.LOAD_TRUNCATED_IMAGES = False
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@pytest.mark.skipif(is_win32(), reason="Requires Unix or macOS")
 | 
					@pytest.mark.skipif(is_win32(), reason="Requires Unix or macOS")
 | 
				
			||||||
| 
						 | 
					@ -812,11 +808,11 @@ class TestTruncatedPngPLeaks(PillowLeakTestCase):
 | 
				
			||||||
    mem_limit = 2 * 1024  # max increase in K
 | 
					    mem_limit = 2 * 1024  # max increase in K
 | 
				
			||||||
    iterations = 100  # Leak is 56k/iteration, this will leak 5.6megs
 | 
					    iterations = 100  # Leak is 56k/iteration, this will leak 5.6megs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_leak_load(self) -> None:
 | 
					    def test_leak_load(self, monkeypatch: pytest.MonkeyPatch) -> None:
 | 
				
			||||||
        with open("Tests/images/hopper.png", "rb") as f:
 | 
					        with open("Tests/images/hopper.png", "rb") as f:
 | 
				
			||||||
            DATA = BytesIO(f.read(16 * 1024))
 | 
					            DATA = BytesIO(f.read(16 * 1024))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ImageFile.LOAD_TRUNCATED_IMAGES = True
 | 
					        monkeypatch.setattr(ImageFile, "LOAD_TRUNCATED_IMAGES", True)
 | 
				
			||||||
        with Image.open(DATA) as im:
 | 
					        with Image.open(DATA) as im:
 | 
				
			||||||
            im.load()
 | 
					            im.load()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -824,7 +820,4 @@ class TestTruncatedPngPLeaks(PillowLeakTestCase):
 | 
				
			||||||
            with Image.open(DATA) as im:
 | 
					            with Image.open(DATA) as im:
 | 
				
			||||||
                im.load()
 | 
					                im.load()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
        self._test_leak(core)
 | 
					        self._test_leak(core)
 | 
				
			||||||
        finally:
 | 
					 | 
				
			||||||
            ImageFile.LOAD_TRUNCATED_IMAGES = False
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -49,7 +49,7 @@ def test_sanity() -> None:
 | 
				
			||||||
        (b"P5 3 1 257 \x00\x00\x00\x80\x01\x01", "I", (0, 32640, 65535)),
 | 
					        (b"P5 3 1 257 \x00\x00\x00\x80\x01\x01", "I", (0, 32640, 65535)),
 | 
				
			||||||
        # P6 with maxval < 255
 | 
					        # P6 with maxval < 255
 | 
				
			||||||
        (
 | 
					        (
 | 
				
			||||||
            b"P6 3 1 17 \x00\x01\x02\x08\x09\x0A\x0F\x10\x11",
 | 
					            b"P6 3 1 17 \x00\x01\x02\x08\x09\x0a\x0f\x10\x11",
 | 
				
			||||||
            "RGB",
 | 
					            "RGB",
 | 
				
			||||||
            (
 | 
					            (
 | 
				
			||||||
                (0, 15, 30),
 | 
					                (0, 15, 30),
 | 
				
			||||||
| 
						 | 
					@ -60,7 +60,7 @@ def test_sanity() -> None:
 | 
				
			||||||
        # P6 with maxval > 255
 | 
					        # P6 with maxval > 255
 | 
				
			||||||
        (
 | 
					        (
 | 
				
			||||||
            b"P6 3 1 257 \x00\x00\x00\x01\x00\x02"
 | 
					            b"P6 3 1 257 \x00\x00\x00\x01\x00\x02"
 | 
				
			||||||
            b"\x00\x80\x00\x81\x00\x82\x01\x00\x01\x01\xFF\xFF",
 | 
					            b"\x00\x80\x00\x81\x00\x82\x01\x00\x01\x01\xff\xff",
 | 
				
			||||||
            "RGB",
 | 
					            "RGB",
 | 
				
			||||||
            (
 | 
					            (
 | 
				
			||||||
                (0, 1, 2),
 | 
					                (0, 1, 2),
 | 
				
			||||||
| 
						 | 
					@ -79,6 +79,7 @@ def test_arbitrary_maxval(
 | 
				
			||||||
        assert im.mode == mode
 | 
					        assert im.mode == mode
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        px = im.load()
 | 
					        px = im.load()
 | 
				
			||||||
 | 
					        assert px is not None
 | 
				
			||||||
        assert tuple(px[x, 0] for x in range(3)) == pixels
 | 
					        assert tuple(px[x, 0] for x in range(3)) == pixels
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -93,7 +94,7 @@ def test_16bit_pgm() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_16bit_pgm_write(tmp_path: Path) -> None:
 | 
					def test_16bit_pgm_write(tmp_path: Path) -> None:
 | 
				
			||||||
    with Image.open("Tests/images/16_bit_binary.pgm") as im:
 | 
					    with Image.open("Tests/images/16_bit_binary.pgm") as im:
 | 
				
			||||||
        filename = str(tmp_path / "temp.pgm")
 | 
					        filename = tmp_path / "temp.pgm"
 | 
				
			||||||
        im.save(filename, "PPM")
 | 
					        im.save(filename, "PPM")
 | 
				
			||||||
        assert_image_equal_tofile(im, filename)
 | 
					        assert_image_equal_tofile(im, filename)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -105,7 +106,7 @@ def test_pnm(tmp_path: Path) -> None:
 | 
				
			||||||
    with Image.open("Tests/images/hopper.pnm") as im:
 | 
					    with Image.open("Tests/images/hopper.pnm") as im:
 | 
				
			||||||
        assert_image_similar(im, hopper(), 0.0001)
 | 
					        assert_image_similar(im, hopper(), 0.0001)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        filename = str(tmp_path / "temp.pnm")
 | 
					        filename = tmp_path / "temp.pnm"
 | 
				
			||||||
        im.save(filename)
 | 
					        im.save(filename)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        assert_image_equal_tofile(im, filename)
 | 
					        assert_image_equal_tofile(im, filename)
 | 
				
			||||||
| 
						 | 
					@ -116,7 +117,7 @@ def test_pfm(tmp_path: Path) -> None:
 | 
				
			||||||
        assert im.info["scale"] == 1.0
 | 
					        assert im.info["scale"] == 1.0
 | 
				
			||||||
        assert_image_equal(im, hopper("F"))
 | 
					        assert_image_equal(im, hopper("F"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        filename = str(tmp_path / "tmp.pfm")
 | 
					        filename = tmp_path / "tmp.pfm"
 | 
				
			||||||
        im.save(filename)
 | 
					        im.save(filename)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        assert_image_equal_tofile(im, filename)
 | 
					        assert_image_equal_tofile(im, filename)
 | 
				
			||||||
| 
						 | 
					@ -127,7 +128,7 @@ def test_pfm_big_endian(tmp_path: Path) -> None:
 | 
				
			||||||
        assert im.info["scale"] == 2.5
 | 
					        assert im.info["scale"] == 2.5
 | 
				
			||||||
        assert_image_equal(im, hopper("F"))
 | 
					        assert_image_equal(im, hopper("F"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        filename = str(tmp_path / "tmp.pfm")
 | 
					        filename = tmp_path / "tmp.pfm"
 | 
				
			||||||
        im.save(filename)
 | 
					        im.save(filename)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        assert_image_equal_tofile(im, filename)
 | 
					        assert_image_equal_tofile(im, filename)
 | 
				
			||||||
| 
						 | 
					@ -193,8 +194,8 @@ def test_16bit_plain_pgm() -> None:
 | 
				
			||||||
def test_plain_data_with_comment(
 | 
					def test_plain_data_with_comment(
 | 
				
			||||||
    tmp_path: Path, header: bytes, data: bytes, comment_count: int
 | 
					    tmp_path: Path, header: bytes, data: bytes, comment_count: int
 | 
				
			||||||
) -> None:
 | 
					) -> None:
 | 
				
			||||||
    path1 = str(tmp_path / "temp1.ppm")
 | 
					    path1 = tmp_path / "temp1.ppm"
 | 
				
			||||||
    path2 = str(tmp_path / "temp2.ppm")
 | 
					    path2 = tmp_path / "temp2.ppm"
 | 
				
			||||||
    comment = b"# comment" * comment_count
 | 
					    comment = b"# comment" * comment_count
 | 
				
			||||||
    with open(path1, "wb") as f1, open(path2, "wb") as f2:
 | 
					    with open(path1, "wb") as f1, open(path2, "wb") as f2:
 | 
				
			||||||
        f1.write(header + b"\n\n" + data)
 | 
					        f1.write(header + b"\n\n" + data)
 | 
				
			||||||
| 
						 | 
					@ -206,7 +207,7 @@ def test_plain_data_with_comment(
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@pytest.mark.parametrize("data", (b"P1\n128 128\n", b"P3\n128 128\n255\n"))
 | 
					@pytest.mark.parametrize("data", (b"P1\n128 128\n", b"P3\n128 128\n255\n"))
 | 
				
			||||||
def test_plain_truncated_data(tmp_path: Path, data: bytes) -> None:
 | 
					def test_plain_truncated_data(tmp_path: Path, data: bytes) -> None:
 | 
				
			||||||
    path = str(tmp_path / "temp.ppm")
 | 
					    path = tmp_path / "temp.ppm"
 | 
				
			||||||
    with open(path, "wb") as f:
 | 
					    with open(path, "wb") as f:
 | 
				
			||||||
        f.write(data)
 | 
					        f.write(data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -217,7 +218,7 @@ def test_plain_truncated_data(tmp_path: Path, data: bytes) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@pytest.mark.parametrize("data", (b"P1\n128 128\n1009", b"P3\n128 128\n255\n100A"))
 | 
					@pytest.mark.parametrize("data", (b"P1\n128 128\n1009", b"P3\n128 128\n255\n100A"))
 | 
				
			||||||
def test_plain_invalid_data(tmp_path: Path, data: bytes) -> None:
 | 
					def test_plain_invalid_data(tmp_path: Path, data: bytes) -> None:
 | 
				
			||||||
    path = str(tmp_path / "temp.ppm")
 | 
					    path = tmp_path / "temp.ppm"
 | 
				
			||||||
    with open(path, "wb") as f:
 | 
					    with open(path, "wb") as f:
 | 
				
			||||||
        f.write(data)
 | 
					        f.write(data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -234,7 +235,7 @@ def test_plain_invalid_data(tmp_path: Path, data: bytes) -> None:
 | 
				
			||||||
    ),
 | 
					    ),
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
def test_plain_ppm_token_too_long(tmp_path: Path, data: bytes) -> None:
 | 
					def test_plain_ppm_token_too_long(tmp_path: Path, data: bytes) -> None:
 | 
				
			||||||
    path = str(tmp_path / "temp.ppm")
 | 
					    path = tmp_path / "temp.ppm"
 | 
				
			||||||
    with open(path, "wb") as f:
 | 
					    with open(path, "wb") as f:
 | 
				
			||||||
        f.write(data)
 | 
					        f.write(data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -244,7 +245,7 @@ def test_plain_ppm_token_too_long(tmp_path: Path, data: bytes) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_plain_ppm_value_negative(tmp_path: Path) -> None:
 | 
					def test_plain_ppm_value_negative(tmp_path: Path) -> None:
 | 
				
			||||||
    path = str(tmp_path / "temp.ppm")
 | 
					    path = tmp_path / "temp.ppm"
 | 
				
			||||||
    with open(path, "wb") as f:
 | 
					    with open(path, "wb") as f:
 | 
				
			||||||
        f.write(b"P3\n128 128\n255\n-1")
 | 
					        f.write(b"P3\n128 128\n255\n-1")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -254,7 +255,7 @@ def test_plain_ppm_value_negative(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_plain_ppm_value_too_large(tmp_path: Path) -> None:
 | 
					def test_plain_ppm_value_too_large(tmp_path: Path) -> None:
 | 
				
			||||||
    path = str(tmp_path / "temp.ppm")
 | 
					    path = tmp_path / "temp.ppm"
 | 
				
			||||||
    with open(path, "wb") as f:
 | 
					    with open(path, "wb") as f:
 | 
				
			||||||
        f.write(b"P3\n128 128\n255\n256")
 | 
					        f.write(b"P3\n128 128\n255\n256")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -269,7 +270,7 @@ def test_magic() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_header_with_comments(tmp_path: Path) -> None:
 | 
					def test_header_with_comments(tmp_path: Path) -> None:
 | 
				
			||||||
    path = str(tmp_path / "temp.ppm")
 | 
					    path = tmp_path / "temp.ppm"
 | 
				
			||||||
    with open(path, "wb") as f:
 | 
					    with open(path, "wb") as f:
 | 
				
			||||||
        f.write(b"P6 #comment\n#comment\r12#comment\r8\n128 #comment\n255\n")
 | 
					        f.write(b"P6 #comment\n#comment\r12#comment\r8\n128 #comment\n255\n")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -278,7 +279,7 @@ def test_header_with_comments(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_non_integer_token(tmp_path: Path) -> None:
 | 
					def test_non_integer_token(tmp_path: Path) -> None:
 | 
				
			||||||
    path = str(tmp_path / "temp.ppm")
 | 
					    path = tmp_path / "temp.ppm"
 | 
				
			||||||
    with open(path, "wb") as f:
 | 
					    with open(path, "wb") as f:
 | 
				
			||||||
        f.write(b"P6\nTEST")
 | 
					        f.write(b"P6\nTEST")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -288,29 +289,25 @@ def test_non_integer_token(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_header_token_too_long(tmp_path: Path) -> None:
 | 
					def test_header_token_too_long(tmp_path: Path) -> None:
 | 
				
			||||||
    path = str(tmp_path / "temp.ppm")
 | 
					    path = tmp_path / "temp.ppm"
 | 
				
			||||||
    with open(path, "wb") as f:
 | 
					    with open(path, "wb") as f:
 | 
				
			||||||
        f.write(b"P6\n 01234567890")
 | 
					        f.write(b"P6\n 01234567890")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with pytest.raises(ValueError) as e:
 | 
					    with pytest.raises(ValueError, match="Token too long in file header: 01234567890"):
 | 
				
			||||||
        with Image.open(path):
 | 
					        with Image.open(path):
 | 
				
			||||||
            pass
 | 
					            pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    assert str(e.value) == "Token too long in file header: 01234567890"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_truncated_file(tmp_path: Path) -> None:
 | 
					def test_truncated_file(tmp_path: Path) -> None:
 | 
				
			||||||
    # Test EOF in header
 | 
					    # Test EOF in header
 | 
				
			||||||
    path = str(tmp_path / "temp.pgm")
 | 
					    path = tmp_path / "temp.pgm"
 | 
				
			||||||
    with open(path, "wb") as f:
 | 
					    with open(path, "wb") as f:
 | 
				
			||||||
        f.write(b"P6")
 | 
					        f.write(b"P6")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with pytest.raises(ValueError) as e:
 | 
					    with pytest.raises(ValueError, match="Reached EOF while reading header"):
 | 
				
			||||||
        with Image.open(path):
 | 
					        with Image.open(path):
 | 
				
			||||||
            pass
 | 
					            pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    assert str(e.value) == "Reached EOF while reading header"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # Test EOF for PyDecoder
 | 
					    # Test EOF for PyDecoder
 | 
				
			||||||
    fp = BytesIO(b"P5 3 1 4")
 | 
					    fp = BytesIO(b"P5 3 1 4")
 | 
				
			||||||
    with Image.open(fp) as im:
 | 
					    with Image.open(fp) as im:
 | 
				
			||||||
| 
						 | 
					@ -319,7 +316,7 @@ def test_truncated_file(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_not_enough_image_data(tmp_path: Path) -> None:
 | 
					def test_not_enough_image_data(tmp_path: Path) -> None:
 | 
				
			||||||
    path = str(tmp_path / "temp.ppm")
 | 
					    path = tmp_path / "temp.ppm"
 | 
				
			||||||
    with open(path, "wb") as f:
 | 
					    with open(path, "wb") as f:
 | 
				
			||||||
        f.write(b"P2 1 2 255 255")
 | 
					        f.write(b"P2 1 2 255 255")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -330,16 +327,16 @@ def test_not_enough_image_data(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@pytest.mark.parametrize("maxval", (b"0", b"65536"))
 | 
					@pytest.mark.parametrize("maxval", (b"0", b"65536"))
 | 
				
			||||||
def test_invalid_maxval(maxval: bytes, tmp_path: Path) -> None:
 | 
					def test_invalid_maxval(maxval: bytes, tmp_path: Path) -> None:
 | 
				
			||||||
    path = str(tmp_path / "temp.ppm")
 | 
					    path = tmp_path / "temp.ppm"
 | 
				
			||||||
    with open(path, "wb") as f:
 | 
					    with open(path, "wb") as f:
 | 
				
			||||||
        f.write(b"P6\n3 1 " + maxval)
 | 
					        f.write(b"P6\n3 1 " + maxval)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with pytest.raises(ValueError) as e:
 | 
					    with pytest.raises(
 | 
				
			||||||
 | 
					        ValueError, match="maxval must be greater than 0 and less than 65536"
 | 
				
			||||||
 | 
					    ):
 | 
				
			||||||
        with Image.open(path):
 | 
					        with Image.open(path):
 | 
				
			||||||
            pass
 | 
					            pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    assert str(e.value) == "maxval must be greater than 0 and less than 65536"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_neg_ppm() -> None:
 | 
					def test_neg_ppm() -> None:
 | 
				
			||||||
    # Storage.c accepted negative values for xsize, ysize.  the
 | 
					    # Storage.c accepted negative values for xsize, ysize.  the
 | 
				
			||||||
| 
						 | 
					@ -353,7 +350,7 @@ def test_neg_ppm() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_mimetypes(tmp_path: Path) -> None:
 | 
					def test_mimetypes(tmp_path: Path) -> None:
 | 
				
			||||||
    path = str(tmp_path / "temp.pgm")
 | 
					    path = tmp_path / "temp.pgm"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with open(path, "wb") as f:
 | 
					    with open(path, "wb") as f:
 | 
				
			||||||
        f.write(b"P4\n128 128\n255")
 | 
					        f.write(b"P4\n128 128\n255")
 | 
				
			||||||
| 
						 | 
					@ -367,22 +364,18 @@ def test_mimetypes(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@pytest.mark.parametrize("buffer", (True, False))
 | 
					@pytest.mark.parametrize("buffer", (True, False))
 | 
				
			||||||
def test_save_stdout(buffer: bool) -> None:
 | 
					def test_save_stdout(buffer: bool, monkeypatch: pytest.MonkeyPatch) -> None:
 | 
				
			||||||
    old_stdout = sys.stdout
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    class MyStdOut:
 | 
					    class MyStdOut:
 | 
				
			||||||
        buffer = BytesIO()
 | 
					        buffer = BytesIO()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    mystdout: MyStdOut | BytesIO = MyStdOut() if buffer else BytesIO()
 | 
					    mystdout: MyStdOut | BytesIO = MyStdOut() if buffer else BytesIO()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    sys.stdout = mystdout
 | 
					    monkeypatch.setattr(sys, "stdout", mystdout)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open(TEST_FILE) as im:
 | 
					    with Image.open(TEST_FILE) as im:
 | 
				
			||||||
        im.save(sys.stdout, "PPM")
 | 
					        im.save(sys.stdout, "PPM")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Reset stdout
 | 
					 | 
				
			||||||
    sys.stdout = old_stdout
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if isinstance(mystdout, MyStdOut):
 | 
					    if isinstance(mystdout, MyStdOut):
 | 
				
			||||||
        mystdout = mystdout.buffer
 | 
					        mystdout = mystdout.buffer
 | 
				
			||||||
    with Image.open(mystdout) as reloaded:
 | 
					    with Image.open(mystdout) as reloaded:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -25,12 +25,12 @@ def test_sanity() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@pytest.mark.skipif(is_pypy(), reason="Requires CPython")
 | 
					@pytest.mark.skipif(is_pypy(), reason="Requires CPython")
 | 
				
			||||||
def test_unclosed_file() -> None:
 | 
					def test_unclosed_file() -> None:
 | 
				
			||||||
    def open() -> None:
 | 
					    def open_test_image() -> None:
 | 
				
			||||||
        im = Image.open(test_file)
 | 
					        im = Image.open(test_file)
 | 
				
			||||||
        im.load()
 | 
					        im.load()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with pytest.warns(ResourceWarning):
 | 
					    with pytest.warns(ResourceWarning):
 | 
				
			||||||
        open()
 | 
					        open_test_image()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_closed_file() -> None:
 | 
					def test_closed_file() -> None:
 | 
				
			||||||
| 
						 | 
					@ -59,17 +59,21 @@ def test_invalid_file() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_n_frames() -> None:
 | 
					def test_n_frames() -> None:
 | 
				
			||||||
    with Image.open("Tests/images/hopper_merged.psd") as im:
 | 
					    with Image.open("Tests/images/hopper_merged.psd") as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, PsdImagePlugin.PsdImageFile)
 | 
				
			||||||
        assert im.n_frames == 1
 | 
					        assert im.n_frames == 1
 | 
				
			||||||
        assert not im.is_animated
 | 
					        assert not im.is_animated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for path in [test_file, "Tests/images/negative_layer_count.psd"]:
 | 
					    for path in [test_file, "Tests/images/negative_layer_count.psd"]:
 | 
				
			||||||
        with Image.open(path) as im:
 | 
					        with Image.open(path) as im:
 | 
				
			||||||
 | 
					            assert isinstance(im, PsdImagePlugin.PsdImageFile)
 | 
				
			||||||
            assert im.n_frames == 2
 | 
					            assert im.n_frames == 2
 | 
				
			||||||
            assert im.is_animated
 | 
					            assert im.is_animated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_eoferror() -> None:
 | 
					def test_eoferror() -> None:
 | 
				
			||||||
    with Image.open(test_file) as im:
 | 
					    with Image.open(test_file) as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, PsdImagePlugin.PsdImageFile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # PSD seek index starts at 1 rather than 0
 | 
					        # PSD seek index starts at 1 rather than 0
 | 
				
			||||||
        n_frames = im.n_frames + 1
 | 
					        n_frames = im.n_frames + 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -119,11 +123,13 @@ def test_rgba() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_negative_top_left_layer() -> None:
 | 
					def test_negative_top_left_layer() -> None:
 | 
				
			||||||
    with Image.open("Tests/images/negative_top_left_layer.psd") as im:
 | 
					    with Image.open("Tests/images/negative_top_left_layer.psd") as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, PsdImagePlugin.PsdImageFile)
 | 
				
			||||||
        assert im.layers[0][2] == (-50, -50, 50, 50)
 | 
					        assert im.layers[0][2] == (-50, -50, 50, 50)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_layer_skip() -> None:
 | 
					def test_layer_skip() -> None:
 | 
				
			||||||
    with Image.open("Tests/images/five_channels.psd") as im:
 | 
					    with Image.open("Tests/images/five_channels.psd") as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, PsdImagePlugin.PsdImageFile)
 | 
				
			||||||
        assert im.n_frames == 1
 | 
					        assert im.n_frames == 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -175,5 +181,6 @@ def test_crashes(test_file: str, raises: type[Exception]) -> None:
 | 
				
			||||||
def test_layer_crashes(test_file: str) -> None:
 | 
					def test_layer_crashes(test_file: str) -> None:
 | 
				
			||||||
    with open(test_file, "rb") as f:
 | 
					    with open(test_file, "rb") as f:
 | 
				
			||||||
        with Image.open(f) as im:
 | 
					        with Image.open(f) as im:
 | 
				
			||||||
 | 
					            assert isinstance(im, PsdImagePlugin.PsdImageFile)
 | 
				
			||||||
            with pytest.raises(SyntaxError):
 | 
					            with pytest.raises(SyntaxError):
 | 
				
			||||||
                im.layers
 | 
					                im.layers
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -71,31 +71,33 @@ def test_invalid_file() -> None:
 | 
				
			||||||
        SgiImagePlugin.SgiImageFile(invalid_file)
 | 
					        SgiImagePlugin.SgiImageFile(invalid_file)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_write(tmp_path: Path) -> None:
 | 
					def roundtrip(img: Image.Image, tmp_path: Path) -> None:
 | 
				
			||||||
    def roundtrip(img: Image.Image) -> None:
 | 
					    out = tmp_path / "temp.sgi"
 | 
				
			||||||
        out = str(tmp_path / "temp.sgi")
 | 
					 | 
				
			||||||
    img.save(out, format="sgi")
 | 
					    img.save(out, format="sgi")
 | 
				
			||||||
    assert_image_equal_tofile(img, out)
 | 
					    assert_image_equal_tofile(img, out)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        out = str(tmp_path / "fp.sgi")
 | 
					    out = tmp_path / "fp.sgi"
 | 
				
			||||||
    with open(out, "wb") as fp:
 | 
					    with open(out, "wb") as fp:
 | 
				
			||||||
        img.save(fp)
 | 
					        img.save(fp)
 | 
				
			||||||
        assert_image_equal_tofile(img, out)
 | 
					        assert_image_equal_tofile(img, out)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        assert not fp.closed
 | 
					        assert not fp.closed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for mode in ("L", "RGB", "RGBA"):
 | 
					 | 
				
			||||||
        roundtrip(hopper(mode))
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Test 1 dimension for an L mode image
 | 
					@pytest.mark.parametrize("mode", ("L", "RGB", "RGBA"))
 | 
				
			||||||
    roundtrip(Image.new("L", (10, 1)))
 | 
					def test_write(mode: str, tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					    roundtrip(hopper(mode), tmp_path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_write_L_mode_1_dimension(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					    roundtrip(Image.new("L", (10, 1)), tmp_path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_write16(tmp_path: Path) -> None:
 | 
					def test_write16(tmp_path: Path) -> None:
 | 
				
			||||||
    test_file = "Tests/images/hopper16.rgb"
 | 
					    test_file = "Tests/images/hopper16.rgb"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open(test_file) as im:
 | 
					    with Image.open(test_file) as im:
 | 
				
			||||||
        out = str(tmp_path / "temp.sgi")
 | 
					        out = tmp_path / "temp.sgi"
 | 
				
			||||||
        im.save(out, format="sgi", bpc=2)
 | 
					        im.save(out, format="sgi", bpc=2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        assert_image_equal_tofile(im, out)
 | 
					        assert_image_equal_tofile(im, out)
 | 
				
			||||||
| 
						 | 
					@ -103,7 +105,7 @@ def test_write16(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_unsupported_mode(tmp_path: Path) -> None:
 | 
					def test_unsupported_mode(tmp_path: Path) -> None:
 | 
				
			||||||
    im = hopper("LA")
 | 
					    im = hopper("LA")
 | 
				
			||||||
    out = str(tmp_path / "temp.sgi")
 | 
					    out = tmp_path / "temp.sgi"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with pytest.raises(ValueError):
 | 
					    with pytest.raises(ValueError):
 | 
				
			||||||
        im.save(out, format="sgi")
 | 
					        im.save(out, format="sgi")
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,7 +7,7 @@ from pathlib import Path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import pytest
 | 
					import pytest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from PIL import Image, ImageSequence, SpiderImagePlugin
 | 
					from PIL import Image, SpiderImagePlugin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .helper import assert_image_equal, hopper, is_pypy
 | 
					from .helper import assert_image_equal, hopper, is_pypy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -24,12 +24,12 @@ def test_sanity() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@pytest.mark.skipif(is_pypy(), reason="Requires CPython")
 | 
					@pytest.mark.skipif(is_pypy(), reason="Requires CPython")
 | 
				
			||||||
def test_unclosed_file() -> None:
 | 
					def test_unclosed_file() -> None:
 | 
				
			||||||
    def open() -> None:
 | 
					    def open_test_image() -> None:
 | 
				
			||||||
        im = Image.open(TEST_FILE)
 | 
					        im = Image.open(TEST_FILE)
 | 
				
			||||||
        im.load()
 | 
					        im.load()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with pytest.warns(ResourceWarning):
 | 
					    with pytest.warns(ResourceWarning):
 | 
				
			||||||
        open()
 | 
					        open_test_image()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_closed_file() -> None:
 | 
					def test_closed_file() -> None:
 | 
				
			||||||
| 
						 | 
					@ -51,7 +51,7 @@ def test_context_manager() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_save(tmp_path: Path) -> None:
 | 
					def test_save(tmp_path: Path) -> None:
 | 
				
			||||||
    # Arrange
 | 
					    # Arrange
 | 
				
			||||||
    temp = str(tmp_path / "temp.spider")
 | 
					    temp = tmp_path / "temp.spider"
 | 
				
			||||||
    im = hopper()
 | 
					    im = hopper()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Act
 | 
					    # Act
 | 
				
			||||||
| 
						 | 
					@ -96,6 +96,7 @@ def test_tell() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_n_frames() -> None:
 | 
					def test_n_frames() -> None:
 | 
				
			||||||
    with Image.open(TEST_FILE) as im:
 | 
					    with Image.open(TEST_FILE) as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, SpiderImagePlugin.SpiderImageFile)
 | 
				
			||||||
        assert im.n_frames == 1
 | 
					        assert im.n_frames == 1
 | 
				
			||||||
        assert not im.is_animated
 | 
					        assert not im.is_animated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -153,8 +154,8 @@ def test_nonstack_file() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_nonstack_dos() -> None:
 | 
					def test_nonstack_dos() -> None:
 | 
				
			||||||
    with Image.open(TEST_FILE) as im:
 | 
					    with Image.open(TEST_FILE) as im:
 | 
				
			||||||
        for i, frame in enumerate(ImageSequence.Iterator(im)):
 | 
					        with pytest.raises(EOFError):
 | 
				
			||||||
            assert i <= 1, "Non-stack DOS file test failed"
 | 
					            im.seek(0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# for issue #4093
 | 
					# for issue #4093
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,10 +1,11 @@
 | 
				
			||||||
from __future__ import annotations
 | 
					from __future__ import annotations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import io
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import pytest
 | 
					import pytest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from PIL import Image, SunImagePlugin
 | 
					from PIL import Image, SunImagePlugin, _binary
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .helper import assert_image_equal_tofile, assert_image_similar, hopper
 | 
					from .helper import assert_image_equal_tofile, assert_image_similar, hopper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -33,6 +34,60 @@ def test_im1() -> None:
 | 
				
			||||||
        assert_image_equal_tofile(im, "Tests/images/sunraster.im1.png")
 | 
					        assert_image_equal_tofile(im, "Tests/images/sunraster.im1.png")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _sun_header(
 | 
				
			||||||
 | 
					    depth: int = 0, file_type: int = 0, palette_length: int = 0
 | 
				
			||||||
 | 
					) -> io.BytesIO:
 | 
				
			||||||
 | 
					    return io.BytesIO(
 | 
				
			||||||
 | 
					        _binary.o32be(0x59A66A95)
 | 
				
			||||||
 | 
					        + b"\x00" * 8
 | 
				
			||||||
 | 
					        + _binary.o32be(depth)
 | 
				
			||||||
 | 
					        + b"\x00" * 4
 | 
				
			||||||
 | 
					        + _binary.o32be(file_type)
 | 
				
			||||||
 | 
					        + b"\x00" * 4
 | 
				
			||||||
 | 
					        + _binary.o32be(palette_length)
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_unsupported_mode_bit_depth() -> None:
 | 
				
			||||||
 | 
					    with pytest.raises(SyntaxError, match="Unsupported Mode/Bit Depth"):
 | 
				
			||||||
 | 
					        with SunImagePlugin.SunImageFile(_sun_header()):
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_unsupported_color_palette_length() -> None:
 | 
				
			||||||
 | 
					    with pytest.raises(SyntaxError, match="Unsupported Color Palette Length"):
 | 
				
			||||||
 | 
					        with SunImagePlugin.SunImageFile(_sun_header(depth=1, palette_length=1025)):
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_unsupported_palette_type() -> None:
 | 
				
			||||||
 | 
					    with pytest.raises(SyntaxError, match="Unsupported Palette Type"):
 | 
				
			||||||
 | 
					        with SunImagePlugin.SunImageFile(_sun_header(depth=1, palette_length=1)):
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_unsupported_file_type() -> None:
 | 
				
			||||||
 | 
					    with pytest.raises(SyntaxError, match="Unsupported Sun Raster file type"):
 | 
				
			||||||
 | 
					        with SunImagePlugin.SunImageFile(_sun_header(depth=1, file_type=6)):
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@pytest.mark.skipif(
 | 
				
			||||||
 | 
					    not os.path.exists(EXTRA_DIR), reason="Extra image files not installed"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					def test_rgbx() -> None:
 | 
				
			||||||
 | 
					    with open(os.path.join(EXTRA_DIR, "32bpp.ras"), "rb") as fp:
 | 
				
			||||||
 | 
					        data = fp.read()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Set file type to 3
 | 
				
			||||||
 | 
					    data = data[:20] + _binary.o32be(3) + data[24:]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    with Image.open(io.BytesIO(data)) as im:
 | 
				
			||||||
 | 
					        r, g, b = im.split()
 | 
				
			||||||
 | 
					        im = Image.merge("RGB", (b, g, r))
 | 
				
			||||||
 | 
					        assert_image_equal_tofile(im, os.path.join(EXTRA_DIR, "32bpp.png"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@pytest.mark.skipif(
 | 
					@pytest.mark.skipif(
 | 
				
			||||||
    not os.path.exists(EXTRA_DIR), reason="Extra image files not installed"
 | 
					    not os.path.exists(EXTRA_DIR), reason="Extra image files not installed"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,7 @@
 | 
				
			||||||
from __future__ import annotations
 | 
					from __future__ import annotations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import warnings
 | 
					import warnings
 | 
				
			||||||
 | 
					from pathlib import Path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import pytest
 | 
					import pytest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -29,6 +30,22 @@ def test_sanity(codec: str, test_path: str, format: str) -> None:
 | 
				
			||||||
                assert im.format == format
 | 
					                assert im.format == format
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_unexpected_end(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					    tmpfile = str(tmp_path / "temp.tar")
 | 
				
			||||||
 | 
					    with open(tmpfile, "w"):
 | 
				
			||||||
 | 
					        pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    with pytest.raises(OSError, match="unexpected end of tar file"):
 | 
				
			||||||
 | 
					        with TarIO.TarIO(tmpfile, "test"):
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_cannot_find_subfile() -> None:
 | 
				
			||||||
 | 
					    with pytest.raises(OSError, match="cannot find subfile"):
 | 
				
			||||||
 | 
					        with TarIO.TarIO(TEST_TAR_FILE, "test"):
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@pytest.mark.skipif(is_pypy(), reason="Requires CPython")
 | 
					@pytest.mark.skipif(is_pypy(), reason="Requires CPython")
 | 
				
			||||||
def test_unclosed_file() -> None:
 | 
					def test_unclosed_file() -> None:
 | 
				
			||||||
    with pytest.warns(ResourceWarning):
 | 
					    with pytest.warns(ResourceWarning):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,8 +1,6 @@
 | 
				
			||||||
from __future__ import annotations
 | 
					from __future__ import annotations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
from glob import glob
 | 
					 | 
				
			||||||
from itertools import product
 | 
					 | 
				
			||||||
from pathlib import Path
 | 
					from pathlib import Path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import pytest
 | 
					import pytest
 | 
				
			||||||
| 
						 | 
					@ -15,16 +13,29 @@ _TGA_DIR = os.path.join("Tests", "images", "tga")
 | 
				
			||||||
_TGA_DIR_COMMON = os.path.join(_TGA_DIR, "common")
 | 
					_TGA_DIR_COMMON = os.path.join(_TGA_DIR, "common")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
_MODES = ("L", "LA", "P", "RGB", "RGBA")
 | 
					 | 
				
			||||||
_ORIGINS = ("tl", "bl")
 | 
					_ORIGINS = ("tl", "bl")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
_ORIGIN_TO_ORIENTATION = {"tl": 1, "bl": -1}
 | 
					_ORIGIN_TO_ORIENTATION = {"tl": 1, "bl": -1}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@pytest.mark.parametrize("mode", _MODES)
 | 
					@pytest.mark.parametrize(
 | 
				
			||||||
def test_sanity(mode: str, tmp_path: Path) -> None:
 | 
					    "size_mode",
 | 
				
			||||||
 | 
					    (
 | 
				
			||||||
 | 
					        ("1x1", "L"),
 | 
				
			||||||
 | 
					        ("200x32", "L"),
 | 
				
			||||||
 | 
					        ("200x32", "LA"),
 | 
				
			||||||
 | 
					        ("200x32", "P"),
 | 
				
			||||||
 | 
					        ("200x32", "RGB"),
 | 
				
			||||||
 | 
					        ("200x32", "RGBA"),
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					@pytest.mark.parametrize("origin", _ORIGINS)
 | 
				
			||||||
 | 
					@pytest.mark.parametrize("rle", (True, False))
 | 
				
			||||||
 | 
					def test_sanity(
 | 
				
			||||||
 | 
					    size_mode: tuple[str, str], origin: str, rle: str, tmp_path: Path
 | 
				
			||||||
 | 
					) -> None:
 | 
				
			||||||
    def roundtrip(original_im: Image.Image) -> None:
 | 
					    def roundtrip(original_im: Image.Image) -> None:
 | 
				
			||||||
        out = str(tmp_path / "temp.tga")
 | 
					        out = tmp_path / "temp.tga"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        original_im.save(out, rle=rle)
 | 
					        original_im.save(out, rle=rle)
 | 
				
			||||||
        with Image.open(out) as saved_im:
 | 
					        with Image.open(out) as saved_im:
 | 
				
			||||||
| 
						 | 
					@ -36,27 +47,20 @@ def test_sanity(mode: str, tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            assert_image_equal(saved_im, original_im)
 | 
					            assert_image_equal(saved_im, original_im)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    png_paths = glob(os.path.join(_TGA_DIR_COMMON, f"*x*_{mode.lower()}.png"))
 | 
					    size, mode = size_mode
 | 
				
			||||||
 | 
					    png_path = os.path.join(_TGA_DIR_COMMON, size + "_" + mode.lower() + ".png")
 | 
				
			||||||
    for png_path in png_paths:
 | 
					 | 
				
			||||||
    with Image.open(png_path) as reference_im:
 | 
					    with Image.open(png_path) as reference_im:
 | 
				
			||||||
        assert reference_im.mode == mode
 | 
					        assert reference_im.mode == mode
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        path_no_ext = os.path.splitext(png_path)[0]
 | 
					        path_no_ext = os.path.splitext(png_path)[0]
 | 
				
			||||||
            for origin, rle in product(_ORIGINS, (True, False)):
 | 
					        tga_path = "{}_{}_{}.tga".format(path_no_ext, origin, "rle" if rle else "raw")
 | 
				
			||||||
                tga_path = "{}_{}_{}.tga".format(
 | 
					 | 
				
			||||||
                    path_no_ext, origin, "rle" if rle else "raw"
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        with Image.open(tga_path) as original_im:
 | 
					        with Image.open(tga_path) as original_im:
 | 
				
			||||||
            assert original_im.format == "TGA"
 | 
					            assert original_im.format == "TGA"
 | 
				
			||||||
            assert original_im.get_format_mimetype() == "image/x-tga"
 | 
					            assert original_im.get_format_mimetype() == "image/x-tga"
 | 
				
			||||||
            if rle:
 | 
					            if rle:
 | 
				
			||||||
                assert original_im.info["compression"] == "tga_rle"
 | 
					                assert original_im.info["compression"] == "tga_rle"
 | 
				
			||||||
                    assert (
 | 
					            assert original_im.info["orientation"] == _ORIGIN_TO_ORIENTATION[origin]
 | 
				
			||||||
                        original_im.info["orientation"]
 | 
					 | 
				
			||||||
                        == _ORIGIN_TO_ORIENTATION[origin]
 | 
					 | 
				
			||||||
                    )
 | 
					 | 
				
			||||||
            if mode == "P":
 | 
					            if mode == "P":
 | 
				
			||||||
                assert original_im.getpalette() == reference_im.getpalette()
 | 
					                assert original_im.getpalette() == reference_im.getpalette()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -65,17 +69,18 @@ def test_sanity(mode: str, tmp_path: Path) -> None:
 | 
				
			||||||
            roundtrip(original_im)
 | 
					            roundtrip(original_im)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_palette_depth_8(tmp_path: Path) -> None:
 | 
					def test_palette_depth_8() -> None:
 | 
				
			||||||
    with pytest.raises(UnidentifiedImageError):
 | 
					    with pytest.raises(UnidentifiedImageError):
 | 
				
			||||||
        Image.open("Tests/images/p_8.tga")
 | 
					        Image.open("Tests/images/p_8.tga")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_palette_depth_16(tmp_path: Path) -> None:
 | 
					def test_palette_depth_16(tmp_path: Path) -> None:
 | 
				
			||||||
    with Image.open("Tests/images/p_16.tga") as im:
 | 
					    with Image.open("Tests/images/p_16.tga") as im:
 | 
				
			||||||
 | 
					        assert im.palette is not None
 | 
				
			||||||
        assert im.palette.mode == "RGBA"
 | 
					        assert im.palette.mode == "RGBA"
 | 
				
			||||||
        assert_image_equal_tofile(im.convert("RGBA"), "Tests/images/p_16.png")
 | 
					        assert_image_equal_tofile(im.convert("RGBA"), "Tests/images/p_16.png")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        out = str(tmp_path / "temp.png")
 | 
					        out = tmp_path / "temp.png"
 | 
				
			||||||
        im.save(out)
 | 
					        im.save(out)
 | 
				
			||||||
        with Image.open(out) as reloaded:
 | 
					        with Image.open(out) as reloaded:
 | 
				
			||||||
            assert_image_equal_tofile(reloaded.convert("RGBA"), "Tests/images/p_16.png")
 | 
					            assert_image_equal_tofile(reloaded.convert("RGBA"), "Tests/images/p_16.png")
 | 
				
			||||||
| 
						 | 
					@ -121,7 +126,7 @@ def test_cross_scan_line() -> None:
 | 
				
			||||||
def test_save(tmp_path: Path) -> None:
 | 
					def test_save(tmp_path: Path) -> None:
 | 
				
			||||||
    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:
 | 
				
			||||||
        out = str(tmp_path / "temp.tga")
 | 
					        out = tmp_path / "temp.tga"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Save
 | 
					        # Save
 | 
				
			||||||
        im.save(out)
 | 
					        im.save(out)
 | 
				
			||||||
| 
						 | 
					@ -140,7 +145,7 @@ def test_small_palette(tmp_path: Path) -> None:
 | 
				
			||||||
    colors = [0, 0, 0]
 | 
					    colors = [0, 0, 0]
 | 
				
			||||||
    im.putpalette(colors)
 | 
					    im.putpalette(colors)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    out = str(tmp_path / "temp.tga")
 | 
					    out = tmp_path / "temp.tga"
 | 
				
			||||||
    im.save(out)
 | 
					    im.save(out)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open(out) as reloaded:
 | 
					    with Image.open(out) as reloaded:
 | 
				
			||||||
| 
						 | 
					@ -154,7 +159,7 @@ def test_missing_palette() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_save_wrong_mode(tmp_path: Path) -> None:
 | 
					def test_save_wrong_mode(tmp_path: Path) -> None:
 | 
				
			||||||
    im = hopper("PA")
 | 
					    im = hopper("PA")
 | 
				
			||||||
    out = str(tmp_path / "temp.tga")
 | 
					    out = tmp_path / "temp.tga"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with pytest.raises(OSError):
 | 
					    with pytest.raises(OSError):
 | 
				
			||||||
        im.save(out)
 | 
					        im.save(out)
 | 
				
			||||||
| 
						 | 
					@ -171,7 +176,7 @@ def test_save_mapdepth() -> None:
 | 
				
			||||||
def test_save_id_section(tmp_path: Path) -> None:
 | 
					def test_save_id_section(tmp_path: Path) -> None:
 | 
				
			||||||
    test_file = "Tests/images/rgb32rle.tga"
 | 
					    test_file = "Tests/images/rgb32rle.tga"
 | 
				
			||||||
    with Image.open(test_file) as im:
 | 
					    with Image.open(test_file) as im:
 | 
				
			||||||
        out = str(tmp_path / "temp.tga")
 | 
					        out = tmp_path / "temp.tga"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Check there is no id section
 | 
					        # Check there is no id section
 | 
				
			||||||
        im.save(out)
 | 
					        im.save(out)
 | 
				
			||||||
| 
						 | 
					@ -201,7 +206,7 @@ def test_save_id_section(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_save_orientation(tmp_path: Path) -> None:
 | 
					def test_save_orientation(tmp_path: Path) -> None:
 | 
				
			||||||
    test_file = "Tests/images/rgb32rle.tga"
 | 
					    test_file = "Tests/images/rgb32rle.tga"
 | 
				
			||||||
    out = str(tmp_path / "temp.tga")
 | 
					    out = tmp_path / "temp.tga"
 | 
				
			||||||
    with Image.open(test_file) as im:
 | 
					    with Image.open(test_file) as im:
 | 
				
			||||||
        assert im.info["orientation"] == -1
 | 
					        assert im.info["orientation"] == -1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -213,10 +218,14 @@ def test_save_orientation(tmp_path: Path) -> None:
 | 
				
			||||||
def test_horizontal_orientations() -> None:
 | 
					def test_horizontal_orientations() -> None:
 | 
				
			||||||
    # These images have been manually hexedited to have the relevant orientations
 | 
					    # These images have been manually hexedited to have the relevant orientations
 | 
				
			||||||
    with Image.open("Tests/images/rgb32rle_top_right.tga") as im:
 | 
					    with Image.open("Tests/images/rgb32rle_top_right.tga") as im:
 | 
				
			||||||
        assert im.load()[90, 90][:3] == (0, 0, 0)
 | 
					        px = im.load()
 | 
				
			||||||
 | 
					        assert px is not None
 | 
				
			||||||
 | 
					        assert px[90, 90][:3] == (0, 0, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open("Tests/images/rgb32rle_bottom_right.tga") as im:
 | 
					    with Image.open("Tests/images/rgb32rle_bottom_right.tga") as im:
 | 
				
			||||||
        assert im.load()[90, 90][:3] == (0, 255, 0)
 | 
					        px = im.load()
 | 
				
			||||||
 | 
					        assert px is not None
 | 
				
			||||||
 | 
					        assert px[90, 90][:3] == (0, 255, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_save_rle(tmp_path: Path) -> None:
 | 
					def test_save_rle(tmp_path: Path) -> None:
 | 
				
			||||||
| 
						 | 
					@ -224,7 +233,7 @@ def test_save_rle(tmp_path: Path) -> None:
 | 
				
			||||||
    with Image.open(test_file) as im:
 | 
					    with Image.open(test_file) as im:
 | 
				
			||||||
        assert im.info["compression"] == "tga_rle"
 | 
					        assert im.info["compression"] == "tga_rle"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        out = str(tmp_path / "temp.tga")
 | 
					        out = tmp_path / "temp.tga"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Save
 | 
					        # Save
 | 
				
			||||||
        im.save(out)
 | 
					        im.save(out)
 | 
				
			||||||
| 
						 | 
					@ -261,7 +270,7 @@ def test_save_l_transparency(tmp_path: Path) -> None:
 | 
				
			||||||
        assert im.mode == "LA"
 | 
					        assert im.mode == "LA"
 | 
				
			||||||
        assert im.getchannel("A").getcolors()[0][0] == num_transparent
 | 
					        assert im.getchannel("A").getcolors()[0][0] == num_transparent
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        out = str(tmp_path / "temp.tga")
 | 
					        out = tmp_path / "temp.tga"
 | 
				
			||||||
        im.save(out)
 | 
					        im.save(out)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open(out) as test_im:
 | 
					    with Image.open(out) as test_im:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,7 +9,13 @@ from types import ModuleType
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import pytest
 | 
					import pytest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from PIL import Image, ImageFile, TiffImagePlugin, UnidentifiedImageError
 | 
					from PIL import (
 | 
				
			||||||
 | 
					    Image,
 | 
				
			||||||
 | 
					    ImageFile,
 | 
				
			||||||
 | 
					    JpegImagePlugin,
 | 
				
			||||||
 | 
					    TiffImagePlugin,
 | 
				
			||||||
 | 
					    UnidentifiedImageError,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
from PIL.TiffImagePlugin import RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION
 | 
					from PIL.TiffImagePlugin import RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .helper import (
 | 
					from .helper import (
 | 
				
			||||||
| 
						 | 
					@ -31,7 +37,7 @@ except ImportError:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TestFileTiff:
 | 
					class TestFileTiff:
 | 
				
			||||||
    def test_sanity(self, tmp_path: Path) -> None:
 | 
					    def test_sanity(self, tmp_path: Path) -> None:
 | 
				
			||||||
        filename = str(tmp_path / "temp.tif")
 | 
					        filename = tmp_path / "temp.tif"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        hopper("RGB").save(filename)
 | 
					        hopper("RGB").save(filename)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -63,12 +69,12 @@ class TestFileTiff:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @pytest.mark.skipif(is_pypy(), reason="Requires CPython")
 | 
					    @pytest.mark.skipif(is_pypy(), reason="Requires CPython")
 | 
				
			||||||
    def test_unclosed_file(self) -> None:
 | 
					    def test_unclosed_file(self) -> None:
 | 
				
			||||||
        def open() -> None:
 | 
					        def open_test_image() -> None:
 | 
				
			||||||
            im = Image.open("Tests/images/multipage.tiff")
 | 
					            im = Image.open("Tests/images/multipage.tiff")
 | 
				
			||||||
            im.load()
 | 
					            im.load()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        with pytest.warns(ResourceWarning):
 | 
					        with pytest.warns(ResourceWarning):
 | 
				
			||||||
            open()
 | 
					            open_test_image()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_closed_file(self) -> None:
 | 
					    def test_closed_file(self) -> None:
 | 
				
			||||||
        with warnings.catch_warnings():
 | 
					        with warnings.catch_warnings():
 | 
				
			||||||
| 
						 | 
					@ -112,22 +118,39 @@ class TestFileTiff:
 | 
				
			||||||
            assert_image_equal_tofile(im, "Tests/images/hopper.tif")
 | 
					            assert_image_equal_tofile(im, "Tests/images/hopper.tif")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        with Image.open("Tests/images/hopper_bigtiff.tif") as im:
 | 
					        with Image.open("Tests/images/hopper_bigtiff.tif") as im:
 | 
				
			||||||
            outfile = str(tmp_path / "temp.tif")
 | 
					            outfile = tmp_path / "temp.tif"
 | 
				
			||||||
 | 
					            assert isinstance(im, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
            im.save(outfile, save_all=True, append_images=[im], tiffinfo=im.tag_v2)
 | 
					            im.save(outfile, save_all=True, append_images=[im], tiffinfo=im.tag_v2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_bigtiff_save(self, tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					        outfile = tmp_path / "temp.tif"
 | 
				
			||||||
 | 
					        im = hopper()
 | 
				
			||||||
 | 
					        im.save(outfile, big_tiff=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        with Image.open(outfile) as reloaded:
 | 
				
			||||||
 | 
					            assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
 | 
					            assert reloaded.tag_v2._bigtiff is True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        im.save(outfile, save_all=True, append_images=[im], big_tiff=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        with Image.open(outfile) as reloaded:
 | 
				
			||||||
 | 
					            assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
 | 
					            assert reloaded.tag_v2._bigtiff is True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_seek_too_large(self) -> None:
 | 
					    def test_seek_too_large(self) -> None:
 | 
				
			||||||
        with pytest.raises(ValueError, match="Unable to seek to frame"):
 | 
					        with pytest.raises(ValueError, match="Unable to seek to frame"):
 | 
				
			||||||
            Image.open("Tests/images/seek_too_large.tif")
 | 
					            Image.open("Tests/images/seek_too_large.tif")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_set_legacy_api(self) -> None:
 | 
					    def test_set_legacy_api(self) -> None:
 | 
				
			||||||
        ifd = TiffImagePlugin.ImageFileDirectory_v2()
 | 
					        ifd = TiffImagePlugin.ImageFileDirectory_v2()
 | 
				
			||||||
        with pytest.raises(Exception) as e:
 | 
					        with pytest.raises(Exception, match="Not allowing setting of legacy api"):
 | 
				
			||||||
            ifd.legacy_api = False
 | 
					            ifd.legacy_api = False
 | 
				
			||||||
        assert str(e.value) == "Not allowing setting of legacy api"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_xyres_tiff(self) -> None:
 | 
					    def test_xyres_tiff(self) -> None:
 | 
				
			||||||
        filename = "Tests/images/pil168.tif"
 | 
					        filename = "Tests/images/pil168.tif"
 | 
				
			||||||
        with Image.open(filename) as im:
 | 
					        with Image.open(filename) as im:
 | 
				
			||||||
 | 
					            assert isinstance(im, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # 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)
 | 
				
			||||||
| 
						 | 
					@ -141,6 +164,8 @@ class TestFileTiff:
 | 
				
			||||||
    def test_xyres_fallback_tiff(self) -> None:
 | 
					    def test_xyres_fallback_tiff(self) -> None:
 | 
				
			||||||
        filename = "Tests/images/compression.tif"
 | 
					        filename = "Tests/images/compression.tif"
 | 
				
			||||||
        with Image.open(filename) as im:
 | 
					        with Image.open(filename) as im:
 | 
				
			||||||
 | 
					            assert isinstance(im, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # 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)
 | 
				
			||||||
| 
						 | 
					@ -155,6 +180,8 @@ class TestFileTiff:
 | 
				
			||||||
    def test_int_resolution(self) -> None:
 | 
					    def test_int_resolution(self) -> None:
 | 
				
			||||||
        filename = "Tests/images/pil168.tif"
 | 
					        filename = "Tests/images/pil168.tif"
 | 
				
			||||||
        with Image.open(filename) as im:
 | 
					        with Image.open(filename) as im:
 | 
				
			||||||
 | 
					            assert isinstance(im, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # 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
 | 
				
			||||||
| 
						 | 
					@ -169,11 +196,12 @@ class TestFileTiff:
 | 
				
			||||||
        with Image.open(
 | 
					        with Image.open(
 | 
				
			||||||
            "Tests/images/hopper_float_dpi_" + str(resolution_unit) + ".tif"
 | 
					            "Tests/images/hopper_float_dpi_" + str(resolution_unit) + ".tif"
 | 
				
			||||||
        ) as im:
 | 
					        ) as im:
 | 
				
			||||||
 | 
					            assert isinstance(im, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
            assert im.tag_v2.get(RESOLUTION_UNIT) == resolution_unit
 | 
					            assert im.tag_v2.get(RESOLUTION_UNIT) == resolution_unit
 | 
				
			||||||
            assert im.info["dpi"] == (dpi, dpi)
 | 
					            assert im.info["dpi"] == (dpi, dpi)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_save_float_dpi(self, tmp_path: Path) -> None:
 | 
					    def test_save_float_dpi(self, tmp_path: Path) -> None:
 | 
				
			||||||
        outfile = str(tmp_path / "temp.tif")
 | 
					        outfile = tmp_path / "temp.tif"
 | 
				
			||||||
        with Image.open("Tests/images/hopper.tif") as im:
 | 
					        with Image.open("Tests/images/hopper.tif") as im:
 | 
				
			||||||
            dpi = (72.2, 72.2)
 | 
					            dpi = (72.2, 72.2)
 | 
				
			||||||
            im.save(outfile, dpi=dpi)
 | 
					            im.save(outfile, dpi=dpi)
 | 
				
			||||||
| 
						 | 
					@ -186,6 +214,7 @@ class TestFileTiff:
 | 
				
			||||||
        with Image.open("Tests/images/10ct_32bit_128.tiff") as im:
 | 
					        with Image.open("Tests/images/10ct_32bit_128.tiff") as im:
 | 
				
			||||||
            im.save(b, format="tiff", resolution=123.45)
 | 
					            im.save(b, format="tiff", resolution=123.45)
 | 
				
			||||||
        with Image.open(b) as im:
 | 
					        with Image.open(b) as im:
 | 
				
			||||||
 | 
					            assert isinstance(im, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
            assert im.tag_v2[X_RESOLUTION] == 123.45
 | 
					            assert im.tag_v2[X_RESOLUTION] == 123.45
 | 
				
			||||||
            assert im.tag_v2[Y_RESOLUTION] == 123.45
 | 
					            assert im.tag_v2[Y_RESOLUTION] == 123.45
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -201,19 +230,21 @@ class TestFileTiff:
 | 
				
			||||||
        TiffImagePlugin.PREFIXES.pop()
 | 
					        TiffImagePlugin.PREFIXES.pop()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_bad_exif(self) -> None:
 | 
					    def test_bad_exif(self) -> None:
 | 
				
			||||||
        with Image.open("Tests/images/hopper_bad_exif.jpg") as i:
 | 
					        with Image.open("Tests/images/hopper_bad_exif.jpg") as im:
 | 
				
			||||||
 | 
					            assert isinstance(im, JpegImagePlugin.JpegImageFile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # Should not raise struct.error.
 | 
					            # Should not raise struct.error.
 | 
				
			||||||
            with pytest.warns(UserWarning):
 | 
					            with pytest.warns(UserWarning):
 | 
				
			||||||
                i._getexif()
 | 
					                im._getexif()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_save_rgba(self, tmp_path: Path) -> None:
 | 
					    def test_save_rgba(self, tmp_path: Path) -> None:
 | 
				
			||||||
        im = hopper("RGBA")
 | 
					        im = hopper("RGBA")
 | 
				
			||||||
        outfile = str(tmp_path / "temp.tif")
 | 
					        outfile = tmp_path / "temp.tif"
 | 
				
			||||||
        im.save(outfile)
 | 
					        im.save(outfile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_save_unsupported_mode(self, tmp_path: Path) -> None:
 | 
					    def test_save_unsupported_mode(self, tmp_path: Path) -> None:
 | 
				
			||||||
        im = hopper("HSV")
 | 
					        im = hopper("HSV")
 | 
				
			||||||
        outfile = str(tmp_path / "temp.tif")
 | 
					        outfile = tmp_path / "temp.tif"
 | 
				
			||||||
        with pytest.raises(OSError):
 | 
					        with pytest.raises(OSError):
 | 
				
			||||||
            im.save(outfile)
 | 
					            im.save(outfile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -295,11 +326,13 @@ class TestFileTiff:
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    def test_n_frames(self, path: str, n_frames: int) -> None:
 | 
					    def test_n_frames(self, path: str, n_frames: int) -> None:
 | 
				
			||||||
        with Image.open(path) as im:
 | 
					        with Image.open(path) as im:
 | 
				
			||||||
 | 
					            assert isinstance(im, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
            assert im.n_frames == n_frames
 | 
					            assert im.n_frames == n_frames
 | 
				
			||||||
            assert im.is_animated == (n_frames != 1)
 | 
					            assert im.is_animated == (n_frames != 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_eoferror(self) -> None:
 | 
					    def test_eoferror(self) -> None:
 | 
				
			||||||
        with Image.open("Tests/images/multipage-lastframe.tif") as im:
 | 
					        with Image.open("Tests/images/multipage-lastframe.tif") as im:
 | 
				
			||||||
 | 
					            assert isinstance(im, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
            n_frames = im.n_frames
 | 
					            n_frames = im.n_frames
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # Test seeking past the last frame
 | 
					            # Test seeking past the last frame
 | 
				
			||||||
| 
						 | 
					@ -343,19 +376,24 @@ class TestFileTiff:
 | 
				
			||||||
    def test_frame_order(self) -> None:
 | 
					    def test_frame_order(self) -> None:
 | 
				
			||||||
        # A frame can't progress to itself after reading
 | 
					        # A frame can't progress to itself after reading
 | 
				
			||||||
        with Image.open("Tests/images/multipage_single_frame_loop.tiff") as im:
 | 
					        with Image.open("Tests/images/multipage_single_frame_loop.tiff") as im:
 | 
				
			||||||
 | 
					            assert isinstance(im, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
            assert im.n_frames == 1
 | 
					            assert im.n_frames == 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # A frame can't progress to a frame that has already been read
 | 
					        # A frame can't progress to a frame that has already been read
 | 
				
			||||||
        with Image.open("Tests/images/multipage_multiple_frame_loop.tiff") as im:
 | 
					        with Image.open("Tests/images/multipage_multiple_frame_loop.tiff") as im:
 | 
				
			||||||
 | 
					            assert isinstance(im, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
            assert im.n_frames == 2
 | 
					            assert im.n_frames == 2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Frames don't have to be in sequence
 | 
					        # Frames don't have to be in sequence
 | 
				
			||||||
        with Image.open("Tests/images/multipage_out_of_order.tiff") as im:
 | 
					        with Image.open("Tests/images/multipage_out_of_order.tiff") as im:
 | 
				
			||||||
 | 
					            assert isinstance(im, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
            assert im.n_frames == 3
 | 
					            assert im.n_frames == 3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test___str__(self) -> None:
 | 
					    def test___str__(self) -> None:
 | 
				
			||||||
        filename = "Tests/images/pil136.tiff"
 | 
					        filename = "Tests/images/pil136.tiff"
 | 
				
			||||||
        with Image.open(filename) as im:
 | 
					        with Image.open(filename) as im:
 | 
				
			||||||
 | 
					            assert isinstance(im, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # Act
 | 
					            # Act
 | 
				
			||||||
            ret = str(im.ifd)
 | 
					            ret = str(im.ifd)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -366,6 +404,8 @@ 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:
 | 
				
			||||||
 | 
					            assert isinstance(im, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # v2 interface
 | 
					            # v2 interface
 | 
				
			||||||
            v2_tags = {
 | 
					            v2_tags = {
 | 
				
			||||||
                256: 55,
 | 
					                256: 55,
 | 
				
			||||||
| 
						 | 
					@ -405,6 +445,7 @@ class TestFileTiff:
 | 
				
			||||||
    def test__delitem__(self) -> None:
 | 
					    def test__delitem__(self) -> None:
 | 
				
			||||||
        filename = "Tests/images/pil136.tiff"
 | 
					        filename = "Tests/images/pil136.tiff"
 | 
				
			||||||
        with Image.open(filename) as im:
 | 
					        with Image.open(filename) as im:
 | 
				
			||||||
 | 
					            assert isinstance(im, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
            len_before = len(dict(im.ifd))
 | 
					            len_before = len(dict(im.ifd))
 | 
				
			||||||
            del im.ifd[256]
 | 
					            del im.ifd[256]
 | 
				
			||||||
            len_after = len(dict(im.ifd))
 | 
					            len_after = len(dict(im.ifd))
 | 
				
			||||||
| 
						 | 
					@ -437,6 +478,7 @@ class TestFileTiff:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_ifd_tag_type(self) -> None:
 | 
					    def test_ifd_tag_type(self) -> None:
 | 
				
			||||||
        with Image.open("Tests/images/ifd_tag_type.tiff") as im:
 | 
					        with Image.open("Tests/images/ifd_tag_type.tiff") as im:
 | 
				
			||||||
 | 
					            assert isinstance(im, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
            assert 0x8825 in im.tag_v2
 | 
					            assert 0x8825 in im.tag_v2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_exif(self, tmp_path: Path) -> None:
 | 
					    def test_exif(self, tmp_path: Path) -> None:
 | 
				
			||||||
| 
						 | 
					@ -473,14 +515,14 @@ class TestFileTiff:
 | 
				
			||||||
            assert gps[0] == b"\x03\x02\x00\x00"
 | 
					            assert gps[0] == b"\x03\x02\x00\x00"
 | 
				
			||||||
            assert gps[18] == "WGS-84"
 | 
					            assert gps[18] == "WGS-84"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        outfile = str(tmp_path / "temp.tif")
 | 
					        outfile = tmp_path / "temp.tif"
 | 
				
			||||||
        with Image.open("Tests/images/ifd_tag_type.tiff") as im:
 | 
					        with Image.open("Tests/images/ifd_tag_type.tiff") as im:
 | 
				
			||||||
            exif = im.getexif()
 | 
					            exif = im.getexif()
 | 
				
			||||||
            check_exif(exif)
 | 
					            check_exif(exif)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            im.save(outfile, exif=exif)
 | 
					            im.save(outfile, exif=exif)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        outfile2 = str(tmp_path / "temp2.tif")
 | 
					        outfile2 = tmp_path / "temp2.tif"
 | 
				
			||||||
        with Image.open(outfile) as im:
 | 
					        with Image.open(outfile) as im:
 | 
				
			||||||
            exif = im.getexif()
 | 
					            exif = im.getexif()
 | 
				
			||||||
            check_exif(exif)
 | 
					            check_exif(exif)
 | 
				
			||||||
| 
						 | 
					@ -492,7 +534,7 @@ class TestFileTiff:
 | 
				
			||||||
            check_exif(exif)
 | 
					            check_exif(exif)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_modify_exif(self, tmp_path: Path) -> None:
 | 
					    def test_modify_exif(self, tmp_path: Path) -> None:
 | 
				
			||||||
        outfile = str(tmp_path / "temp.tif")
 | 
					        outfile = tmp_path / "temp.tif"
 | 
				
			||||||
        with Image.open("Tests/images/ifd_tag_type.tiff") as im:
 | 
					        with Image.open("Tests/images/ifd_tag_type.tiff") as im:
 | 
				
			||||||
            exif = im.getexif()
 | 
					            exif = im.getexif()
 | 
				
			||||||
            exif[264] = 100
 | 
					            exif[264] = 100
 | 
				
			||||||
| 
						 | 
					@ -521,10 +563,11 @@ class TestFileTiff:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @pytest.mark.parametrize("mode", ("1", "L"))
 | 
					    @pytest.mark.parametrize("mode", ("1", "L"))
 | 
				
			||||||
    def test_photometric(self, mode: str, tmp_path: Path) -> None:
 | 
					    def test_photometric(self, mode: str, tmp_path: Path) -> None:
 | 
				
			||||||
        filename = str(tmp_path / "temp.tif")
 | 
					        filename = tmp_path / "temp.tif"
 | 
				
			||||||
        im = hopper(mode)
 | 
					        im = hopper(mode)
 | 
				
			||||||
        im.save(filename, tiffinfo={262: 0})
 | 
					        im.save(filename, tiffinfo={262: 0})
 | 
				
			||||||
        with Image.open(filename) as reloaded:
 | 
					        with Image.open(filename) as reloaded:
 | 
				
			||||||
 | 
					            assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
            assert reloaded.tag_v2[262] == 0
 | 
					            assert reloaded.tag_v2[262] == 0
 | 
				
			||||||
            assert_image_equal(im, reloaded)
 | 
					            assert_image_equal(im, reloaded)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -600,9 +643,11 @@ class TestFileTiff:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_with_underscores(self, tmp_path: Path) -> None:
 | 
					    def test_with_underscores(self, tmp_path: Path) -> None:
 | 
				
			||||||
        kwargs = {"resolution_unit": "inch", "x_resolution": 72, "y_resolution": 36}
 | 
					        kwargs = {"resolution_unit": "inch", "x_resolution": 72, "y_resolution": 36}
 | 
				
			||||||
        filename = str(tmp_path / "temp.tif")
 | 
					        filename = tmp_path / "temp.tif"
 | 
				
			||||||
        hopper("RGB").save(filename, "TIFF", **kwargs)
 | 
					        hopper("RGB").save(filename, "TIFF", **kwargs)
 | 
				
			||||||
        with Image.open(filename) as im:
 | 
					        with Image.open(filename) as im:
 | 
				
			||||||
 | 
					            assert isinstance(im, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # 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
 | 
				
			||||||
| 
						 | 
					@ -618,14 +663,14 @@ class TestFileTiff:
 | 
				
			||||||
        with Image.open(infile) as im:
 | 
					        with Image.open(infile) as im:
 | 
				
			||||||
            assert im.getpixel((0, 0)) == pixel_value
 | 
					            assert im.getpixel((0, 0)) == pixel_value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            tmpfile = str(tmp_path / "temp.tif")
 | 
					            tmpfile = tmp_path / "temp.tif"
 | 
				
			||||||
            im.save(tmpfile)
 | 
					            im.save(tmpfile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            assert_image_equal_tofile(im, tmpfile)
 | 
					            assert_image_equal_tofile(im, tmpfile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_iptc(self, tmp_path: Path) -> None:
 | 
					    def test_iptc(self, tmp_path: Path) -> None:
 | 
				
			||||||
        # Do not preserve IPTC_NAA_CHUNK by default if type is LONG
 | 
					        # Do not preserve IPTC_NAA_CHUNK by default if type is LONG
 | 
				
			||||||
        outfile = str(tmp_path / "temp.tif")
 | 
					        outfile = tmp_path / "temp.tif"
 | 
				
			||||||
        with Image.open("Tests/images/hopper.tif") as im:
 | 
					        with Image.open("Tests/images/hopper.tif") as im:
 | 
				
			||||||
            im.load()
 | 
					            im.load()
 | 
				
			||||||
            assert isinstance(im, TiffImagePlugin.TiffImageFile)
 | 
					            assert isinstance(im, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
| 
						 | 
					@ -640,7 +685,7 @@ class TestFileTiff:
 | 
				
			||||||
            assert 33723 not in im.tag_v2
 | 
					            assert 33723 not in im.tag_v2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_rowsperstrip(self, tmp_path: Path) -> None:
 | 
					    def test_rowsperstrip(self, tmp_path: Path) -> None:
 | 
				
			||||||
        outfile = str(tmp_path / "temp.tif")
 | 
					        outfile = tmp_path / "temp.tif"
 | 
				
			||||||
        im = hopper()
 | 
					        im = hopper()
 | 
				
			||||||
        im.save(outfile, tiffinfo={278: 256})
 | 
					        im.save(outfile, tiffinfo={278: 256})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -648,6 +693,18 @@ class TestFileTiff:
 | 
				
			||||||
            assert isinstance(im, TiffImagePlugin.TiffImageFile)
 | 
					            assert isinstance(im, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
            assert im.tag_v2[278] == 256
 | 
					            assert im.tag_v2[278] == 256
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        im = hopper()
 | 
				
			||||||
 | 
					        im2 = Image.new("L", (128, 128))
 | 
				
			||||||
 | 
					        im2.encoderinfo = {"tiffinfo": {278: 256}}
 | 
				
			||||||
 | 
					        im.save(outfile, save_all=True, append_images=[im2])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        with Image.open(outfile) as im:
 | 
				
			||||||
 | 
					            assert isinstance(im, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
 | 
					            assert im.tag_v2[278] == 128
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            im.seek(1)
 | 
				
			||||||
 | 
					            assert im.tag_v2[278] == 256
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_strip_raw(self) -> None:
 | 
					    def test_strip_raw(self) -> None:
 | 
				
			||||||
        infile = "Tests/images/tiff_strip_raw.tif"
 | 
					        infile = "Tests/images/tiff_strip_raw.tif"
 | 
				
			||||||
        with Image.open(infile) as im:
 | 
					        with Image.open(infile) as im:
 | 
				
			||||||
| 
						 | 
					@ -677,9 +734,10 @@ class TestFileTiff:
 | 
				
			||||||
    def test_planar_configuration_save(self, tmp_path: Path) -> None:
 | 
					    def test_planar_configuration_save(self, tmp_path: Path) -> None:
 | 
				
			||||||
        infile = "Tests/images/tiff_tiled_planar_raw.tif"
 | 
					        infile = "Tests/images/tiff_tiled_planar_raw.tif"
 | 
				
			||||||
        with Image.open(infile) as im:
 | 
					        with Image.open(infile) as im:
 | 
				
			||||||
 | 
					            assert isinstance(im, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
            assert im._planar_configuration == 2
 | 
					            assert im._planar_configuration == 2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            outfile = str(tmp_path / "temp.tif")
 | 
					            outfile = tmp_path / "temp.tif"
 | 
				
			||||||
            im.save(outfile)
 | 
					            im.save(outfile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            with Image.open(outfile) as reloaded:
 | 
					            with Image.open(outfile) as reloaded:
 | 
				
			||||||
| 
						 | 
					@ -694,7 +752,7 @@ class TestFileTiff:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @pytest.mark.parametrize("mode", ("P", "PA"))
 | 
					    @pytest.mark.parametrize("mode", ("P", "PA"))
 | 
				
			||||||
    def test_palette(self, mode: str, tmp_path: Path) -> None:
 | 
					    def test_palette(self, mode: str, tmp_path: Path) -> None:
 | 
				
			||||||
        outfile = str(tmp_path / "temp.tif")
 | 
					        outfile = tmp_path / "temp.tif"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        im = hopper(mode)
 | 
					        im = hopper(mode)
 | 
				
			||||||
        im.save(outfile)
 | 
					        im.save(outfile)
 | 
				
			||||||
| 
						 | 
					@ -709,6 +767,7 @@ class TestFileTiff:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        mp.seek(0, os.SEEK_SET)
 | 
					        mp.seek(0, os.SEEK_SET)
 | 
				
			||||||
        with Image.open(mp) as im:
 | 
					        with Image.open(mp) as im:
 | 
				
			||||||
 | 
					            assert isinstance(im, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
            assert im.n_frames == 3
 | 
					            assert im.n_frames == 3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Test appending images
 | 
					        # Test appending images
 | 
				
			||||||
| 
						 | 
					@ -719,6 +778,7 @@ class TestFileTiff:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        mp.seek(0, os.SEEK_SET)
 | 
					        mp.seek(0, os.SEEK_SET)
 | 
				
			||||||
        with Image.open(mp) as reread:
 | 
					        with Image.open(mp) as reread:
 | 
				
			||||||
 | 
					            assert isinstance(reread, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
            assert reread.n_frames == 3
 | 
					            assert reread.n_frames == 3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Test appending using a generator
 | 
					        # Test appending using a generator
 | 
				
			||||||
| 
						 | 
					@ -730,6 +790,7 @@ class TestFileTiff:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        mp.seek(0, os.SEEK_SET)
 | 
					        mp.seek(0, os.SEEK_SET)
 | 
				
			||||||
        with Image.open(mp) as reread:
 | 
					        with Image.open(mp) as reread:
 | 
				
			||||||
 | 
					            assert isinstance(reread, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
            assert reread.n_frames == 3
 | 
					            assert reread.n_frames == 3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_fixoffsets(self) -> None:
 | 
					    def test_fixoffsets(self) -> None:
 | 
				
			||||||
| 
						 | 
					@ -746,6 +807,39 @@ class TestFileTiff:
 | 
				
			||||||
            with pytest.raises(RuntimeError):
 | 
					            with pytest.raises(RuntimeError):
 | 
				
			||||||
                a.fixOffsets(1)
 | 
					                a.fixOffsets(1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        b = BytesIO(b"II\x2a\x00\x00\x00\x00\x00")
 | 
				
			||||||
 | 
					        with TiffImagePlugin.AppendingTiffWriter(b) as a:
 | 
				
			||||||
 | 
					            a.offsetOfNewPage = 2**16
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            b.seek(0)
 | 
				
			||||||
 | 
					            a.fixOffsets(1, isShort=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        b = BytesIO(b"II\x2b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
 | 
				
			||||||
 | 
					        with TiffImagePlugin.AppendingTiffWriter(b) as a:
 | 
				
			||||||
 | 
					            a.offsetOfNewPage = 2**32
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            b.seek(0)
 | 
				
			||||||
 | 
					            a.fixOffsets(1, isShort=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            b.seek(0)
 | 
				
			||||||
 | 
					            a.fixOffsets(1, isLong=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_appending_tiff_writer_writelong(self) -> None:
 | 
				
			||||||
 | 
					        data = b"II\x2a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
 | 
				
			||||||
 | 
					        b = BytesIO(data)
 | 
				
			||||||
 | 
					        with TiffImagePlugin.AppendingTiffWriter(b) as a:
 | 
				
			||||||
 | 
					            a.seek(-4, os.SEEK_CUR)
 | 
				
			||||||
 | 
					            a.writeLong(2**32 - 1)
 | 
				
			||||||
 | 
					            assert b.getvalue() == data[:-4] + b"\xff\xff\xff\xff"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_appending_tiff_writer_rewritelastshorttolong(self) -> None:
 | 
				
			||||||
 | 
					        data = b"II\x2a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
 | 
				
			||||||
 | 
					        b = BytesIO(data)
 | 
				
			||||||
 | 
					        with TiffImagePlugin.AppendingTiffWriter(b) as a:
 | 
				
			||||||
 | 
					            a.seek(-2, os.SEEK_CUR)
 | 
				
			||||||
 | 
					            a.rewriteLastShortToLong(2**32 - 1)
 | 
				
			||||||
 | 
					            assert b.getvalue() == data[:-4] + b"\xff\xff\xff\xff"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_saving_icc_profile(self, tmp_path: Path) -> None:
 | 
					    def test_saving_icc_profile(self, tmp_path: Path) -> None:
 | 
				
			||||||
        # Tests saving TIFF with icc_profile set.
 | 
					        # Tests saving TIFF with icc_profile set.
 | 
				
			||||||
        # At the time of writing this will only work for non-compressed tiffs
 | 
					        # At the time of writing this will only work for non-compressed tiffs
 | 
				
			||||||
| 
						 | 
					@ -755,7 +849,7 @@ class TestFileTiff:
 | 
				
			||||||
        im.info["icc_profile"] = "Dummy value"
 | 
					        im.info["icc_profile"] = "Dummy value"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Try save-load round trip to make sure both handle icc_profile.
 | 
					        # Try save-load round trip to make sure both handle icc_profile.
 | 
				
			||||||
        tmpfile = str(tmp_path / "temp.tif")
 | 
					        tmpfile = tmp_path / "temp.tif"
 | 
				
			||||||
        im.save(tmpfile, "TIFF", compression="raw")
 | 
					        im.save(tmpfile, "TIFF", compression="raw")
 | 
				
			||||||
        with Image.open(tmpfile) as reloaded:
 | 
					        with Image.open(tmpfile) as reloaded:
 | 
				
			||||||
            assert b"Dummy value" == reloaded.info["icc_profile"]
 | 
					            assert b"Dummy value" == reloaded.info["icc_profile"]
 | 
				
			||||||
| 
						 | 
					@ -764,7 +858,7 @@ class TestFileTiff:
 | 
				
			||||||
        im = hopper()
 | 
					        im = hopper()
 | 
				
			||||||
        assert "icc_profile" not in im.info
 | 
					        assert "icc_profile" not in im.info
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        outfile = str(tmp_path / "temp.tif")
 | 
					        outfile = tmp_path / "temp.tif"
 | 
				
			||||||
        icc_profile = b"Dummy value"
 | 
					        icc_profile = b"Dummy value"
 | 
				
			||||||
        im.save(outfile, icc_profile=icc_profile)
 | 
					        im.save(outfile, icc_profile=icc_profile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -775,11 +869,11 @@ class TestFileTiff:
 | 
				
			||||||
        with Image.open("Tests/images/hopper.bmp") as im:
 | 
					        with Image.open("Tests/images/hopper.bmp") as im:
 | 
				
			||||||
            assert im.info["compression"] == 0
 | 
					            assert im.info["compression"] == 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            outfile = str(tmp_path / "temp.tif")
 | 
					            outfile = tmp_path / "temp.tif"
 | 
				
			||||||
            im.save(outfile)
 | 
					            im.save(outfile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_discard_icc_profile(self, tmp_path: Path) -> None:
 | 
					    def test_discard_icc_profile(self, tmp_path: Path) -> None:
 | 
				
			||||||
        outfile = str(tmp_path / "temp.tif")
 | 
					        outfile = tmp_path / "temp.tif"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        with Image.open("Tests/images/icc_profile.png") as im:
 | 
					        with Image.open("Tests/images/icc_profile.png") as im:
 | 
				
			||||||
            assert "icc_profile" in im.info
 | 
					            assert "icc_profile" in im.info
 | 
				
			||||||
| 
						 | 
					@ -807,6 +901,7 @@ class TestFileTiff:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_get_photoshop_blocks(self) -> None:
 | 
					    def test_get_photoshop_blocks(self) -> None:
 | 
				
			||||||
        with Image.open("Tests/images/lab.tif") as im:
 | 
					        with Image.open("Tests/images/lab.tif") as im:
 | 
				
			||||||
 | 
					            assert isinstance(im, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
            assert list(im.get_photoshop_blocks().keys()) == [
 | 
					            assert list(im.get_photoshop_blocks().keys()) == [
 | 
				
			||||||
                1061,
 | 
					                1061,
 | 
				
			||||||
                1002,
 | 
					                1002,
 | 
				
			||||||
| 
						 | 
					@ -832,7 +927,7 @@ class TestFileTiff:
 | 
				
			||||||
            ]
 | 
					            ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_tiff_chunks(self, tmp_path: Path) -> None:
 | 
					    def test_tiff_chunks(self, tmp_path: Path) -> None:
 | 
				
			||||||
        tmpfile = str(tmp_path / "temp.tif")
 | 
					        tmpfile = tmp_path / "temp.tif"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        im = hopper()
 | 
					        im = hopper()
 | 
				
			||||||
        with open(tmpfile, "wb") as fp:
 | 
					        with open(tmpfile, "wb") as fp:
 | 
				
			||||||
| 
						 | 
					@ -854,7 +949,7 @@ class TestFileTiff:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_close_on_load_exclusive(self, tmp_path: Path) -> None:
 | 
					    def test_close_on_load_exclusive(self, tmp_path: Path) -> None:
 | 
				
			||||||
        # similar to test_fd_leak, but runs on unixlike os
 | 
					        # similar to test_fd_leak, but runs on unixlike os
 | 
				
			||||||
        tmpfile = str(tmp_path / "temp.tif")
 | 
					        tmpfile = tmp_path / "temp.tif"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        with Image.open("Tests/images/uint16_1_4660.tif") as im:
 | 
					        with Image.open("Tests/images/uint16_1_4660.tif") as im:
 | 
				
			||||||
            im.save(tmpfile)
 | 
					            im.save(tmpfile)
 | 
				
			||||||
| 
						 | 
					@ -866,7 +961,7 @@ class TestFileTiff:
 | 
				
			||||||
        assert fp.closed
 | 
					        assert fp.closed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_close_on_load_nonexclusive(self, tmp_path: Path) -> None:
 | 
					    def test_close_on_load_nonexclusive(self, tmp_path: Path) -> None:
 | 
				
			||||||
        tmpfile = str(tmp_path / "temp.tif")
 | 
					        tmpfile = tmp_path / "temp.tif"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        with Image.open("Tests/images/uint16_1_4660.tif") as im:
 | 
					        with Image.open("Tests/images/uint16_1_4660.tif") as im:
 | 
				
			||||||
            im.save(tmpfile)
 | 
					            im.save(tmpfile)
 | 
				
			||||||
| 
						 | 
					@ -895,11 +990,10 @@ class TestFileTiff:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @pytest.mark.timeout(6)
 | 
					    @pytest.mark.timeout(6)
 | 
				
			||||||
    @pytest.mark.filterwarnings("ignore:Truncated File Read")
 | 
					    @pytest.mark.filterwarnings("ignore:Truncated File Read")
 | 
				
			||||||
    def test_timeout(self) -> None:
 | 
					    def test_timeout(self, monkeypatch: pytest.MonkeyPatch) -> None:
 | 
				
			||||||
        with Image.open("Tests/images/timeout-6646305047838720") as im:
 | 
					        with Image.open("Tests/images/timeout-6646305047838720") as im:
 | 
				
			||||||
            ImageFile.LOAD_TRUNCATED_IMAGES = True
 | 
					            monkeypatch.setattr(ImageFile, "LOAD_TRUNCATED_IMAGES", True)
 | 
				
			||||||
            im.load()
 | 
					            im.load()
 | 
				
			||||||
            ImageFile.LOAD_TRUNCATED_IMAGES = False
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @pytest.mark.parametrize(
 | 
					    @pytest.mark.parametrize(
 | 
				
			||||||
        "test_file",
 | 
					        "test_file",
 | 
				
			||||||
| 
						 | 
					@ -918,7 +1012,7 @@ class TestFileTiff:
 | 
				
			||||||
@pytest.mark.skipif(not is_win32(), reason="Windows only")
 | 
					@pytest.mark.skipif(not is_win32(), reason="Windows only")
 | 
				
			||||||
class TestFileTiffW32:
 | 
					class TestFileTiffW32:
 | 
				
			||||||
    def test_fd_leak(self, tmp_path: Path) -> None:
 | 
					    def test_fd_leak(self, tmp_path: Path) -> None:
 | 
				
			||||||
        tmpfile = str(tmp_path / "temp.tif")
 | 
					        tmpfile = tmp_path / "temp.tif"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # this is an mmaped file.
 | 
					        # this is an mmaped file.
 | 
				
			||||||
        with Image.open("Tests/images/uint16_1_4660.tif") as im:
 | 
					        with Image.open("Tests/images/uint16_1_4660.tif") as im:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -56,11 +56,12 @@ def test_rt_metadata(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    info[ImageDescription] = text_data
 | 
					    info[ImageDescription] = text_data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    f = str(tmp_path / "temp.tif")
 | 
					    f = tmp_path / "temp.tif"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    img.save(f, tiffinfo=info)
 | 
					    img.save(f, tiffinfo=info)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open(f) as loaded:
 | 
					    with Image.open(f) as loaded:
 | 
				
			||||||
 | 
					        assert isinstance(loaded, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
        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),)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -80,12 +81,14 @@ def test_rt_metadata(tmp_path: Path) -> None:
 | 
				
			||||||
    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 isinstance(loaded, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
        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() -> None:
 | 
					def test_read_metadata() -> None:
 | 
				
			||||||
    with Image.open("Tests/images/hopper_g4.tif") as img:
 | 
					    with Image.open("Tests/images/hopper_g4.tif") as img:
 | 
				
			||||||
 | 
					        assert isinstance(img, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
        assert {
 | 
					        assert {
 | 
				
			||||||
            "YResolution": IFDRational(4294967295, 113653537),
 | 
					            "YResolution": IFDRational(4294967295, 113653537),
 | 
				
			||||||
            "PlanarConfiguration": 1,
 | 
					            "PlanarConfiguration": 1,
 | 
				
			||||||
| 
						 | 
					@ -128,13 +131,15 @@ def test_read_metadata() -> None:
 | 
				
			||||||
def test_write_metadata(tmp_path: Path) -> None:
 | 
					def test_write_metadata(tmp_path: Path) -> None:
 | 
				
			||||||
    """Test metadata writing through the python code"""
 | 
					    """Test metadata writing through the python code"""
 | 
				
			||||||
    with Image.open("Tests/images/hopper.tif") as img:
 | 
					    with Image.open("Tests/images/hopper.tif") as img:
 | 
				
			||||||
        f = str(tmp_path / "temp.tiff")
 | 
					        assert isinstance(img, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
 | 
					        f = tmp_path / "temp.tiff"
 | 
				
			||||||
        del img.tag[278]
 | 
					        del img.tag[278]
 | 
				
			||||||
        img.save(f, tiffinfo=img.tag)
 | 
					        img.save(f, tiffinfo=img.tag)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        original = img.tag_v2.named()
 | 
					        original = img.tag_v2.named()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open(f) as loaded:
 | 
					    with Image.open(f) as loaded:
 | 
				
			||||||
 | 
					        assert isinstance(loaded, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
        reloaded = loaded.tag_v2.named()
 | 
					        reloaded = loaded.tag_v2.named()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ignored = ["StripByteCounts", "RowsPerStrip", "PageNumber", "StripOffsets"]
 | 
					    ignored = ["StripByteCounts", "RowsPerStrip", "PageNumber", "StripOffsets"]
 | 
				
			||||||
| 
						 | 
					@ -163,8 +168,9 @@ def test_write_metadata(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_change_stripbytecounts_tag_type(tmp_path: Path) -> None:
 | 
					def test_change_stripbytecounts_tag_type(tmp_path: Path) -> None:
 | 
				
			||||||
    out = str(tmp_path / "temp.tiff")
 | 
					    out = tmp_path / "temp.tiff"
 | 
				
			||||||
    with Image.open("Tests/images/hopper.tif") as im:
 | 
					    with Image.open("Tests/images/hopper.tif") as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
        info = im.tag_v2
 | 
					        info = im.tag_v2
 | 
				
			||||||
        del info[278]
 | 
					        del info[278]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -178,6 +184,7 @@ def test_change_stripbytecounts_tag_type(tmp_path: Path) -> None:
 | 
				
			||||||
        im.save(out, tiffinfo=info)
 | 
					        im.save(out, tiffinfo=info)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open(out) as reloaded:
 | 
					    with Image.open(out) as reloaded:
 | 
				
			||||||
 | 
					        assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
        assert reloaded.tag_v2.tagtype[TiffImagePlugin.STRIPBYTECOUNTS] == TiffTags.LONG
 | 
					        assert reloaded.tag_v2.tagtype[TiffImagePlugin.STRIPBYTECOUNTS] == TiffTags.LONG
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -210,7 +217,7 @@ def test_no_duplicate_50741_tag() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_iptc(tmp_path: Path) -> None:
 | 
					def test_iptc(tmp_path: Path) -> None:
 | 
				
			||||||
    out = str(tmp_path / "temp.tiff")
 | 
					    out = tmp_path / "temp.tiff"
 | 
				
			||||||
    with Image.open("Tests/images/hopper.Lab.tif") as im:
 | 
					    with Image.open("Tests/images/hopper.Lab.tif") as im:
 | 
				
			||||||
        im.save(out)
 | 
					        im.save(out)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -227,10 +234,11 @@ def test_writing_other_types_to_ascii(
 | 
				
			||||||
    info[271] = value
 | 
					    info[271] = value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    im = hopper()
 | 
					    im = hopper()
 | 
				
			||||||
    out = str(tmp_path / "temp.tiff")
 | 
					    out = tmp_path / "temp.tiff"
 | 
				
			||||||
    im.save(out, tiffinfo=info)
 | 
					    im.save(out, tiffinfo=info)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open(out) as reloaded:
 | 
					    with Image.open(out) as reloaded:
 | 
				
			||||||
 | 
					        assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
        assert reloaded.tag_v2[271] == expected
 | 
					        assert reloaded.tag_v2[271] == expected
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -244,10 +252,11 @@ def test_writing_other_types_to_bytes(value: int | IFDRational, tmp_path: Path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    info[700] = value
 | 
					    info[700] = value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    out = str(tmp_path / "temp.tiff")
 | 
					    out = tmp_path / "temp.tiff"
 | 
				
			||||||
    im.save(out, tiffinfo=info)
 | 
					    im.save(out, tiffinfo=info)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open(out) as reloaded:
 | 
					    with Image.open(out) as reloaded:
 | 
				
			||||||
 | 
					        assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
        assert reloaded.tag_v2[700] == b"\x01"
 | 
					        assert reloaded.tag_v2[700] == b"\x01"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -263,10 +272,11 @@ def test_writing_other_types_to_undefined(
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    info[33723] = value
 | 
					    info[33723] = value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    out = str(tmp_path / "temp.tiff")
 | 
					    out = tmp_path / "temp.tiff"
 | 
				
			||||||
    im.save(out, tiffinfo=info)
 | 
					    im.save(out, tiffinfo=info)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open(out) as reloaded:
 | 
					    with Image.open(out) as reloaded:
 | 
				
			||||||
 | 
					        assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
        assert reloaded.tag_v2[33723] == b"1"
 | 
					        assert reloaded.tag_v2[33723] == b"1"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -296,7 +306,7 @@ def test_empty_metadata() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_iccprofile(tmp_path: Path) -> None:
 | 
					def test_iccprofile(tmp_path: Path) -> None:
 | 
				
			||||||
    # https://github.com/python-pillow/Pillow/issues/1462
 | 
					    # https://github.com/python-pillow/Pillow/issues/1462
 | 
				
			||||||
    out = str(tmp_path / "temp.tiff")
 | 
					    out = tmp_path / "temp.tiff"
 | 
				
			||||||
    with Image.open("Tests/images/hopper.iccprofile.tif") as im:
 | 
					    with Image.open("Tests/images/hopper.iccprofile.tif") as im:
 | 
				
			||||||
        im.save(out)
 | 
					        im.save(out)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -311,19 +321,20 @@ def test_iccprofile_binary() -> None:
 | 
				
			||||||
    # but probably won't be able to save it.
 | 
					    # but probably won't be able to save it.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open("Tests/images/hopper.iccprofile_binary.tif") as im:
 | 
					    with Image.open("Tests/images/hopper.iccprofile_binary.tif") as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
        assert im.tag_v2.tagtype[34675] == 1
 | 
					        assert im.tag_v2.tagtype[34675] == 1
 | 
				
			||||||
        assert im.info["icc_profile"]
 | 
					        assert im.info["icc_profile"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_iccprofile_save_png(tmp_path: Path) -> None:
 | 
					def test_iccprofile_save_png(tmp_path: Path) -> None:
 | 
				
			||||||
    with Image.open("Tests/images/hopper.iccprofile.tif") as im:
 | 
					    with Image.open("Tests/images/hopper.iccprofile.tif") as im:
 | 
				
			||||||
        outfile = str(tmp_path / "temp.png")
 | 
					        outfile = tmp_path / "temp.png"
 | 
				
			||||||
        im.save(outfile)
 | 
					        im.save(outfile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_iccprofile_binary_save_png(tmp_path: Path) -> None:
 | 
					def test_iccprofile_binary_save_png(tmp_path: Path) -> None:
 | 
				
			||||||
    with Image.open("Tests/images/hopper.iccprofile_binary.tif") as im:
 | 
					    with Image.open("Tests/images/hopper.iccprofile_binary.tif") as im:
 | 
				
			||||||
        outfile = str(tmp_path / "temp.png")
 | 
					        outfile = tmp_path / "temp.png"
 | 
				
			||||||
        im.save(outfile)
 | 
					        im.save(outfile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -332,10 +343,11 @@ def test_exif_div_zero(tmp_path: Path) -> None:
 | 
				
			||||||
    info = TiffImagePlugin.ImageFileDirectory_v2()
 | 
					    info = TiffImagePlugin.ImageFileDirectory_v2()
 | 
				
			||||||
    info[41988] = TiffImagePlugin.IFDRational(0, 0)
 | 
					    info[41988] = TiffImagePlugin.IFDRational(0, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    out = str(tmp_path / "temp.tiff")
 | 
					    out = tmp_path / "temp.tiff"
 | 
				
			||||||
    im.save(out, tiffinfo=info, compression="raw")
 | 
					    im.save(out, tiffinfo=info, compression="raw")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open(out) as reloaded:
 | 
					    with Image.open(out) as reloaded:
 | 
				
			||||||
 | 
					        assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
        assert 0 == reloaded.tag_v2[41988].numerator
 | 
					        assert 0 == reloaded.tag_v2[41988].numerator
 | 
				
			||||||
        assert 0 == reloaded.tag_v2[41988].denominator
 | 
					        assert 0 == reloaded.tag_v2[41988].denominator
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -351,10 +363,11 @@ def test_ifd_unsigned_rational(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    info[41493] = TiffImagePlugin.IFDRational(numerator, 1)
 | 
					    info[41493] = TiffImagePlugin.IFDRational(numerator, 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    out = str(tmp_path / "temp.tiff")
 | 
					    out = tmp_path / "temp.tiff"
 | 
				
			||||||
    im.save(out, tiffinfo=info, compression="raw")
 | 
					    im.save(out, tiffinfo=info, compression="raw")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open(out) as reloaded:
 | 
					    with Image.open(out) as reloaded:
 | 
				
			||||||
 | 
					        assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
        assert max_long == reloaded.tag_v2[41493].numerator
 | 
					        assert max_long == reloaded.tag_v2[41493].numerator
 | 
				
			||||||
        assert 1 == reloaded.tag_v2[41493].denominator
 | 
					        assert 1 == reloaded.tag_v2[41493].denominator
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -363,10 +376,11 @@ def test_ifd_unsigned_rational(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    info[41493] = TiffImagePlugin.IFDRational(numerator, 1)
 | 
					    info[41493] = TiffImagePlugin.IFDRational(numerator, 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    out = str(tmp_path / "temp.tiff")
 | 
					    out = tmp_path / "temp.tiff"
 | 
				
			||||||
    im.save(out, tiffinfo=info, compression="raw")
 | 
					    im.save(out, tiffinfo=info, compression="raw")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open(out) as reloaded:
 | 
					    with Image.open(out) as reloaded:
 | 
				
			||||||
 | 
					        assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
        assert max_long == reloaded.tag_v2[41493].numerator
 | 
					        assert max_long == reloaded.tag_v2[41493].numerator
 | 
				
			||||||
        assert 1 == reloaded.tag_v2[41493].denominator
 | 
					        assert 1 == reloaded.tag_v2[41493].denominator
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -381,10 +395,11 @@ def test_ifd_signed_rational(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    info[37380] = TiffImagePlugin.IFDRational(numerator, denominator)
 | 
					    info[37380] = TiffImagePlugin.IFDRational(numerator, denominator)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    out = str(tmp_path / "temp.tiff")
 | 
					    out = tmp_path / "temp.tiff"
 | 
				
			||||||
    im.save(out, tiffinfo=info, compression="raw")
 | 
					    im.save(out, tiffinfo=info, compression="raw")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open(out) as reloaded:
 | 
					    with Image.open(out) as reloaded:
 | 
				
			||||||
 | 
					        assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
        assert numerator == reloaded.tag_v2[37380].numerator
 | 
					        assert numerator == reloaded.tag_v2[37380].numerator
 | 
				
			||||||
        assert denominator == reloaded.tag_v2[37380].denominator
 | 
					        assert denominator == reloaded.tag_v2[37380].denominator
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -393,10 +408,11 @@ def test_ifd_signed_rational(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    info[37380] = TiffImagePlugin.IFDRational(numerator, denominator)
 | 
					    info[37380] = TiffImagePlugin.IFDRational(numerator, denominator)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    out = str(tmp_path / "temp.tiff")
 | 
					    out = tmp_path / "temp.tiff"
 | 
				
			||||||
    im.save(out, tiffinfo=info, compression="raw")
 | 
					    im.save(out, tiffinfo=info, compression="raw")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open(out) as reloaded:
 | 
					    with Image.open(out) as reloaded:
 | 
				
			||||||
 | 
					        assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
        assert numerator == reloaded.tag_v2[37380].numerator
 | 
					        assert numerator == reloaded.tag_v2[37380].numerator
 | 
				
			||||||
        assert denominator == reloaded.tag_v2[37380].denominator
 | 
					        assert denominator == reloaded.tag_v2[37380].denominator
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -406,10 +422,11 @@ def test_ifd_signed_rational(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    info[37380] = TiffImagePlugin.IFDRational(numerator, denominator)
 | 
					    info[37380] = TiffImagePlugin.IFDRational(numerator, denominator)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    out = str(tmp_path / "temp.tiff")
 | 
					    out = tmp_path / "temp.tiff"
 | 
				
			||||||
    im.save(out, tiffinfo=info, compression="raw")
 | 
					    im.save(out, tiffinfo=info, compression="raw")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open(out) as reloaded:
 | 
					    with Image.open(out) as reloaded:
 | 
				
			||||||
 | 
					        assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
        assert 2**31 - 1 == reloaded.tag_v2[37380].numerator
 | 
					        assert 2**31 - 1 == reloaded.tag_v2[37380].numerator
 | 
				
			||||||
        assert -1 == reloaded.tag_v2[37380].denominator
 | 
					        assert -1 == reloaded.tag_v2[37380].denominator
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -420,10 +437,11 @@ def test_ifd_signed_long(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    info[37000] = -60000
 | 
					    info[37000] = -60000
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    out = str(tmp_path / "temp.tiff")
 | 
					    out = tmp_path / "temp.tiff"
 | 
				
			||||||
    im.save(out, tiffinfo=info, compression="raw")
 | 
					    im.save(out, tiffinfo=info, compression="raw")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open(out) as reloaded:
 | 
					    with Image.open(out) as reloaded:
 | 
				
			||||||
 | 
					        assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
        assert reloaded.tag_v2[37000] == -60000
 | 
					        assert reloaded.tag_v2[37000] == -60000
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -444,11 +462,13 @@ def test_empty_values() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_photoshop_info(tmp_path: Path) -> None:
 | 
					def test_photoshop_info(tmp_path: Path) -> None:
 | 
				
			||||||
    with Image.open("Tests/images/issue_2278.tif") as im:
 | 
					    with Image.open("Tests/images/issue_2278.tif") as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
        assert len(im.tag_v2[34377]) == 70
 | 
					        assert len(im.tag_v2[34377]) == 70
 | 
				
			||||||
        assert isinstance(im.tag_v2[34377], bytes)
 | 
					        assert isinstance(im.tag_v2[34377], bytes)
 | 
				
			||||||
        out = str(tmp_path / "temp.tiff")
 | 
					        out = tmp_path / "temp.tiff"
 | 
				
			||||||
        im.save(out)
 | 
					        im.save(out)
 | 
				
			||||||
    with Image.open(out) as reloaded:
 | 
					    with Image.open(out) as reloaded:
 | 
				
			||||||
 | 
					        assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
 | 
				
			||||||
        assert len(reloaded.tag_v2[34377]) == 70
 | 
					        assert len(reloaded.tag_v2[34377]) == 70
 | 
				
			||||||
        assert isinstance(reloaded.tag_v2[34377], bytes)
 | 
					        assert isinstance(reloaded.tag_v2[34377], bytes)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -480,7 +500,7 @@ def test_tag_group_data() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_empty_subifd(tmp_path: Path) -> None:
 | 
					def test_empty_subifd(tmp_path: Path) -> None:
 | 
				
			||||||
    out = str(tmp_path / "temp.jpg")
 | 
					    out = tmp_path / "temp.jpg"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    im = hopper()
 | 
					    im = hopper()
 | 
				
			||||||
    exif = im.getexif()
 | 
					    exif = im.getexif()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -21,7 +21,11 @@ def test_open() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_load() -> None:
 | 
					def test_load() -> None:
 | 
				
			||||||
    with WalImageFile.open(TEST_FILE) as im:
 | 
					    with WalImageFile.open(TEST_FILE) as im:
 | 
				
			||||||
        assert im.load()[0, 0] == 122
 | 
					        px = im.load()
 | 
				
			||||||
 | 
					        assert px is not None
 | 
				
			||||||
 | 
					        assert px[0, 0] == 122
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Test again now that it has already been loaded once
 | 
					        # Test again now that it has already been loaded once
 | 
				
			||||||
        assert im.load()[0, 0] == 122
 | 
					        px = im.load()
 | 
				
			||||||
 | 
					        assert px is not None
 | 
				
			||||||
 | 
					        assert px[0, 0] == 122
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -28,9 +28,9 @@ except ImportError:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TestUnsupportedWebp:
 | 
					class TestUnsupportedWebp:
 | 
				
			||||||
    def test_unsupported(self) -> None:
 | 
					    def test_unsupported(self, monkeypatch: pytest.MonkeyPatch) -> None:
 | 
				
			||||||
        if HAVE_WEBP:
 | 
					        if HAVE_WEBP:
 | 
				
			||||||
            WebPImagePlugin.SUPPORTED = False
 | 
					            monkeypatch.setattr(WebPImagePlugin, "SUPPORTED", False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        file_path = "Tests/images/hopper.webp"
 | 
					        file_path = "Tests/images/hopper.webp"
 | 
				
			||||||
        with pytest.warns(UserWarning):
 | 
					        with pytest.warns(UserWarning):
 | 
				
			||||||
| 
						 | 
					@ -38,9 +38,6 @@ class TestUnsupportedWebp:
 | 
				
			||||||
                with Image.open(file_path):
 | 
					                with Image.open(file_path):
 | 
				
			||||||
                    pass
 | 
					                    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if HAVE_WEBP:
 | 
					 | 
				
			||||||
            WebPImagePlugin.SUPPORTED = True
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
@skip_unless_feature("webp")
 | 
					@skip_unless_feature("webp")
 | 
				
			||||||
class TestFileWebp:
 | 
					class TestFileWebp:
 | 
				
			||||||
| 
						 | 
					@ -157,9 +154,8 @@ class TestFileWebp:
 | 
				
			||||||
    @pytest.mark.skipif(sys.maxsize <= 2**32, reason="Requires 64-bit system")
 | 
					    @pytest.mark.skipif(sys.maxsize <= 2**32, reason="Requires 64-bit system")
 | 
				
			||||||
    def test_write_encoding_error_message(self, tmp_path: Path) -> None:
 | 
					    def test_write_encoding_error_message(self, tmp_path: Path) -> None:
 | 
				
			||||||
        im = Image.new("RGB", (15000, 15000))
 | 
					        im = Image.new("RGB", (15000, 15000))
 | 
				
			||||||
        with pytest.raises(ValueError) as e:
 | 
					        with pytest.raises(ValueError, match="encoding error 6"):
 | 
				
			||||||
            im.save(tmp_path / "temp.webp", method=0)
 | 
					            im.save(tmp_path / "temp.webp", method=0)
 | 
				
			||||||
        assert str(e.value) == "encoding error 6"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @pytest.mark.skipif(sys.maxsize <= 2**32, reason="Requires 64-bit system")
 | 
					    @pytest.mark.skipif(sys.maxsize <= 2**32, reason="Requires 64-bit system")
 | 
				
			||||||
    def test_write_encoding_error_bad_dimension(self, tmp_path: Path) -> None:
 | 
					    def test_write_encoding_error_bad_dimension(self, tmp_path: Path) -> None:
 | 
				
			||||||
| 
						 | 
					@ -234,7 +230,7 @@ class TestFileWebp:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        with Image.open(out_gif) as reread:
 | 
					        with Image.open(out_gif) as reread:
 | 
				
			||||||
            reread_value = reread.convert("RGB").getpixel((1, 1))
 | 
					            reread_value = reread.convert("RGB").getpixel((1, 1))
 | 
				
			||||||
        difference = sum(abs(original_value[i] - reread_value[i]) for i in range(0, 3))
 | 
					        difference = sum(abs(original_value[i] - reread_value[i]) for i in range(3))
 | 
				
			||||||
        assert difference < 5
 | 
					        assert difference < 5
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_duration(self, tmp_path: Path) -> None:
 | 
					    def test_duration(self, tmp_path: Path) -> None:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -42,7 +42,7 @@ def test_write_lossless_rgb(tmp_path: Path) -> None:
 | 
				
			||||||
    Does it have the bits we expect?
 | 
					    Does it have the bits we expect?
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    temp_file = str(tmp_path / "temp.webp")
 | 
					    temp_file = tmp_path / "temp.webp"
 | 
				
			||||||
    # temp_file = "temp.webp"
 | 
					    # temp_file = "temp.webp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pil_image = hopper("RGBA")
 | 
					    pil_image = hopper("RGBA")
 | 
				
			||||||
| 
						 | 
					@ -71,7 +71,7 @@ def test_write_rgba(tmp_path: Path) -> None:
 | 
				
			||||||
    Does it have the bits we expect?
 | 
					    Does it have the bits we expect?
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    temp_file = str(tmp_path / "temp.webp")
 | 
					    temp_file = tmp_path / "temp.webp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pil_image = Image.new("RGBA", (10, 10), (255, 0, 0, 20))
 | 
					    pil_image = Image.new("RGBA", (10, 10), (255, 0, 0, 20))
 | 
				
			||||||
    pil_image.save(temp_file)
 | 
					    pil_image.save(temp_file)
 | 
				
			||||||
| 
						 | 
					@ -104,7 +104,7 @@ def test_keep_rgb_values_when_transparent(tmp_path: Path) -> None:
 | 
				
			||||||
    half_transparent_image.putalpha(new_alpha)
 | 
					    half_transparent_image.putalpha(new_alpha)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # save with transparent area preserved
 | 
					    # save with transparent area preserved
 | 
				
			||||||
    temp_file = str(tmp_path / "temp.webp")
 | 
					    temp_file = tmp_path / "temp.webp"
 | 
				
			||||||
    half_transparent_image.save(temp_file, exact=True, lossless=True)
 | 
					    half_transparent_image.save(temp_file, exact=True, lossless=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open(temp_file) as reloaded:
 | 
					    with Image.open(temp_file) as reloaded:
 | 
				
			||||||
| 
						 | 
					@ -123,7 +123,7 @@ def test_write_unsupported_mode_PA(tmp_path: Path) -> None:
 | 
				
			||||||
    should work, and be similar to the original file.
 | 
					    should work, and be similar to the original file.
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    temp_file = str(tmp_path / "temp.webp")
 | 
					    temp_file = tmp_path / "temp.webp"
 | 
				
			||||||
    file_path = "Tests/images/transparent.gif"
 | 
					    file_path = "Tests/images/transparent.gif"
 | 
				
			||||||
    with Image.open(file_path) as im:
 | 
					    with Image.open(file_path) as im:
 | 
				
			||||||
        im.save(temp_file)
 | 
					        im.save(temp_file)
 | 
				
			||||||
| 
						 | 
					@ -142,10 +142,10 @@ def test_write_unsupported_mode_PA(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_alpha_quality(tmp_path: Path) -> None:
 | 
					def test_alpha_quality(tmp_path: Path) -> None:
 | 
				
			||||||
    with Image.open("Tests/images/transparent.png") as im:
 | 
					    with Image.open("Tests/images/transparent.png") as im:
 | 
				
			||||||
        out = str(tmp_path / "temp.webp")
 | 
					        out = tmp_path / "temp.webp"
 | 
				
			||||||
        im.save(out)
 | 
					        im.save(out)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        out_quality = str(tmp_path / "quality.webp")
 | 
					        out_quality = tmp_path / "quality.webp"
 | 
				
			||||||
        im.save(out_quality, alpha_quality=50)
 | 
					        im.save(out_quality, alpha_quality=50)
 | 
				
			||||||
        with Image.open(out) as reloaded:
 | 
					        with Image.open(out) as reloaded:
 | 
				
			||||||
            with Image.open(out_quality) as reloaded_quality:
 | 
					            with Image.open(out_quality) as reloaded_quality:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,7 +6,7 @@ from pathlib import Path
 | 
				
			||||||
import pytest
 | 
					import pytest
 | 
				
			||||||
from packaging.version import parse as parse_version
 | 
					from packaging.version import parse as parse_version
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from PIL import Image, features
 | 
					from PIL import GifImagePlugin, Image, WebPImagePlugin, features
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .helper import (
 | 
					from .helper import (
 | 
				
			||||||
    assert_image_equal,
 | 
					    assert_image_equal,
 | 
				
			||||||
| 
						 | 
					@ -22,10 +22,12 @@ def test_n_frames() -> None:
 | 
				
			||||||
    """Ensure that WebP format sets n_frames and is_animated attributes correctly."""
 | 
					    """Ensure that WebP format sets n_frames and is_animated attributes correctly."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open("Tests/images/hopper.webp") as im:
 | 
					    with Image.open("Tests/images/hopper.webp") as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, WebPImagePlugin.WebPImageFile)
 | 
				
			||||||
        assert im.n_frames == 1
 | 
					        assert im.n_frames == 1
 | 
				
			||||||
        assert not im.is_animated
 | 
					        assert not im.is_animated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open("Tests/images/iss634.webp") as im:
 | 
					    with Image.open("Tests/images/iss634.webp") as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, WebPImagePlugin.WebPImageFile)
 | 
				
			||||||
        assert im.n_frames == 42
 | 
					        assert im.n_frames == 42
 | 
				
			||||||
        assert im.is_animated
 | 
					        assert im.is_animated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -37,11 +39,13 @@ def test_write_animation_L(tmp_path: Path) -> None:
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open("Tests/images/iss634.gif") as orig:
 | 
					    with Image.open("Tests/images/iss634.gif") as orig:
 | 
				
			||||||
 | 
					        assert isinstance(orig, GifImagePlugin.GifImageFile)
 | 
				
			||||||
        assert orig.n_frames > 1
 | 
					        assert orig.n_frames > 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        temp_file = str(tmp_path / "temp.webp")
 | 
					        temp_file = tmp_path / "temp.webp"
 | 
				
			||||||
        orig.save(temp_file, save_all=True)
 | 
					        orig.save(temp_file, save_all=True)
 | 
				
			||||||
        with Image.open(temp_file) as im:
 | 
					        with Image.open(temp_file) as im:
 | 
				
			||||||
 | 
					            assert isinstance(im, WebPImagePlugin.WebPImageFile)
 | 
				
			||||||
            assert im.n_frames == orig.n_frames
 | 
					            assert im.n_frames == orig.n_frames
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # Compare first and last frames to the original animated GIF
 | 
					            # Compare first and last frames to the original animated GIF
 | 
				
			||||||
| 
						 | 
					@ -67,8 +71,9 @@ def test_write_animation_RGB(tmp_path: Path) -> None:
 | 
				
			||||||
    are visually similar to the originals.
 | 
					    are visually similar to the originals.
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def check(temp_file: str) -> None:
 | 
					    def check(temp_file: Path) -> None:
 | 
				
			||||||
        with Image.open(temp_file) as im:
 | 
					        with Image.open(temp_file) as im:
 | 
				
			||||||
 | 
					            assert isinstance(im, WebPImagePlugin.WebPImageFile)
 | 
				
			||||||
            assert im.n_frames == 2
 | 
					            assert im.n_frames == 2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # Compare first frame to original
 | 
					            # Compare first frame to original
 | 
				
			||||||
| 
						 | 
					@ -87,7 +92,7 @@ def test_write_animation_RGB(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open("Tests/images/anim_frame1.webp") as frame1:
 | 
					    with Image.open("Tests/images/anim_frame1.webp") as frame1:
 | 
				
			||||||
        with Image.open("Tests/images/anim_frame2.webp") as frame2:
 | 
					        with Image.open("Tests/images/anim_frame2.webp") as frame2:
 | 
				
			||||||
            temp_file1 = str(tmp_path / "temp.webp")
 | 
					            temp_file1 = tmp_path / "temp.webp"
 | 
				
			||||||
            frame1.copy().save(
 | 
					            frame1.copy().save(
 | 
				
			||||||
                temp_file1, save_all=True, append_images=[frame2], lossless=True
 | 
					                temp_file1, save_all=True, append_images=[frame2], lossless=True
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
| 
						 | 
					@ -99,7 +104,7 @@ def test_write_animation_RGB(tmp_path: Path) -> None:
 | 
				
			||||||
            ) -> Generator[Image.Image, None, None]:
 | 
					            ) -> Generator[Image.Image, None, None]:
 | 
				
			||||||
                yield from ims
 | 
					                yield from ims
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            temp_file2 = str(tmp_path / "temp_generator.webp")
 | 
					            temp_file2 = tmp_path / "temp_generator.webp"
 | 
				
			||||||
            frame1.copy().save(
 | 
					            frame1.copy().save(
 | 
				
			||||||
                temp_file2,
 | 
					                temp_file2,
 | 
				
			||||||
                save_all=True,
 | 
					                save_all=True,
 | 
				
			||||||
| 
						 | 
					@ -116,7 +121,7 @@ def test_timestamp_and_duration(tmp_path: Path) -> None:
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    durations = [0, 10, 20, 30, 40]
 | 
					    durations = [0, 10, 20, 30, 40]
 | 
				
			||||||
    temp_file = str(tmp_path / "temp.webp")
 | 
					    temp_file = tmp_path / "temp.webp"
 | 
				
			||||||
    with Image.open("Tests/images/anim_frame1.webp") as frame1:
 | 
					    with Image.open("Tests/images/anim_frame1.webp") as frame1:
 | 
				
			||||||
        with Image.open("Tests/images/anim_frame2.webp") as frame2:
 | 
					        with Image.open("Tests/images/anim_frame2.webp") as frame2:
 | 
				
			||||||
            frame1.save(
 | 
					            frame1.save(
 | 
				
			||||||
| 
						 | 
					@ -127,6 +132,7 @@ def test_timestamp_and_duration(tmp_path: Path) -> None:
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open(temp_file) as im:
 | 
					    with Image.open(temp_file) as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, WebPImagePlugin.WebPImageFile)
 | 
				
			||||||
        assert im.n_frames == 5
 | 
					        assert im.n_frames == 5
 | 
				
			||||||
        assert im.is_animated
 | 
					        assert im.is_animated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -141,7 +147,7 @@ def test_timestamp_and_duration(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_float_duration(tmp_path: Path) -> None:
 | 
					def test_float_duration(tmp_path: Path) -> None:
 | 
				
			||||||
    temp_file = str(tmp_path / "temp.webp")
 | 
					    temp_file = tmp_path / "temp.webp"
 | 
				
			||||||
    with Image.open("Tests/images/iss634.apng") as im:
 | 
					    with Image.open("Tests/images/iss634.apng") as im:
 | 
				
			||||||
        assert im.info["duration"] == 70.0
 | 
					        assert im.info["duration"] == 70.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -159,7 +165,7 @@ def test_seeking(tmp_path: Path) -> None:
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    dur = 33
 | 
					    dur = 33
 | 
				
			||||||
    temp_file = str(tmp_path / "temp.webp")
 | 
					    temp_file = tmp_path / "temp.webp"
 | 
				
			||||||
    with Image.open("Tests/images/anim_frame1.webp") as frame1:
 | 
					    with Image.open("Tests/images/anim_frame1.webp") as frame1:
 | 
				
			||||||
        with Image.open("Tests/images/anim_frame2.webp") as frame2:
 | 
					        with Image.open("Tests/images/anim_frame2.webp") as frame2:
 | 
				
			||||||
            frame1.save(
 | 
					            frame1.save(
 | 
				
			||||||
| 
						 | 
					@ -170,6 +176,7 @@ def test_seeking(tmp_path: Path) -> None:
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open(temp_file) as im:
 | 
					    with Image.open(temp_file) as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, WebPImagePlugin.WebPImageFile)
 | 
				
			||||||
        assert im.n_frames == 5
 | 
					        assert im.n_frames == 5
 | 
				
			||||||
        assert im.is_animated
 | 
					        assert im.is_animated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -196,10 +203,10 @@ def test_alpha_quality(tmp_path: Path) -> None:
 | 
				
			||||||
    with Image.open("Tests/images/transparent.png") as im:
 | 
					    with Image.open("Tests/images/transparent.png") as im:
 | 
				
			||||||
        first_frame = Image.new("L", im.size)
 | 
					        first_frame = Image.new("L", im.size)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        out = str(tmp_path / "temp.webp")
 | 
					        out = tmp_path / "temp.webp"
 | 
				
			||||||
        first_frame.save(out, save_all=True, append_images=[im])
 | 
					        first_frame.save(out, save_all=True, append_images=[im])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        out_quality = str(tmp_path / "quality.webp")
 | 
					        out_quality = tmp_path / "quality.webp"
 | 
				
			||||||
        first_frame.save(
 | 
					        first_frame.save(
 | 
				
			||||||
            out_quality, save_all=True, append_images=[im], alpha_quality=50
 | 
					            out_quality, save_all=True, append_images=[im], alpha_quality=50
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,7 +13,7 @@ RGB_MODE = "RGB"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_write_lossless_rgb(tmp_path: Path) -> None:
 | 
					def test_write_lossless_rgb(tmp_path: Path) -> None:
 | 
				
			||||||
    temp_file = str(tmp_path / "temp.webp")
 | 
					    temp_file = tmp_path / "temp.webp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    hopper(RGB_MODE).save(temp_file, lossless=True)
 | 
					    hopper(RGB_MODE).save(temp_file, lossless=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,7 +6,7 @@ from types import ModuleType
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import pytest
 | 
					import pytest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from PIL import Image
 | 
					from PIL import Image, WebPImagePlugin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .helper import mark_if_feature_version, skip_unless_feature
 | 
					from .helper import mark_if_feature_version, skip_unless_feature
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -40,7 +40,7 @@ def test_read_exif_metadata() -> None:
 | 
				
			||||||
def test_read_exif_metadata_without_prefix() -> None:
 | 
					def test_read_exif_metadata_without_prefix() -> None:
 | 
				
			||||||
    with Image.open("Tests/images/flower2.webp") as im:
 | 
					    with Image.open("Tests/images/flower2.webp") as im:
 | 
				
			||||||
        # Assert prefix is not present
 | 
					        # Assert prefix is not present
 | 
				
			||||||
        assert im.info["exif"][:6] != b"Exif\x00\x00"
 | 
					        assert not im.info["exif"].startswith(b"Exif\x00\x00")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        exif = im.getexif()
 | 
					        exif = im.getexif()
 | 
				
			||||||
        assert exif[305] == "Adobe Photoshop CS6 (Macintosh)"
 | 
					        assert exif[305] == "Adobe Photoshop CS6 (Macintosh)"
 | 
				
			||||||
| 
						 | 
					@ -110,6 +110,7 @@ def test_read_no_exif() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    test_buffer.seek(0)
 | 
					    test_buffer.seek(0)
 | 
				
			||||||
    with Image.open(test_buffer) as webp_image:
 | 
					    with Image.open(test_buffer) as webp_image:
 | 
				
			||||||
 | 
					        assert isinstance(webp_image, WebPImagePlugin.WebPImageFile)
 | 
				
			||||||
        assert not webp_image._getexif()
 | 
					        assert not webp_image._getexif()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -146,7 +147,7 @@ def test_write_animated_metadata(tmp_path: Path) -> None:
 | 
				
			||||||
    exif_data = b"<exif_data>"
 | 
					    exif_data = b"<exif_data>"
 | 
				
			||||||
    xmp_data = b"<xmp_data>"
 | 
					    xmp_data = b"<xmp_data>"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    temp_file = str(tmp_path / "temp.webp")
 | 
					    temp_file = tmp_path / "temp.webp"
 | 
				
			||||||
    with Image.open("Tests/images/anim_frame1.webp") as frame1:
 | 
					    with Image.open("Tests/images/anim_frame1.webp") as frame1:
 | 
				
			||||||
        with Image.open("Tests/images/anim_frame2.webp") as frame2:
 | 
					        with Image.open("Tests/images/anim_frame2.webp") as frame2:
 | 
				
			||||||
            frame1.save(
 | 
					            frame1.save(
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,7 +8,7 @@ import pytest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from PIL import Image, ImageFile, WmfImagePlugin
 | 
					from PIL import Image, ImageFile, WmfImagePlugin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .helper import assert_image_similar_tofile, hopper
 | 
					from .helper import assert_image_equal_tofile, assert_image_similar_tofile, hopper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_load_raw() -> None:
 | 
					def test_load_raw() -> None:
 | 
				
			||||||
| 
						 | 
					@ -32,7 +32,25 @@ def test_load_raw() -> None:
 | 
				
			||||||
def test_load() -> None:
 | 
					def test_load() -> None:
 | 
				
			||||||
    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"):
 | 
				
			||||||
            assert im.load()[0, 0] == (255, 255, 255)
 | 
					            px = im.load()
 | 
				
			||||||
 | 
					            assert px is not None
 | 
				
			||||||
 | 
					            assert px[0, 0] == (255, 255, 255)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_load_zero_inch() -> None:
 | 
				
			||||||
 | 
					    b = BytesIO(b"\xd7\xcd\xc6\x9a\x00\x00" + b"\x00" * 10)
 | 
				
			||||||
 | 
					    with pytest.raises(ValueError):
 | 
				
			||||||
 | 
					        with Image.open(b):
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_render() -> None:
 | 
				
			||||||
 | 
					    with open("Tests/images/drawing.emf", "rb") as fp:
 | 
				
			||||||
 | 
					        data = fp.read()
 | 
				
			||||||
 | 
					    b = BytesIO(data[:808] + b"\x00" + data[809:])
 | 
				
			||||||
 | 
					    with Image.open(b) as im:
 | 
				
			||||||
 | 
					        if hasattr(Image.core, "drawwmf"):
 | 
				
			||||||
 | 
					            assert_image_equal_tofile(im, "Tests/images/drawing.emf")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_register_handler(tmp_path: Path) -> None:
 | 
					def test_register_handler(tmp_path: Path) -> None:
 | 
				
			||||||
| 
						 | 
					@ -50,7 +68,7 @@ def test_register_handler(tmp_path: Path) -> None:
 | 
				
			||||||
    WmfImagePlugin.register_handler(handler)
 | 
					    WmfImagePlugin.register_handler(handler)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    im = hopper()
 | 
					    im = hopper()
 | 
				
			||||||
    tmpfile = str(tmp_path / "temp.wmf")
 | 
					    tmpfile = tmp_path / "temp.wmf"
 | 
				
			||||||
    im.save(tmpfile)
 | 
					    im.save(tmpfile)
 | 
				
			||||||
    assert handler.methodCalled
 | 
					    assert handler.methodCalled
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -64,13 +82,14 @@ def test_load_float_dpi() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with open("Tests/images/drawing.emf", "rb") as fp:
 | 
					    with open("Tests/images/drawing.emf", "rb") as fp:
 | 
				
			||||||
        data = fp.read()
 | 
					        data = fp.read()
 | 
				
			||||||
    b = BytesIO(data[:8] + b"\x06\xFA" + data[10:])
 | 
					    b = BytesIO(data[:8] + b"\x06\xfa" + data[10:])
 | 
				
			||||||
    with Image.open(b) as im:
 | 
					    with Image.open(b) as im:
 | 
				
			||||||
        assert im.info["dpi"][0] == 2540
 | 
					        assert im.info["dpi"][0] == 2540
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_load_set_dpi() -> None:
 | 
					def test_load_set_dpi() -> None:
 | 
				
			||||||
    with Image.open("Tests/images/drawing.wmf") as im:
 | 
					    with Image.open("Tests/images/drawing.wmf") as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, WmfImagePlugin.WmfStubImageFile)
 | 
				
			||||||
        assert im.size == (82, 82)
 | 
					        assert im.size == (82, 82)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if hasattr(Image.core, "drawwmf"):
 | 
					        if hasattr(Image.core, "drawwmf"):
 | 
				
			||||||
| 
						 | 
					@ -79,11 +98,27 @@ def test_load_set_dpi() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            assert_image_similar_tofile(im, "Tests/images/drawing_wmf_ref_144.png", 2.1)
 | 
					            assert_image_similar_tofile(im, "Tests/images/drawing_wmf_ref_144.png", 2.1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    with Image.open("Tests/images/drawing.emf") as im:
 | 
				
			||||||
 | 
					        assert im.size == (1625, 1625)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not hasattr(Image.core, "drawwmf"):
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        assert isinstance(im, WmfImagePlugin.WmfStubImageFile)
 | 
				
			||||||
 | 
					        im.load(im.info["dpi"])
 | 
				
			||||||
 | 
					        assert im.size == (1625, 1625)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    with Image.open("Tests/images/drawing.emf") as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, WmfImagePlugin.WmfStubImageFile)
 | 
				
			||||||
 | 
					        im.load((72, 144))
 | 
				
			||||||
 | 
					        assert im.size == (82, 164)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assert_image_equal_tofile(im, "Tests/images/drawing_emf_ref_72_144.png")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@pytest.mark.parametrize("ext", (".wmf", ".emf"))
 | 
					@pytest.mark.parametrize("ext", (".wmf", ".emf"))
 | 
				
			||||||
def test_save(ext: str, tmp_path: Path) -> None:
 | 
					def test_save(ext: str, tmp_path: Path) -> None:
 | 
				
			||||||
    im = hopper()
 | 
					    im = hopper()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    tmpfile = str(tmp_path / ("temp" + ext))
 | 
					    tmpfile = tmp_path / ("temp" + ext)
 | 
				
			||||||
    with pytest.raises(OSError):
 | 
					    with pytest.raises(OSError):
 | 
				
			||||||
        im.save(tmpfile)
 | 
					        im.save(tmpfile)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -73,7 +73,7 @@ def test_invalid_file() -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_save_wrong_mode(tmp_path: Path) -> None:
 | 
					def test_save_wrong_mode(tmp_path: Path) -> None:
 | 
				
			||||||
    im = hopper()
 | 
					    im = hopper()
 | 
				
			||||||
    out = str(tmp_path / "temp.xbm")
 | 
					    out = tmp_path / "temp.xbm"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with pytest.raises(OSError):
 | 
					    with pytest.raises(OSError):
 | 
				
			||||||
        im.save(out)
 | 
					        im.save(out)
 | 
				
			||||||
| 
						 | 
					@ -81,7 +81,7 @@ def test_save_wrong_mode(tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_hotspot(tmp_path: Path) -> None:
 | 
					def test_hotspot(tmp_path: Path) -> None:
 | 
				
			||||||
    im = hopper("1")
 | 
					    im = hopper("1")
 | 
				
			||||||
    out = str(tmp_path / "temp.xbm")
 | 
					    out = tmp_path / "temp.xbm"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    hotspot = (0, 7)
 | 
					    hotspot = (0, 7)
 | 
				
			||||||
    im.save(out, hotspot=hotspot)
 | 
					    im.save(out, hotspot=hotspot)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -30,6 +30,7 @@ def test_invalid_file() -> None:
 | 
				
			||||||
def test_load_read() -> None:
 | 
					def test_load_read() -> None:
 | 
				
			||||||
    # Arrange
 | 
					    # Arrange
 | 
				
			||||||
    with Image.open(TEST_FILE) as im:
 | 
					    with Image.open(TEST_FILE) as im:
 | 
				
			||||||
 | 
					        assert isinstance(im, XpmImagePlugin.XpmImageFile)
 | 
				
			||||||
        dummy_bytes = 1
 | 
					        dummy_bytes = 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Act
 | 
					        # Act
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,7 @@
 | 
				
			||||||
from __future__ import annotations
 | 
					from __future__ import annotations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import io
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import pytest
 | 
					import pytest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from PIL import BdfFontFile, FontFile
 | 
					from PIL import BdfFontFile, FontFile
 | 
				
			||||||
| 
						 | 
					@ -8,13 +10,20 @@ filename = "Tests/images/courB08.bdf"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_sanity() -> None:
 | 
					def test_sanity() -> None:
 | 
				
			||||||
    with open(filename, "rb") as test_file:
 | 
					    with open(filename, "rb") as fp:
 | 
				
			||||||
        font = BdfFontFile.BdfFontFile(test_file)
 | 
					        font = BdfFontFile.BdfFontFile(fp)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    assert isinstance(font, FontFile.FontFile)
 | 
					    assert isinstance(font, FontFile.FontFile)
 | 
				
			||||||
    assert len([_f for _f in font.glyph if _f]) == 190
 | 
					    assert len([_f for _f in font.glyph if _f]) == 190
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_zero_width_chars() -> None:
 | 
				
			||||||
 | 
					    with open(filename, "rb") as fp:
 | 
				
			||||||
 | 
					        data = fp.read()
 | 
				
			||||||
 | 
					    data = data[:2650] + b"\x00\x00" + data[2652:]
 | 
				
			||||||
 | 
					    BdfFontFile.BdfFontFile(io.BytesIO(data))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_invalid_file() -> None:
 | 
					def test_invalid_file() -> None:
 | 
				
			||||||
    with open("Tests/images/flower.jpg", "rb") as fp:
 | 
					    with open("Tests/images/flower.jpg", "rb") as fp:
 | 
				
			||||||
        with pytest.raises(SyntaxError):
 | 
					        with pytest.raises(SyntaxError):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,7 +4,20 @@ from pathlib import Path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import pytest
 | 
					import pytest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from PIL import FontFile
 | 
					from PIL import FontFile, Image
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_compile() -> None:
 | 
				
			||||||
 | 
					    font = FontFile.FontFile()
 | 
				
			||||||
 | 
					    font.glyph[0] = ((0, 0), (0, 0, 0, 0), (0, 0, 0, 1), Image.new("L", (0, 0)))
 | 
				
			||||||
 | 
					    font.compile()
 | 
				
			||||||
 | 
					    assert font.ysize == 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    font.ysize = 2
 | 
				
			||||||
 | 
					    font.compile()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Assert that compiling again did not change anything
 | 
				
			||||||
 | 
					    assert font.ysize == 2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_save(tmp_path: Path) -> None:
 | 
					def test_save(tmp_path: Path) -> None:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -22,28 +22,26 @@ def test_sanity() -> None:
 | 
				
			||||||
    Image.new("HSV", (100, 100))
 | 
					    Image.new("HSV", (100, 100))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def wedge() -> Image.Image:
 | 
					def linear_gradient() -> Image.Image:
 | 
				
			||||||
    w = Image._wedge()
 | 
					    im = Image.linear_gradient(mode="L")
 | 
				
			||||||
    w90 = w.rotate(90)
 | 
					    im90 = im.rotate(90)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    (px, h) = w.size
 | 
					    (px, h) = im.size
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    r = Image.new("L", (px * 3, h))
 | 
					    r = Image.new("L", (px * 3, h))
 | 
				
			||||||
    g = r.copy()
 | 
					    g = r.copy()
 | 
				
			||||||
    b = r.copy()
 | 
					    b = r.copy()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    r.paste(w, (0, 0))
 | 
					    r.paste(im, (0, 0))
 | 
				
			||||||
    r.paste(w90, (px, 0))
 | 
					    r.paste(im90, (px, 0))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    g.paste(w90, (0, 0))
 | 
					    g.paste(im90, (0, 0))
 | 
				
			||||||
    g.paste(w, (2 * px, 0))
 | 
					    g.paste(im, (2 * px, 0))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    b.paste(w, (px, 0))
 | 
					    b.paste(im, (px, 0))
 | 
				
			||||||
    b.paste(w90, (2 * px, 0))
 | 
					    b.paste(im90, (2 * px, 0))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    img = Image.merge("RGB", (r, g, b))
 | 
					    return Image.merge("RGB", (r, g, b))
 | 
				
			||||||
 | 
					 | 
				
			||||||
    return img
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def to_xxx_colorsys(
 | 
					def to_xxx_colorsys(
 | 
				
			||||||
| 
						 | 
					@ -79,8 +77,8 @@ def to_rgb_colorsys(im: Image.Image) -> Image.Image:
 | 
				
			||||||
    return to_xxx_colorsys(im, colorsys.hsv_to_rgb, "RGB")
 | 
					    return to_xxx_colorsys(im, colorsys.hsv_to_rgb, "RGB")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_wedge() -> None:
 | 
					def test_linear_gradient() -> None:
 | 
				
			||||||
    src = wedge().resize((3 * 32, 32), Image.Resampling.BILINEAR)
 | 
					    src = linear_gradient().resize((3 * 32, 32), Image.Resampling.BILINEAR)
 | 
				
			||||||
    im = src.convert("HSV")
 | 
					    im = src.convert("HSV")
 | 
				
			||||||
    comparable = to_hsv_colorsys(src)
 | 
					    comparable = to_hsv_colorsys(src)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -65,21 +65,20 @@ class TestImage:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @pytest.mark.parametrize("mode", ("", "bad", "very very long"))
 | 
					    @pytest.mark.parametrize("mode", ("", "bad", "very very long"))
 | 
				
			||||||
    def test_image_modes_fail(self, mode: str) -> None:
 | 
					    def test_image_modes_fail(self, mode: str) -> None:
 | 
				
			||||||
        with pytest.raises(ValueError) as e:
 | 
					        with pytest.raises(ValueError, match="unrecognized image mode"):
 | 
				
			||||||
            Image.new(mode, (1, 1))
 | 
					            Image.new(mode, (1, 1))
 | 
				
			||||||
        assert str(e.value) == "unrecognized image mode"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_exception_inheritance(self) -> None:
 | 
					    def test_exception_inheritance(self) -> None:
 | 
				
			||||||
        assert issubclass(UnidentifiedImageError, OSError)
 | 
					        assert issubclass(UnidentifiedImageError, OSError)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_sanity(self) -> None:
 | 
					    def test_sanity(self) -> None:
 | 
				
			||||||
        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).startswith("<PIL.Image.Image image mode=L size=100x100 at")
 | 
				
			||||||
        assert im.mode == "L"
 | 
					        assert im.mode == "L"
 | 
				
			||||||
        assert im.size == (100, 100)
 | 
					        assert im.size == (100, 100)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        im = Image.new("RGB", (100, 100))
 | 
					        im = Image.new("RGB", (100, 100))
 | 
				
			||||||
        assert repr(im)[:45] == "<PIL.Image.Image image mode=RGB size=100x100 "
 | 
					        assert repr(im).startswith("<PIL.Image.Image image mode=RGB size=100x100 ")
 | 
				
			||||||
        assert im.mode == "RGB"
 | 
					        assert im.mode == "RGB"
 | 
				
			||||||
        assert im.size == (100, 100)
 | 
					        assert im.size == (100, 100)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -176,6 +175,13 @@ class TestImage:
 | 
				
			||||||
            with Image.open(io.StringIO()):  # type: ignore[arg-type]
 | 
					            with Image.open(io.StringIO()):  # type: ignore[arg-type]
 | 
				
			||||||
                pass
 | 
					                pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_string(self, tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					        out = str(tmp_path / "temp.png")
 | 
				
			||||||
 | 
					        im = hopper()
 | 
				
			||||||
 | 
					        im.save(out)
 | 
				
			||||||
 | 
					        with Image.open(out) as reloaded:
 | 
				
			||||||
 | 
					            assert_image_equal(im, reloaded)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_pathlib(self, tmp_path: Path) -> None:
 | 
					    def test_pathlib(self, tmp_path: Path) -> None:
 | 
				
			||||||
        with Image.open(Path("Tests/images/multipage-mmap.tiff")) as im:
 | 
					        with Image.open(Path("Tests/images/multipage-mmap.tiff")) as im:
 | 
				
			||||||
            assert im.mode == "P"
 | 
					            assert im.mode == "P"
 | 
				
			||||||
| 
						 | 
					@ -188,16 +194,13 @@ class TestImage:
 | 
				
			||||||
            for ext in (".jpg", ".jp2"):
 | 
					            for ext in (".jpg", ".jp2"):
 | 
				
			||||||
                if ext == ".jp2" and not features.check_codec("jpg_2000"):
 | 
					                if ext == ".jp2" and not features.check_codec("jpg_2000"):
 | 
				
			||||||
                    pytest.skip("jpg_2000 not available")
 | 
					                    pytest.skip("jpg_2000 not available")
 | 
				
			||||||
                temp_file = str(tmp_path / ("temp." + ext))
 | 
					                im.save(tmp_path / ("temp." + ext))
 | 
				
			||||||
                if os.path.exists(temp_file):
 | 
					 | 
				
			||||||
                    os.remove(temp_file)
 | 
					 | 
				
			||||||
                im.save(Path(temp_file))
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_fp_name(self, tmp_path: Path) -> None:
 | 
					    def test_fp_name(self, tmp_path: Path) -> None:
 | 
				
			||||||
        temp_file = str(tmp_path / "temp.jpg")
 | 
					        temp_file = tmp_path / "temp.jpg"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        class FP(io.BytesIO):
 | 
					        class FP(io.BytesIO):
 | 
				
			||||||
            name: str
 | 
					            name: Path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if sys.version_info >= (3, 12):
 | 
					            if sys.version_info >= (3, 12):
 | 
				
			||||||
                from collections.abc import Buffer
 | 
					                from collections.abc import Buffer
 | 
				
			||||||
| 
						 | 
					@ -228,7 +231,7 @@ class TestImage:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_unknown_extension(self, tmp_path: Path) -> None:
 | 
					    def test_unknown_extension(self, tmp_path: Path) -> None:
 | 
				
			||||||
        im = hopper()
 | 
					        im = hopper()
 | 
				
			||||||
        temp_file = str(tmp_path / "temp.unknown")
 | 
					        temp_file = tmp_path / "temp.unknown"
 | 
				
			||||||
        with pytest.raises(ValueError):
 | 
					        with pytest.raises(ValueError):
 | 
				
			||||||
            im.save(temp_file)
 | 
					            im.save(temp_file)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -248,7 +251,7 @@ class TestImage:
 | 
				
			||||||
        reason="Test requires opening an mmaped file for writing",
 | 
					        reason="Test requires opening an mmaped file for writing",
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    def test_readonly_save(self, tmp_path: Path) -> None:
 | 
					    def test_readonly_save(self, tmp_path: Path) -> None:
 | 
				
			||||||
        temp_file = str(tmp_path / "temp.bmp")
 | 
					        temp_file = tmp_path / "temp.bmp"
 | 
				
			||||||
        shutil.copy("Tests/images/rgb32bf-rgba.bmp", temp_file)
 | 
					        shutil.copy("Tests/images/rgb32bf-rgba.bmp", temp_file)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        with Image.open(temp_file) as im:
 | 
					        with Image.open(temp_file) as im:
 | 
				
			||||||
| 
						 | 
					@ -580,9 +583,7 @@ class TestImage:
 | 
				
			||||||
    def test_one_item_tuple(self) -> None:
 | 
					    def test_one_item_tuple(self) -> None:
 | 
				
			||||||
        for mode in ("I", "F", "L"):
 | 
					        for mode in ("I", "F", "L"):
 | 
				
			||||||
            im = Image.new(mode, (100, 100), (5,))
 | 
					            im = Image.new(mode, (100, 100), (5,))
 | 
				
			||||||
            px = im.load()
 | 
					            assert im.getpixel((0, 0)) == 5
 | 
				
			||||||
            assert px is not None
 | 
					 | 
				
			||||||
            assert px[0, 0] == 5
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_linear_gradient_wrong_mode(self) -> None:
 | 
					    def test_linear_gradient_wrong_mode(self) -> None:
 | 
				
			||||||
        # Arrange
 | 
					        # Arrange
 | 
				
			||||||
| 
						 | 
					@ -662,12 +663,13 @@ class TestImage:
 | 
				
			||||||
        im.putpalette(list(range(256)) * 4, "RGBA")
 | 
					        im.putpalette(list(range(256)) * 4, "RGBA")
 | 
				
			||||||
        im_remapped = im.remap_palette(list(range(256)))
 | 
					        im_remapped = im.remap_palette(list(range(256)))
 | 
				
			||||||
        assert_image_equal(im, im_remapped)
 | 
					        assert_image_equal(im, im_remapped)
 | 
				
			||||||
 | 
					        assert im.palette is not None
 | 
				
			||||||
        assert im.palette.palette == im_remapped.palette.palette
 | 
					        assert im.palette.palette == im_remapped.palette.palette
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Test illegal image mode
 | 
					        # Test illegal image mode
 | 
				
			||||||
        with hopper() as im:
 | 
					        with hopper() as im:
 | 
				
			||||||
            with pytest.raises(ValueError):
 | 
					            with pytest.raises(ValueError):
 | 
				
			||||||
                im.remap_palette(None)
 | 
					                im.remap_palette([])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_remap_palette_transparency(self) -> None:
 | 
					    def test_remap_palette_transparency(self) -> None:
 | 
				
			||||||
        im = Image.new("P", (1, 2), (0, 0, 0))
 | 
					        im = Image.new("P", (1, 2), (0, 0, 0))
 | 
				
			||||||
| 
						 | 
					@ -732,7 +734,7 @@ class TestImage:
 | 
				
			||||||
        # https://github.com/python-pillow/Pillow/issues/835
 | 
					        # https://github.com/python-pillow/Pillow/issues/835
 | 
				
			||||||
        # Arrange
 | 
					        # Arrange
 | 
				
			||||||
        test_file = "Tests/images/hopper.png"
 | 
					        test_file = "Tests/images/hopper.png"
 | 
				
			||||||
        temp_file = str(tmp_path / "temp.jpg")
 | 
					        temp_file = tmp_path / "temp.jpg"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Act/Assert
 | 
					        # Act/Assert
 | 
				
			||||||
        with Image.open(test_file) as im:
 | 
					        with Image.open(test_file) as im:
 | 
				
			||||||
| 
						 | 
					@ -742,7 +744,7 @@ class TestImage:
 | 
				
			||||||
                im.save(temp_file)
 | 
					                im.save(temp_file)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_no_new_file_on_error(self, tmp_path: Path) -> None:
 | 
					    def test_no_new_file_on_error(self, tmp_path: Path) -> None:
 | 
				
			||||||
        temp_file = str(tmp_path / "temp.jpg")
 | 
					        temp_file = tmp_path / "temp.jpg"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        im = Image.new("RGB", (0, 0))
 | 
					        im = Image.new("RGB", (0, 0))
 | 
				
			||||||
        with pytest.raises(ValueError):
 | 
					        with pytest.raises(ValueError):
 | 
				
			||||||
| 
						 | 
					@ -770,7 +772,7 @@ class TestImage:
 | 
				
			||||||
        assert dict(exif)
 | 
					        assert dict(exif)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Test that exif data is cleared after another load
 | 
					        # Test that exif data is cleared after another load
 | 
				
			||||||
        exif.load(None)
 | 
					        exif.load(b"")
 | 
				
			||||||
        assert not dict(exif)
 | 
					        assert not dict(exif)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Test loading just the EXIF header
 | 
					        # Test loading just the EXIF header
 | 
				
			||||||
| 
						 | 
					@ -793,6 +795,10 @@ class TestImage:
 | 
				
			||||||
        ifd[36864] = b"0220"
 | 
					        ifd[36864] = b"0220"
 | 
				
			||||||
        assert exif.get_ifd(0x8769) == {36864: b"0220"}
 | 
					        assert exif.get_ifd(0x8769) == {36864: b"0220"}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        reloaded_exif = Image.Exif()
 | 
				
			||||||
 | 
					        reloaded_exif.load(exif.tobytes())
 | 
				
			||||||
 | 
					        assert reloaded_exif.get_ifd(0x8769) == {36864: b"0220"}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @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"
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
| 
						 | 
					@ -805,7 +811,7 @@ class TestImage:
 | 
				
			||||||
            assert exif[296] == 2
 | 
					            assert exif[296] == 2
 | 
				
			||||||
            assert exif[11] == "gThumb 3.0.1"
 | 
					            assert exif[11] == "gThumb 3.0.1"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            out = str(tmp_path / "temp.jpg")
 | 
					            out = tmp_path / "temp.jpg"
 | 
				
			||||||
            exif[258] = 8
 | 
					            exif[258] = 8
 | 
				
			||||||
            del exif[274]
 | 
					            del exif[274]
 | 
				
			||||||
            del exif[282]
 | 
					            del exif[282]
 | 
				
			||||||
| 
						 | 
					@ -827,7 +833,7 @@ class TestImage:
 | 
				
			||||||
            assert exif[274] == 1
 | 
					            assert exif[274] == 1
 | 
				
			||||||
            assert exif[305] == "Adobe Photoshop CC 2017 (Macintosh)"
 | 
					            assert exif[305] == "Adobe Photoshop CC 2017 (Macintosh)"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            out = str(tmp_path / "temp.jpg")
 | 
					            out = tmp_path / "temp.jpg"
 | 
				
			||||||
            exif[258] = 8
 | 
					            exif[258] = 8
 | 
				
			||||||
            del exif[306]
 | 
					            del exif[306]
 | 
				
			||||||
            exif[274] = 455
 | 
					            exif[274] = 455
 | 
				
			||||||
| 
						 | 
					@ -846,7 +852,7 @@ class TestImage:
 | 
				
			||||||
            exif = im.getexif()
 | 
					            exif = im.getexif()
 | 
				
			||||||
            assert exif == {}
 | 
					            assert exif == {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            out = str(tmp_path / "temp.webp")
 | 
					            out = tmp_path / "temp.webp"
 | 
				
			||||||
            exif[258] = 8
 | 
					            exif[258] = 8
 | 
				
			||||||
            exif[40963] = 455
 | 
					            exif[40963] = 455
 | 
				
			||||||
            exif[305] = "Pillow test"
 | 
					            exif[305] = "Pillow test"
 | 
				
			||||||
| 
						 | 
					@ -868,7 +874,7 @@ class TestImage:
 | 
				
			||||||
            exif = im.getexif()
 | 
					            exif = im.getexif()
 | 
				
			||||||
            assert exif == {274: 1}
 | 
					            assert exif == {274: 1}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            out = str(tmp_path / "temp.png")
 | 
					            out = tmp_path / "temp.png"
 | 
				
			||||||
            exif[258] = 8
 | 
					            exif[258] = 8
 | 
				
			||||||
            del exif[274]
 | 
					            del exif[274]
 | 
				
			||||||
            exif[40963] = 455
 | 
					            exif[40963] = 455
 | 
				
			||||||
| 
						 | 
					@ -987,6 +993,11 @@ class TestImage:
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            assert im.getxmp() == {"xmpmeta": None}
 | 
					            assert im.getxmp() == {"xmpmeta": None}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_get_child_images(self) -> None:
 | 
				
			||||||
 | 
					        im = Image.new("RGB", (1, 1))
 | 
				
			||||||
 | 
					        with pytest.warns(DeprecationWarning):
 | 
				
			||||||
 | 
					            assert im.get_child_images() == []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @pytest.mark.parametrize("size", ((1, 0), (0, 1), (0, 0)))
 | 
					    @pytest.mark.parametrize("size", ((1, 0), (0, 1), (0, 0)))
 | 
				
			||||||
    def test_zero_tobytes(self, size: tuple[int, int]) -> None:
 | 
					    def test_zero_tobytes(self, size: tuple[int, int]) -> None:
 | 
				
			||||||
        im = Image.new("RGB", size)
 | 
					        im = Image.new("RGB", size)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -271,13 +271,25 @@ class TestImagePutPixelError:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TestEmbeddable:
 | 
					class TestEmbeddable:
 | 
				
			||||||
    @pytest.mark.xfail(reason="failing test")
 | 
					    @pytest.mark.xfail(not (sys.version_info >= (3, 13)), reason="failing test")
 | 
				
			||||||
    @pytest.mark.skipif(not is_win32(), reason="requires Windows")
 | 
					    @pytest.mark.skipif(not is_win32(), reason="requires Windows")
 | 
				
			||||||
    def test_embeddable(self) -> None:
 | 
					    def test_embeddable(self) -> None:
 | 
				
			||||||
        import ctypes
 | 
					        import ctypes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        from setuptools.command import build_ext
 | 
					        from setuptools.command import build_ext
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        compiler = getattr(build_ext, "new_compiler")()
 | 
				
			||||||
 | 
					        compiler.add_include_dir(sysconfig.get_config_var("INCLUDEPY"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        libdir = sysconfig.get_config_var("LIBDIR") or sysconfig.get_config_var(
 | 
				
			||||||
 | 
					            "INCLUDEPY"
 | 
				
			||||||
 | 
					        ).replace("include", "libs")
 | 
				
			||||||
 | 
					        compiler.add_library_dir(libdir)
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            compiler.initialize()
 | 
				
			||||||
 | 
					        except Exception:
 | 
				
			||||||
 | 
					            pytest.skip("Compiler could not be initialized")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        with open("embed_pil.c", "w", encoding="utf-8") as fh:
 | 
					        with open("embed_pil.c", "w", encoding="utf-8") as fh:
 | 
				
			||||||
            home = sys.prefix.replace("\\", "\\\\")
 | 
					            home = sys.prefix.replace("\\", "\\\\")
 | 
				
			||||||
            fh.write(
 | 
					            fh.write(
 | 
				
			||||||
| 
						 | 
					@ -305,13 +317,6 @@ int main(int argc, char* argv[])
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        compiler = getattr(build_ext, "new_compiler")()
 | 
					 | 
				
			||||||
        compiler.add_include_dir(sysconfig.get_config_var("INCLUDEPY"))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        libdir = sysconfig.get_config_var("LIBDIR") or sysconfig.get_config_var(
 | 
					 | 
				
			||||||
            "INCLUDEPY"
 | 
					 | 
				
			||||||
        ).replace("include", "libs")
 | 
					 | 
				
			||||||
        compiler.add_library_dir(libdir)
 | 
					 | 
				
			||||||
        objects = compiler.compile(["embed_pil.c"])
 | 
					        objects = compiler.compile(["embed_pil.c"])
 | 
				
			||||||
        compiler.link_executable(objects, "embed_pil")
 | 
					        compiler.link_executable(objects, "embed_pil")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -118,7 +118,7 @@ def test_trns_p(tmp_path: Path) -> None:
 | 
				
			||||||
    im = hopper("P")
 | 
					    im = hopper("P")
 | 
				
			||||||
    im.info["transparency"] = 0
 | 
					    im.info["transparency"] = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    f = str(tmp_path / "temp.png")
 | 
					    f = tmp_path / "temp.png"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    im_l = im.convert("L")
 | 
					    im_l = im.convert("L")
 | 
				
			||||||
    assert im_l.info["transparency"] == 0
 | 
					    assert im_l.info["transparency"] == 0
 | 
				
			||||||
| 
						 | 
					@ -154,7 +154,7 @@ def test_trns_l(tmp_path: Path) -> None:
 | 
				
			||||||
    im = hopper("L")
 | 
					    im = hopper("L")
 | 
				
			||||||
    im.info["transparency"] = 128
 | 
					    im.info["transparency"] = 128
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    f = str(tmp_path / "temp.png")
 | 
					    f = tmp_path / "temp.png"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    im_la = im.convert("LA")
 | 
					    im_la = im.convert("LA")
 | 
				
			||||||
    assert "transparency" not in im_la.info
 | 
					    assert "transparency" not in im_la.info
 | 
				
			||||||
| 
						 | 
					@ -177,7 +177,7 @@ def test_trns_RGB(tmp_path: Path) -> None:
 | 
				
			||||||
    im = hopper("RGB")
 | 
					    im = hopper("RGB")
 | 
				
			||||||
    im.info["transparency"] = im.getpixel((0, 0))
 | 
					    im.info["transparency"] = im.getpixel((0, 0))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    f = str(tmp_path / "temp.png")
 | 
					    f = tmp_path / "temp.png"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    im_l = im.convert("L")
 | 
					    im_l = im.convert("L")
 | 
				
			||||||
    assert im_l.info["transparency"] == im_l.getpixel((0, 0))  # undone
 | 
					    assert im_l.info["transparency"] == im_l.getpixel((0, 0))  # undone
 | 
				
			||||||
| 
						 | 
					@ -222,9 +222,7 @@ def test_l_macro_rounding(convert_mode: str) -> None:
 | 
				
			||||||
        im.palette.getcolor((0, 1, 2))
 | 
					        im.palette.getcolor((0, 1, 2))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        converted_im = im.convert(convert_mode)
 | 
					        converted_im = im.convert(convert_mode)
 | 
				
			||||||
        px = converted_im.load()
 | 
					        converted_color = converted_im.getpixel((0, 0))
 | 
				
			||||||
        assert px is not None
 | 
					 | 
				
			||||||
        converted_color = px[0, 0]
 | 
					 | 
				
			||||||
        if convert_mode == "LA":
 | 
					        if convert_mode == "LA":
 | 
				
			||||||
            assert isinstance(converted_color, tuple)
 | 
					            assert isinstance(converted_color, tuple)
 | 
				
			||||||
            converted_color = converted_color[0]
 | 
					            converted_color = converted_color[0]
 | 
				
			||||||
| 
						 | 
					@ -236,6 +234,7 @@ def test_gif_with_rgba_palette_to_p() -> None:
 | 
				
			||||||
    with Image.open("Tests/images/hopper.gif") as im:
 | 
					    with Image.open("Tests/images/hopper.gif") as im:
 | 
				
			||||||
        im.info["transparency"] = 255
 | 
					        im.info["transparency"] = 255
 | 
				
			||||||
        im.load()
 | 
					        im.load()
 | 
				
			||||||
 | 
					        assert im.palette is not None
 | 
				
			||||||
        assert im.palette.mode == "RGB"
 | 
					        assert im.palette.mode == "RGB"
 | 
				
			||||||
        im_p = im.convert("P")
 | 
					        im_p = im.convert("P")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -148,10 +148,8 @@ def test_palette(method: Image.Quantize, color: tuple[int, ...]) -> None:
 | 
				
			||||||
    im = Image.new("RGBA" if len(color) == 4 else "RGB", (1, 1), color)
 | 
					    im = Image.new("RGBA" if len(color) == 4 else "RGB", (1, 1), color)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    converted = im.quantize(method=method)
 | 
					    converted = im.quantize(method=method)
 | 
				
			||||||
    converted_px = converted.load()
 | 
					 | 
				
			||||||
    assert converted_px is not None
 | 
					 | 
				
			||||||
    assert converted.palette is not None
 | 
					    assert converted.palette is not None
 | 
				
			||||||
    assert converted_px[0, 0] == converted.palette.colors[color]
 | 
					    assert converted.getpixel((0, 0)) == converted.palette.colors[color]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_small_palette() -> None:
 | 
					def test_small_palette() -> None:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -171,7 +171,7 @@ class TestImagingCoreResize:
 | 
				
			||||||
        # platforms. So if a future Pillow change requires that the test file
 | 
					        # platforms. So if a future Pillow change requires that the test file
 | 
				
			||||||
        # be updated, that is okay.
 | 
					        # be updated, that is okay.
 | 
				
			||||||
        im = hopper().resize((64, 64))
 | 
					        im = hopper().resize((64, 64))
 | 
				
			||||||
        temp_file = str(tmp_path / "temp.gif")
 | 
					        temp_file = tmp_path / "temp.gif"
 | 
				
			||||||
        im.save(temp_file)
 | 
					        im.save(temp_file)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        with Image.open(temp_file) as reloaded:
 | 
					        with Image.open(temp_file) as reloaded:
 | 
				
			||||||
| 
						 | 
					@ -309,7 +309,7 @@ class TestImageResize:
 | 
				
			||||||
        # Test unknown resampling filter
 | 
					        # Test unknown resampling filter
 | 
				
			||||||
        with hopper() as im:
 | 
					        with hopper() as im:
 | 
				
			||||||
            with pytest.raises(ValueError):
 | 
					            with pytest.raises(ValueError):
 | 
				
			||||||
                im.resize((10, 10), "unknown")
 | 
					                im.resize((10, 10), -1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @skip_unless_feature("libtiff")
 | 
					    @skip_unless_feature("libtiff")
 | 
				
			||||||
    def test_transposed(self) -> None:
 | 
					    def test_transposed(self) -> None:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
		Reference in New Issue
	
	Block a user