mirror of
				https://github.com/python-pillow/Pillow.git
				synced 2025-11-04 01:47:47 +03:00 
			
		
		
		
	Merge branch 'python-pillow'
This commit is contained in:
		
						commit
						23f55505c8
					
				| 
						 | 
					@ -21,9 +21,11 @@ environment:
 | 
				
			||||||
install:
 | 
					install:
 | 
				
			||||||
- '%PYTHON%\%EXECUTABLE% --version'
 | 
					- '%PYTHON%\%EXECUTABLE% --version'
 | 
				
			||||||
- curl -fsSL -o pillow-depends.zip https://github.com/python-pillow/pillow-depends/archive/main.zip
 | 
					- curl -fsSL -o pillow-depends.zip https://github.com/python-pillow/pillow-depends/archive/main.zip
 | 
				
			||||||
 | 
					- curl -fsSL -o pillow-test-images.zip https://github.com/python-pillow/test-images/archive/main.zip
 | 
				
			||||||
- 7z x pillow-depends.zip -oc:\
 | 
					- 7z x pillow-depends.zip -oc:\
 | 
				
			||||||
 | 
					- 7z x pillow-test-images.zip -oc:\
 | 
				
			||||||
- mv c:\pillow-depends-main c:\pillow-depends
 | 
					- mv c:\pillow-depends-main c:\pillow-depends
 | 
				
			||||||
- xcopy /S /Y c:\pillow-depends\test_images\* c:\pillow\tests\images
 | 
					- xcopy /S /Y c:\test-images-main\* c:\pillow\tests\images
 | 
				
			||||||
- 7z x ..\pillow-depends\nasm-2.15.05-win64.zip -oc:\
 | 
					- 7z x ..\pillow-depends\nasm-2.15.05-win64.zip -oc:\
 | 
				
			||||||
- ..\pillow-depends\gs1000w32.exe /S
 | 
					- ..\pillow-depends\gs1000w32.exe /S
 | 
				
			||||||
- path c:\nasm-2.15.05;C:\Program Files (x86)\gs\gs10.0.0\bin;%PATH%
 | 
					- path c:\nasm-2.15.05;C:\Program Files (x86)\gs\gs10.0.0\bin;%PATH%
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -37,7 +37,8 @@ python3 -m pip install -U pytest-timeout
 | 
				
			||||||
python3 -m pip install pyroma
 | 
					python3 -m pip install pyroma
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if [[ $(uname) != CYGWIN* ]]; then
 | 
					if [[ $(uname) != CYGWIN* ]]; then
 | 
				
			||||||
    python3 -m pip install numpy
 | 
					    # TODO Remove condition when NumPy supports 3.12
 | 
				
			||||||
 | 
					    if ! [ "$GHA_PYTHON_VERSION" == "3.12-dev" ]; then python3 -m pip install numpy ; fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # PyQt6 doesn't support PyPy3
 | 
					    # PyQt6 doesn't support PyPy3
 | 
				
			||||||
    if [[ $GHA_PYTHON_VERSION == 3.* ]]; then
 | 
					    if [[ $GHA_PYTHON_VERSION == 3.* ]]; then
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										3
									
								
								.github/workflows/macos-install.sh
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.github/workflows/macos-install.sh
									
									
									
									
										vendored
									
									
								
							| 
						 | 
					@ -13,7 +13,8 @@ python3 -m pip install -U pytest-cov
 | 
				
			||||||
python3 -m pip install -U pytest-timeout
 | 
					python3 -m pip install -U pytest-timeout
 | 
				
			||||||
python3 -m pip install pyroma
 | 
					python3 -m pip install pyroma
 | 
				
			||||||
 | 
					
 | 
				
			||||||
python3 -m pip install numpy
 | 
					# TODO Remove condition when NumPy supports 3.12
 | 
				
			||||||
 | 
					if ! [ "$GHA_PYTHON_VERSION" == "3.12-dev" ]; then python3 -m pip install numpy ; fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# extra test images
 | 
					# extra test images
 | 
				
			||||||
pushd depends && ./install_extra_test_images.sh && popd
 | 
					pushd depends && ./install_extra_test_images.sh && popd
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										28
									
								
								.github/workflows/test-cygwin.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										28
									
								
								.github/workflows/test-cygwin.yml
									
									
									
									
										vendored
									
									
								
							| 
						 | 
					@ -34,18 +34,34 @@ jobs:
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          platform: x86_64
 | 
					          platform: x86_64
 | 
				
			||||||
          packages: >
 | 
					          packages: >
 | 
				
			||||||
            ImageMagick gcc-g++ ghostscript jpeg libfreetype-devel
 | 
					            gcc-g++
 | 
				
			||||||
            libimagequant-devel libjpeg-devel liblapack-devel
 | 
					            ghostscript
 | 
				
			||||||
            liblcms2-devel libopenjp2-devel libraqm-devel
 | 
					            ImageMagick
 | 
				
			||||||
            libtiff-devel libwebp-devel libxcb-devel libxcb-xinerama0
 | 
					            jpeg
 | 
				
			||||||
            make netpbm perl
 | 
					            libfreetype-devel
 | 
				
			||||||
 | 
					            libimagequant-devel
 | 
				
			||||||
 | 
					            libjpeg-devel
 | 
				
			||||||
 | 
					            liblapack-devel
 | 
				
			||||||
 | 
					            liblcms2-devel
 | 
				
			||||||
 | 
					            libopenjp2-devel
 | 
				
			||||||
 | 
					            libraqm-devel
 | 
				
			||||||
 | 
					            libtiff-devel
 | 
				
			||||||
 | 
					            libwebp-devel
 | 
				
			||||||
 | 
					            libxcb-devel
 | 
				
			||||||
 | 
					            libxcb-xinerama0
 | 
				
			||||||
 | 
					            make
 | 
				
			||||||
 | 
					            netpbm
 | 
				
			||||||
 | 
					            perl
 | 
				
			||||||
            python3${{ matrix.python-minor-version }}-cffi
 | 
					            python3${{ matrix.python-minor-version }}-cffi
 | 
				
			||||||
            python3${{ matrix.python-minor-version }}-cython
 | 
					            python3${{ matrix.python-minor-version }}-cython
 | 
				
			||||||
            python3${{ matrix.python-minor-version }}-devel
 | 
					            python3${{ matrix.python-minor-version }}-devel
 | 
				
			||||||
            python3${{ matrix.python-minor-version }}-numpy
 | 
					            python3${{ matrix.python-minor-version }}-numpy
 | 
				
			||||||
            python3${{ matrix.python-minor-version }}-sip
 | 
					            python3${{ matrix.python-minor-version }}-sip
 | 
				
			||||||
            python3${{ matrix.python-minor-version }}-tkinter
 | 
					            python3${{ matrix.python-minor-version }}-tkinter
 | 
				
			||||||
            qt5-devel-tools subversion xorg-server-extra zlib-devel
 | 
					            qt5-devel-tools
 | 
				
			||||||
 | 
					            wget
 | 
				
			||||||
 | 
					            xorg-server-extra
 | 
				
			||||||
 | 
					            zlib-devel
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Add Lapack to PATH
 | 
					      - name: Add Lapack to PATH
 | 
				
			||||||
        uses: egor-tensin/cleanup-path@v3
 | 
					        uses: egor-tensin/cleanup-path@v3
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										1
									
								
								.github/workflows/test-docker.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/workflows/test-docker.yml
									
									
									
									
										vendored
									
									
								
							| 
						 | 
					@ -87,6 +87,7 @@ jobs:
 | 
				
			||||||
      with:
 | 
					      with:
 | 
				
			||||||
        flags: GHA_Docker
 | 
					        flags: GHA_Docker
 | 
				
			||||||
        name: ${{ matrix.docker }}
 | 
					        name: ${{ matrix.docker }}
 | 
				
			||||||
 | 
					        gcov: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  success:
 | 
					  success:
 | 
				
			||||||
    permissions:
 | 
					    permissions:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										19
									
								
								.github/workflows/test-mingw.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										19
									
								
								.github/workflows/test-mingw.yml
									
									
									
									
										vendored
									
									
								
							| 
						 | 
					@ -5,7 +5,7 @@ on: [push, pull_request, workflow_dispatch]
 | 
				
			||||||
permissions:
 | 
					permissions:
 | 
				
			||||||
  contents: read
 | 
					  contents: read
 | 
				
			||||||
 | 
					
 | 
				
			||||||
concurrency: 
 | 
					concurrency:
 | 
				
			||||||
  group: ${{ github.workflow }}-${{ github.ref }}
 | 
					  group: ${{ github.workflow }}-${{ github.ref }}
 | 
				
			||||||
  cancel-in-progress: true
 | 
					  cancel-in-progress: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -45,12 +45,6 @@ jobs:
 | 
				
			||||||
      - name: Install dependencies
 | 
					      - name: Install dependencies
 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
          pacman -S --noconfirm \
 | 
					          pacman -S --noconfirm \
 | 
				
			||||||
              ${{ matrix.package }}-python3-cffi \
 | 
					 | 
				
			||||||
              ${{ matrix.package }}-python3-numpy \
 | 
					 | 
				
			||||||
              ${{ matrix.package }}-python3-olefile \
 | 
					 | 
				
			||||||
              ${{ matrix.package }}-python3-pip \
 | 
					 | 
				
			||||||
              ${{ matrix.package }}-python-pyqt6 \
 | 
					 | 
				
			||||||
              ${{ matrix.package }}-python3-setuptools \
 | 
					 | 
				
			||||||
              ${{ matrix.package }}-freetype \
 | 
					              ${{ matrix.package }}-freetype \
 | 
				
			||||||
              ${{ matrix.package }}-gcc \
 | 
					              ${{ matrix.package }}-gcc \
 | 
				
			||||||
              ${{ matrix.package }}-ghostscript \
 | 
					              ${{ matrix.package }}-ghostscript \
 | 
				
			||||||
| 
						 | 
					@ -61,7 +55,16 @@ jobs:
 | 
				
			||||||
              ${{ matrix.package }}-libtiff \
 | 
					              ${{ matrix.package }}-libtiff \
 | 
				
			||||||
              ${{ matrix.package }}-libwebp \
 | 
					              ${{ matrix.package }}-libwebp \
 | 
				
			||||||
              ${{ matrix.package }}-openjpeg2 \
 | 
					              ${{ matrix.package }}-openjpeg2 \
 | 
				
			||||||
              subversion
 | 
					              ${{ matrix.package }}-python3-cffi \
 | 
				
			||||||
 | 
					              ${{ matrix.package }}-python3-numpy \
 | 
				
			||||||
 | 
					              ${{ matrix.package }}-python3-olefile \
 | 
				
			||||||
 | 
					              ${{ matrix.package }}-python3-pip \
 | 
				
			||||||
 | 
					              ${{ matrix.package }}-python3-setuptools
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          if [ ${{ matrix.package }} == "mingw-w64-x86_64" ]; then
 | 
				
			||||||
 | 
					              pacman -S --noconfirm \
 | 
				
			||||||
 | 
					                  ${{ matrix.package }}-python-pyqt6
 | 
				
			||||||
 | 
					          fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          python3 -m pip install pyroma pytest pytest-cov pytest-timeout
 | 
					          python3 -m pip install pyroma pytest pytest-cov pytest-timeout
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										2
									
								
								.github/workflows/test-valgrind.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/test-valgrind.yml
									
									
									
									
										vendored
									
									
								
							| 
						 | 
					@ -48,5 +48,5 @@ jobs:
 | 
				
			||||||
      run: |
 | 
					      run: |
 | 
				
			||||||
        # The Pillow user in the docker container is UID 1000
 | 
					        # The Pillow user in the docker container is UID 1000
 | 
				
			||||||
        sudo chown -R 1000 $GITHUB_WORKSPACE
 | 
					        sudo chown -R 1000 $GITHUB_WORKSPACE
 | 
				
			||||||
        docker run --name pillow_container  -v $GITHUB_WORKSPACE:/Pillow pythonpillow/${{ matrix.docker }}:${{ matrix.dockerTag }}
 | 
					        docker run --name pillow_container -e "PILLOW_VALGRIND_TEST=true" -v $GITHUB_WORKSPACE:/Pillow pythonpillow/${{ matrix.docker }}:${{ matrix.dockerTag }}
 | 
				
			||||||
        sudo chown -R runner $GITHUB_WORKSPACE
 | 
					        sudo chown -R runner $GITHUB_WORKSPACE
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										11
									
								
								.github/workflows/test-windows.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										11
									
								
								.github/workflows/test-windows.yml
									
									
									
									
										vendored
									
									
								
							| 
						 | 
					@ -15,7 +15,7 @@ jobs:
 | 
				
			||||||
    strategy:
 | 
					    strategy:
 | 
				
			||||||
      fail-fast: false
 | 
					      fail-fast: false
 | 
				
			||||||
      matrix:
 | 
					      matrix:
 | 
				
			||||||
        python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
 | 
					        python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12-dev"]
 | 
				
			||||||
        architecture: ["x86", "x64"]
 | 
					        architecture: ["x86", "x64"]
 | 
				
			||||||
        include:
 | 
					        include:
 | 
				
			||||||
          # PyPy 7.3.4+ only ships 64-bit binaries for Windows
 | 
					          # PyPy 7.3.4+ only ships 64-bit binaries for Windows
 | 
				
			||||||
| 
						 | 
					@ -38,6 +38,12 @@ jobs:
 | 
				
			||||||
        repository: python-pillow/pillow-depends
 | 
					        repository: python-pillow/pillow-depends
 | 
				
			||||||
        path: winbuild\depends
 | 
					        path: winbuild\depends
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    - name: Checkout extra test images
 | 
				
			||||||
 | 
					      uses: actions/checkout@v3
 | 
				
			||||||
 | 
					      with:
 | 
				
			||||||
 | 
					        repository: python-pillow/test-images
 | 
				
			||||||
 | 
					        path: Tests\test-images
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # sets env: pythonLocation
 | 
					    # sets env: pythonLocation
 | 
				
			||||||
    - name: Set up Python
 | 
					    - name: Set up Python
 | 
				
			||||||
      uses: actions/setup-python@v4
 | 
					      uses: actions/setup-python@v4
 | 
				
			||||||
| 
						 | 
					@ -62,7 +68,8 @@ jobs:
 | 
				
			||||||
        winbuild\depends\gs1000w32.exe /S
 | 
					        winbuild\depends\gs1000w32.exe /S
 | 
				
			||||||
        echo "C:\Program Files (x86)\gs\gs10.0.0\bin" >> $env:GITHUB_PATH
 | 
					        echo "C:\Program Files (x86)\gs\gs10.0.0\bin" >> $env:GITHUB_PATH
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        xcopy /S /Y winbuild\depends\test_images\* Tests\images\
 | 
					        # Install extra test images
 | 
				
			||||||
 | 
					        xcopy /S /Y Tests\test-images\* Tests\images
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # make cache key depend on VS version
 | 
					        # make cache key depend on VS version
 | 
				
			||||||
        & "C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" `
 | 
					        & "C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" `
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										3
									
								
								.github/workflows/test.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.github/workflows/test.yml
									
									
									
									
										vendored
									
									
								
							| 
						 | 
					@ -22,6 +22,7 @@ jobs:
 | 
				
			||||||
        python-version: [
 | 
					        python-version: [
 | 
				
			||||||
          "pypy3.9",
 | 
					          "pypy3.9",
 | 
				
			||||||
          "pypy3.8",
 | 
					          "pypy3.8",
 | 
				
			||||||
 | 
					          "3.12-dev",
 | 
				
			||||||
          "3.11",
 | 
					          "3.11",
 | 
				
			||||||
          "3.10",
 | 
					          "3.10",
 | 
				
			||||||
          "3.9",
 | 
					          "3.9",
 | 
				
			||||||
| 
						 | 
					@ -107,9 +108,9 @@ jobs:
 | 
				
			||||||
    - name: Upload coverage
 | 
					    - name: Upload coverage
 | 
				
			||||||
      uses: codecov/codecov-action@v3
 | 
					      uses: codecov/codecov-action@v3
 | 
				
			||||||
      with:
 | 
					      with:
 | 
				
			||||||
        file: ./coverage.xml
 | 
					 | 
				
			||||||
        flags: ${{ matrix.os == 'macos-latest' && 'GHA_macOS' || 'GHA_Ubuntu' }}
 | 
					        flags: ${{ matrix.os == 'macos-latest' && 'GHA_macOS' || 'GHA_Ubuntu' }}
 | 
				
			||||||
        name: ${{ matrix.os }} Python ${{ matrix.python-version }}
 | 
					        name: ${{ matrix.os }} Python ${{ matrix.python-version }}
 | 
				
			||||||
 | 
					        gcov: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  success:
 | 
					  success:
 | 
				
			||||||
    permissions:
 | 
					    permissions:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| 
						 | 
					@ -79,7 +79,7 @@ docs/_build/
 | 
				
			||||||
# JetBrains
 | 
					# JetBrains
 | 
				
			||||||
.idea
 | 
					.idea
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Extra test images installed from pillow-depends/test_images
 | 
					# Extra test images installed from python-pillow/test-images
 | 
				
			||||||
Tests/images/README.md
 | 
					Tests/images/README.md
 | 
				
			||||||
Tests/images/crash_1.tif
 | 
					Tests/images/crash_1.tif
 | 
				
			||||||
Tests/images/crash_2.tif
 | 
					Tests/images/crash_2.tif
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
repos:
 | 
					repos:
 | 
				
			||||||
  - repo: https://github.com/psf/black
 | 
					  - repo: https://github.com/psf/black
 | 
				
			||||||
    rev: 22.12.0
 | 
					    rev: 23.1.0
 | 
				
			||||||
    hooks:
 | 
					    hooks:
 | 
				
			||||||
      - id: black
 | 
					      - id: black
 | 
				
			||||||
        args: [--target-version=py37]
 | 
					        args: [--target-version=py37]
 | 
				
			||||||
| 
						 | 
					@ -9,7 +9,7 @@ repos:
 | 
				
			||||||
        types: []
 | 
					        types: []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - repo: https://github.com/PyCQA/isort
 | 
					  - repo: https://github.com/PyCQA/isort
 | 
				
			||||||
    rev: 5.11.1
 | 
					    rev: 5.12.0
 | 
				
			||||||
    hooks:
 | 
					    hooks:
 | 
				
			||||||
      - id: isort
 | 
					      - id: isort
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -26,7 +26,7 @@ repos:
 | 
				
			||||||
      - id: yesqa
 | 
					      - id: yesqa
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - repo: https://github.com/Lucas-C/pre-commit-hooks
 | 
					  - repo: https://github.com/Lucas-C/pre-commit-hooks
 | 
				
			||||||
    rev: v1.3.1
 | 
					    rev: v1.4.2
 | 
				
			||||||
    hooks:
 | 
					    hooks:
 | 
				
			||||||
      - id: remove-tabs
 | 
					      - id: remove-tabs
 | 
				
			||||||
        exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.opt$)
 | 
					        exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.opt$)
 | 
				
			||||||
| 
						 | 
					@ -39,7 +39,7 @@ repos:
 | 
				
			||||||
          [flake8-2020, flake8-errmsg, flake8-implicit-str-concat]
 | 
					          [flake8-2020, flake8-errmsg, flake8-implicit-str-concat]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - repo: https://github.com/pre-commit/pygrep-hooks
 | 
					  - repo: https://github.com/pre-commit/pygrep-hooks
 | 
				
			||||||
    rev: v1.9.0
 | 
					    rev: v1.10.0
 | 
				
			||||||
    hooks:
 | 
					    hooks:
 | 
				
			||||||
      - id: python-check-blanket-noqa
 | 
					      - id: python-check-blanket-noqa
 | 
				
			||||||
      - id: rst-backticks
 | 
					      - id: rst-backticks
 | 
				
			||||||
| 
						 | 
					@ -57,7 +57,7 @@ repos:
 | 
				
			||||||
      - id: sphinx-lint
 | 
					      - id: sphinx-lint
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - repo: https://github.com/tox-dev/tox-ini-fmt
 | 
					  - repo: https://github.com/tox-dev/tox-ini-fmt
 | 
				
			||||||
    rev: 0.5.2
 | 
					    rev: 0.6.1
 | 
				
			||||||
    hooks:
 | 
					    hooks:
 | 
				
			||||||
      - id: tox-ini-fmt
 | 
					      - id: tox-ini-fmt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										53
									
								
								CHANGES.rst
									
									
									
									
									
								
							
							
						
						
									
										53
									
								
								CHANGES.rst
									
									
									
									
									
								
							| 
						 | 
					@ -2,9 +2,60 @@
 | 
				
			||||||
Changelog (Pillow)
 | 
					Changelog (Pillow)
 | 
				
			||||||
==================
 | 
					==================
 | 
				
			||||||
 | 
					
 | 
				
			||||||
9.4.0 (unreleased)
 | 
					9.5.0 (unreleased)
 | 
				
			||||||
------------------
 | 
					------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Added memoryview support to frombytes() #6974
 | 
				
			||||||
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Allow comments in FITS images #6973
 | 
				
			||||||
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Support saving PDF with different X and Y resolutions #6961
 | 
				
			||||||
 | 
					  [jvanderneutstulen, radarhere, hugovk]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Fixed writing int as UNDEFINED tag #6950
 | 
				
			||||||
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Raise an error if EXIF data is too long when saving JPEG #6939
 | 
				
			||||||
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Handle more than one directory returned by pkg-config #6896
 | 
				
			||||||
 | 
					  [sebastic, radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Do not retry past formats when loading all formats for the first time #6902
 | 
				
			||||||
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Do not retry specified formats if they failed when opening #6893
 | 
				
			||||||
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Do not unintentionally load TIFF format at first #6892
 | 
				
			||||||
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Stop reading when EPS line becomes too long #6897
 | 
				
			||||||
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Allow writing IFDRational to BYTE tag #6890
 | 
				
			||||||
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Raise ValueError for BoxBlur filter with negative radius #6874
 | 
				
			||||||
 | 
					  [hugovk, radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Support arbitrary number of loaded modules on Windows #6761
 | 
				
			||||||
 | 
					  [javidcf, radarhere, nulano]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					9.4.0 (2023-01-02)
 | 
				
			||||||
 | 
					------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Fixed null pointer dereference crash with malformed font #6846
 | 
				
			||||||
 | 
					  [wiredfool, radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Return from ImagingFill early if image has a zero dimension #6842
 | 
				
			||||||
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Reversed deprecations for Image constants, except for duplicate Resampling attributes #6830
 | 
				
			||||||
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Improve exception traceback readability #6836
 | 
					- Improve exception traceback readability #6836
 | 
				
			||||||
  [hugovk, radarhere]
 | 
					  [hugovk, radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										6
									
								
								LICENSE
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								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-2022 by Alex Clark and contributors
 | 
					    Copyright © 2010-2023 by Jeffrey A. Clark (Alex) and contributors.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Like PIL, Pillow is licensed under the open source HPND License:
 | 
					Like PIL, Pillow is licensed under the open source HPND License:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,8 +13,8 @@ By obtaining, using, and/or copying this software and/or its associated
 | 
				
			||||||
documentation, you agree that you have read, understood, and will comply
 | 
					documentation, you agree that you have read, understood, and will comply
 | 
				
			||||||
with the following terms and conditions:
 | 
					with the following terms and conditions:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Permission to use, copy, modify, and distribute this software and its
 | 
					Permission to use, copy, modify and distribute this software and its
 | 
				
			||||||
associated documentation for any purpose and without fee is hereby granted,
 | 
					documentation for any purpose and without fee is hereby granted,
 | 
				
			||||||
provided that the above copyright notice appears in all copies, and that
 | 
					provided that the above copyright notice appears in all copies, and that
 | 
				
			||||||
both that copyright notice and this permission notice appear in supporting
 | 
					both that copyright notice and this permission notice appear in supporting
 | 
				
			||||||
documentation, and that the name of Secret Labs AB or the author not be
 | 
					documentation, and that the name of Secret Labs AB or the author not be
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,8 +6,8 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Python Imaging Library (Fork)
 | 
					## Python Imaging Library (Fork)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Pillow is the friendly PIL fork by [Alex Clark and
 | 
					Pillow is the friendly PIL fork by [Jeffrey A. Clark (Alex) and
 | 
				
			||||||
Contributors](https://github.com/python-pillow/Pillow/graphs/contributors).
 | 
					contributors](https://github.com/python-pillow/Pillow/graphs/contributors).
 | 
				
			||||||
PIL is the Python Imaging Library by Fredrik Lundh and Contributors.
 | 
					PIL is the Python Imaging Library by Fredrik Lundh and Contributors.
 | 
				
			||||||
As of 2019, Pillow development is
 | 
					As of 2019, Pillow development is
 | 
				
			||||||
[supported by Tidelift](https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=readme&utm_campaign=enterprise).
 | 
					[supported by Tidelift](https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=readme&utm_campaign=enterprise).
 | 
				
			||||||
| 
						 | 
					@ -88,6 +88,10 @@ As of 2019, Pillow development is
 | 
				
			||||||
            <a href="https://twitter.com/PythonPillow"><img
 | 
					            <a href="https://twitter.com/PythonPillow"><img
 | 
				
			||||||
                alt="Follow on https://twitter.com/PythonPillow"
 | 
					                alt="Follow on https://twitter.com/PythonPillow"
 | 
				
			||||||
                src="https://img.shields.io/badge/tweet-on%20Twitter-00aced.svg"></a>
 | 
					                src="https://img.shields.io/badge/tweet-on%20Twitter-00aced.svg"></a>
 | 
				
			||||||
 | 
					            <a href="https://fosstodon.org/@pillow"><img
 | 
				
			||||||
 | 
					                alt="Follow on https://fosstodon.org/@pillow"
 | 
				
			||||||
 | 
					                src="https://img.shields.io/badge/publish-on%20Mastodon-595aff.svg"
 | 
				
			||||||
 | 
					                rel="me"></a>
 | 
				
			||||||
        </td>
 | 
					        </td>
 | 
				
			||||||
    </tr>
 | 
					    </tr>
 | 
				
			||||||
</table>
 | 
					</table>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -111,7 +111,7 @@ Released as needed privately to individual vendors for critical security-related
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Publicize Release
 | 
					## Publicize Release
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* [ ] Announce release availability via [Twitter](https://twitter.com/pythonpillow) e.g. https://twitter.com/PythonPillow/status/1013789184354603010
 | 
					* [ ] Announce release availability via [Twitter](https://twitter.com/pythonpillow) and [Mastodon](https://fosstodon.org/@pillow) e.g. https://twitter.com/PythonPillow/status/1013789184354603010
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Documentation
 | 
					## Documentation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,7 +4,6 @@ TEST_FILE = "Tests/images/fli_overflow.fli"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_fli_overflow():
 | 
					def test_fli_overflow():
 | 
				
			||||||
 | 
					 | 
				
			||||||
    # this should not crash with a malloc error or access violation
 | 
					    # this should not crash with a malloc error or access violation
 | 
				
			||||||
    with Image.open(TEST_FILE) as im:
 | 
					    with Image.open(TEST_FILE) as im:
 | 
				
			||||||
        im.load()
 | 
					        im.load()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -23,7 +23,6 @@ def test_ignore_dos_text():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_dos_text():
 | 
					def test_dos_text():
 | 
				
			||||||
 | 
					 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        im = Image.open(TEST_FILE)
 | 
					        im = Image.open(TEST_FILE)
 | 
				
			||||||
        im.load()
 | 
					        im.load()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										10
									
								
								Tests/fonts/fuzz_font-5203009437302784
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								Tests/fonts/fuzz_font-5203009437302784
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,10 @@
 | 
				
			||||||
 | 
					STARTFONT
 | 
				
			||||||
 | 
					FONT ÿ
 | 
				
			||||||
 | 
					SIZE 10
 | 
				
			||||||
 | 
					FONTBOUNDINGBOX
 | 
				
			||||||
 | 
					CHARS
 | 
				
			||||||
 | 
					STARTCHAR 
 | 
				
			||||||
 | 
					ENCODING
 | 
				
			||||||
 | 
					BBX 2 5
 | 
				
			||||||
 | 
					ENDCHAR
 | 
				
			||||||
 | 
					ENDFONT
 | 
				
			||||||
| 
						 | 
					@ -57,6 +57,6 @@ def test_fuzz_fonts(path):
 | 
				
			||||||
    with open(path, "rb") as f:
 | 
					    with open(path, "rb") as f:
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            fuzzers.fuzz_font(f.read())
 | 
					            fuzzers.fuzz_font(f.read())
 | 
				
			||||||
        except (Image.DecompressionBombError, Image.DecompressionBombWarning):
 | 
					        except (Image.DecompressionBombError, Image.DecompressionBombWarning, OSError):
 | 
				
			||||||
            pass
 | 
					            pass
 | 
				
			||||||
        assert True
 | 
					        assert True
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -18,7 +18,6 @@ def test_bad():
 | 
				
			||||||
    """These shouldn't crash/dos, but they shouldn't return anything
 | 
					    """These shouldn't crash/dos, but they shouldn't return anything
 | 
				
			||||||
    either"""
 | 
					    either"""
 | 
				
			||||||
    for f in get_files("b"):
 | 
					    for f in get_files("b"):
 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Assert that there is no unclosed file warning
 | 
					        # Assert that there is no unclosed file warning
 | 
				
			||||||
        with warnings.catch_warnings():
 | 
					        with warnings.catch_warnings():
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,6 +11,11 @@ from PIL import _deprecate
 | 
				
			||||||
            "Old thing is deprecated and will be removed in Pillow 10 "
 | 
					            "Old thing is deprecated and will be removed in Pillow 10 "
 | 
				
			||||||
            r"\(2023-07-01\)\. Use new thing instead\.",
 | 
					            r"\(2023-07-01\)\. Use new thing instead\.",
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
 | 
					        (
 | 
				
			||||||
 | 
					            11,
 | 
				
			||||||
 | 
					            "Old thing is deprecated and will be removed in Pillow 11 "
 | 
				
			||||||
 | 
					            r"\(2024-10-15\)\. Use new thing instead\.",
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
        (
 | 
					        (
 | 
				
			||||||
            None,
 | 
					            None,
 | 
				
			||||||
            r"Old thing is deprecated and will be removed in a future version\. "
 | 
					            r"Old thing is deprecated and will be removed in a future version\. "
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -141,7 +141,6 @@ def test_rgba_bitfields():
 | 
				
			||||||
    # This test image has been manually hexedited
 | 
					    # This test image has been manually hexedited
 | 
				
			||||||
    # to change the bitfield compression in the header from XBGR to RGBA
 | 
					    # to change the bitfield compression in the header from XBGR to RGBA
 | 
				
			||||||
    with Image.open("Tests/images/rgb32bf-rgba.bmp") as im:
 | 
					    with Image.open("Tests/images/rgb32bf-rgba.bmp") as im:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        # So before the comparing the image, swap the channels
 | 
					        # So before the comparing the image, swap the channels
 | 
				
			||||||
        b, g, r = im.split()[1:]
 | 
					        b, g, r = im.split()[1:]
 | 
				
			||||||
        im = Image.merge("RGB", (r, g, b))
 | 
					        im = Image.merge("RGB", (r, g, b))
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,7 +10,6 @@ TEST_FILE = "Tests/images/gfs.t06z.rassda.tm00.bufr_d"
 | 
				
			||||||
def test_open():
 | 
					def test_open():
 | 
				
			||||||
    # Act
 | 
					    # Act
 | 
				
			||||||
    with Image.open(TEST_FILE) as im:
 | 
					    with Image.open(TEST_FILE) as im:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Assert
 | 
					        # Assert
 | 
				
			||||||
        assert im.format == "BUFR"
 | 
					        assert im.format == "BUFR"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -31,7 +30,6 @@ def test_invalid_file():
 | 
				
			||||||
def test_load():
 | 
					def test_load():
 | 
				
			||||||
    # Arrange
 | 
					    # Arrange
 | 
				
			||||||
    with Image.open(TEST_FILE) as im:
 | 
					    with Image.open(TEST_FILE) as im:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Act / Assert: stub cannot load without an implemented handler
 | 
					        # Act / Assert: stub cannot load without an implemented handler
 | 
				
			||||||
        with pytest.raises(OSError):
 | 
					        with pytest.raises(OSError):
 | 
				
			||||||
            im.load()
 | 
					            im.load()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,7 +15,6 @@ def test_sanity():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Act
 | 
					    # Act
 | 
				
			||||||
    with Image.open(TEST_FILE) as im:
 | 
					    with Image.open(TEST_FILE) as im:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Assert
 | 
					        # Assert
 | 
				
			||||||
        assert im.size == (128, 128)
 | 
					        assert im.size == (128, 128)
 | 
				
			||||||
        assert isinstance(im, DcxImagePlugin.DcxImageFile)
 | 
					        assert isinstance(im, DcxImagePlugin.DcxImageFile)
 | 
				
			||||||
| 
						 | 
					@ -54,7 +53,6 @@ def test_invalid_file():
 | 
				
			||||||
def test_tell():
 | 
					def test_tell():
 | 
				
			||||||
    # Arrange
 | 
					    # Arrange
 | 
				
			||||||
    with Image.open(TEST_FILE) as im:
 | 
					    with Image.open(TEST_FILE) as im:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Act
 | 
					        # Act
 | 
				
			||||||
        frame = im.tell()
 | 
					        frame = im.tell()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -80,7 +80,6 @@ def test_invalid_file():
 | 
				
			||||||
@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")
 | 
					@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")
 | 
				
			||||||
def test_cmyk():
 | 
					def test_cmyk():
 | 
				
			||||||
    with Image.open("Tests/images/pil_sample_cmyk.eps") as cmyk_image:
 | 
					    with Image.open("Tests/images/pil_sample_cmyk.eps") as cmyk_image:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        assert cmyk_image.mode == "CMYK"
 | 
					        assert cmyk_image.mode == "CMYK"
 | 
				
			||||||
        assert cmyk_image.size == (100, 100)
 | 
					        assert cmyk_image.size == (100, 100)
 | 
				
			||||||
        assert cmyk_image.format == "EPS"
 | 
					        assert cmyk_image.format == "EPS"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,7 +12,6 @@ TEST_FILE = "Tests/images/hopper.fits"
 | 
				
			||||||
def test_open():
 | 
					def test_open():
 | 
				
			||||||
    # Act
 | 
					    # Act
 | 
				
			||||||
    with Image.open(TEST_FILE) as im:
 | 
					    with Image.open(TEST_FILE) as im:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Assert
 | 
					        # Assert
 | 
				
			||||||
        assert im.format == "FITS"
 | 
					        assert im.format == "FITS"
 | 
				
			||||||
        assert im.size == (128, 128)
 | 
					        assert im.size == (128, 128)
 | 
				
			||||||
| 
						 | 
					@ -45,6 +44,12 @@ def test_naxis_zero():
 | 
				
			||||||
            pass
 | 
					            pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_comment():
 | 
				
			||||||
 | 
					    image_data = b"SIMPLE  =                    T / comment string"
 | 
				
			||||||
 | 
					    with pytest.raises(OSError):
 | 
				
			||||||
 | 
					        FitsImagePlugin.FitsImageFile(BytesIO(image_data))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_stub_deprecated():
 | 
					def test_stub_deprecated():
 | 
				
			||||||
    class Handler:
 | 
					    class Handler:
 | 
				
			||||||
        opened = False
 | 
					        opened = False
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -64,7 +64,6 @@ def test_context_manager():
 | 
				
			||||||
def test_tell():
 | 
					def test_tell():
 | 
				
			||||||
    # Arrange
 | 
					    # Arrange
 | 
				
			||||||
    with Image.open(static_test_file) as im:
 | 
					    with Image.open(static_test_file) as im:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Act
 | 
					        # Act
 | 
				
			||||||
        frame = im.tell()
 | 
					        frame = im.tell()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -110,7 +109,6 @@ def test_eoferror():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_seek_tell():
 | 
					def test_seek_tell():
 | 
				
			||||||
    with Image.open(animated_test_file) as im:
 | 
					    with Image.open(animated_test_file) as im:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        layer_number = im.tell()
 | 
					        layer_number = im.tell()
 | 
				
			||||||
        assert layer_number == 0
 | 
					        assert layer_number == 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -158,39 +158,42 @@ def test_optimize():
 | 
				
			||||||
    assert test_bilevel(1) == 799
 | 
					    assert test_bilevel(1) == 799
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_optimize_correctness():
 | 
					@pytest.mark.parametrize(
 | 
				
			||||||
    # 256 color Palette image, posterize to > 128 and < 128 levels
 | 
					    "colors, size, expected_palette_length",
 | 
				
			||||||
    # Size bigger and smaller than 512x512
 | 
					    (
 | 
				
			||||||
 | 
					        # These do optimize the palette
 | 
				
			||||||
 | 
					        (256, 511, 256),
 | 
				
			||||||
 | 
					        (255, 511, 255),
 | 
				
			||||||
 | 
					        (129, 511, 129),
 | 
				
			||||||
 | 
					        (128, 511, 128),
 | 
				
			||||||
 | 
					        (64, 511, 64),
 | 
				
			||||||
 | 
					        (4, 511, 4),
 | 
				
			||||||
 | 
					        # These don't optimize the palette
 | 
				
			||||||
 | 
					        (128, 513, 256),
 | 
				
			||||||
 | 
					        (64, 513, 256),
 | 
				
			||||||
 | 
					        (4, 513, 256),
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					def test_optimize_correctness(colors, size, expected_palette_length):
 | 
				
			||||||
 | 
					    # 256 color Palette image, posterize to > 128 and < 128 levels.
 | 
				
			||||||
 | 
					    # Size bigger and smaller than 512x512.
 | 
				
			||||||
    # Check the palette for number of colors allocated.
 | 
					    # Check the palette for number of colors allocated.
 | 
				
			||||||
    # Check for correctness after conversion back to RGB
 | 
					    # Check for correctness after conversion back to RGB.
 | 
				
			||||||
    def check(colors, size, expected_palette_length):
 | 
					 | 
				
			||||||
        # make an image with empty colors in the start of the palette range
 | 
					 | 
				
			||||||
        im = Image.frombytes(
 | 
					 | 
				
			||||||
            "P", (colors, colors), bytes(range(256 - colors, 256)) * colors
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        im = im.resize((size, size))
 | 
					 | 
				
			||||||
        outfile = BytesIO()
 | 
					 | 
				
			||||||
        im.save(outfile, "GIF")
 | 
					 | 
				
			||||||
        outfile.seek(0)
 | 
					 | 
				
			||||||
        with Image.open(outfile) as reloaded:
 | 
					 | 
				
			||||||
            # check palette length
 | 
					 | 
				
			||||||
            palette_length = max(i + 1 for i, v in enumerate(reloaded.histogram()) if v)
 | 
					 | 
				
			||||||
            assert expected_palette_length == palette_length
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            assert_image_equal(im.convert("RGB"), reloaded.convert("RGB"))
 | 
					    # make an image with empty colors in the start of the palette range
 | 
				
			||||||
 | 
					    im = Image.frombytes(
 | 
				
			||||||
 | 
					        "P", (colors, colors), bytes(range(256 - colors, 256)) * colors
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    im = im.resize((size, size))
 | 
				
			||||||
 | 
					    outfile = BytesIO()
 | 
				
			||||||
 | 
					    im.save(outfile, "GIF")
 | 
				
			||||||
 | 
					    outfile.seek(0)
 | 
				
			||||||
 | 
					    with Image.open(outfile) as reloaded:
 | 
				
			||||||
 | 
					        # check palette length
 | 
				
			||||||
 | 
					        palette_length = max(i + 1 for i, v in enumerate(reloaded.histogram()) if v)
 | 
				
			||||||
 | 
					        assert expected_palette_length == palette_length
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # These do optimize the palette
 | 
					        assert_image_equal(im.convert("RGB"), reloaded.convert("RGB"))
 | 
				
			||||||
    check(256, 511, 256)
 | 
					 | 
				
			||||||
    check(255, 511, 255)
 | 
					 | 
				
			||||||
    check(129, 511, 129)
 | 
					 | 
				
			||||||
    check(128, 511, 128)
 | 
					 | 
				
			||||||
    check(64, 511, 64)
 | 
					 | 
				
			||||||
    check(4, 511, 4)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # These don't optimize the palette
 | 
					 | 
				
			||||||
    check(128, 513, 256)
 | 
					 | 
				
			||||||
    check(64, 513, 256)
 | 
					 | 
				
			||||||
    check(4, 513, 256)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_optimize_full_l():
 | 
					def test_optimize_full_l():
 | 
				
			||||||
| 
						 | 
					@ -206,7 +209,7 @@ def test_optimize_if_palette_can_be_reduced_by_half():
 | 
				
			||||||
        im = im.resize((591, 443))
 | 
					        im = im.resize((591, 443))
 | 
				
			||||||
    im_rgb = im.convert("RGB")
 | 
					    im_rgb = im.convert("RGB")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (optimize, colors) in ((False, 256), (True, 8)):
 | 
					    for optimize, colors in ((False, 256), (True, 8)):
 | 
				
			||||||
        out = BytesIO()
 | 
					        out = BytesIO()
 | 
				
			||||||
        im_rgb.save(out, "GIF", optimize=optimize)
 | 
					        im_rgb.save(out, "GIF", optimize=optimize)
 | 
				
			||||||
        with Image.open(out) as reloaded:
 | 
					        with Image.open(out) as reloaded:
 | 
				
			||||||
| 
						 | 
					@ -218,7 +221,6 @@ def test_roundtrip(tmp_path):
 | 
				
			||||||
    im = hopper()
 | 
					    im = hopper()
 | 
				
			||||||
    im.save(out)
 | 
					    im.save(out)
 | 
				
			||||||
    with Image.open(out) as reread:
 | 
					    with Image.open(out) as reread:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        assert_image_similar(reread.convert("RGB"), im, 50)
 | 
					        assert_image_similar(reread.convert("RGB"), im, 50)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -229,7 +231,6 @@ def test_roundtrip2(tmp_path):
 | 
				
			||||||
        im2 = im.copy()
 | 
					        im2 = im.copy()
 | 
				
			||||||
        im2.save(out)
 | 
					        im2.save(out)
 | 
				
			||||||
    with Image.open(out) as reread:
 | 
					    with Image.open(out) as reread:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        assert_image_similar(reread.convert("RGB"), hopper(), 50)
 | 
					        assert_image_similar(reread.convert("RGB"), hopper(), 50)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -239,7 +240,6 @@ def test_roundtrip_save_all(tmp_path):
 | 
				
			||||||
    im = hopper()
 | 
					    im = hopper()
 | 
				
			||||||
    im.save(out, save_all=True)
 | 
					    im.save(out, save_all=True)
 | 
				
			||||||
    with Image.open(out) as reread:
 | 
					    with Image.open(out) as reread:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        assert_image_similar(reread.convert("RGB"), im, 50)
 | 
					        assert_image_similar(reread.convert("RGB"), im, 50)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Multiframe image
 | 
					    # Multiframe image
 | 
				
			||||||
| 
						 | 
					@ -281,13 +281,11 @@ def test_headers_saving_for_animated_gifs(tmp_path):
 | 
				
			||||||
    important_headers = ["background", "version", "duration", "loop"]
 | 
					    important_headers = ["background", "version", "duration", "loop"]
 | 
				
			||||||
    # Multiframe image
 | 
					    # Multiframe image
 | 
				
			||||||
    with Image.open("Tests/images/dispose_bgnd.gif") as im:
 | 
					    with Image.open("Tests/images/dispose_bgnd.gif") as im:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        info = im.info.copy()
 | 
					        info = im.info.copy()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        out = str(tmp_path / "temp.gif")
 | 
					        out = str(tmp_path / "temp.gif")
 | 
				
			||||||
        im.save(out, save_all=True)
 | 
					        im.save(out, save_all=True)
 | 
				
			||||||
    with Image.open(out) as reread:
 | 
					    with Image.open(out) as reread:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        for header in important_headers:
 | 
					        for header in important_headers:
 | 
				
			||||||
            assert info[header] == reread.info[header]
 | 
					            assert info[header] == reread.info[header]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -305,7 +303,6 @@ def test_palette_handling(tmp_path):
 | 
				
			||||||
        im2.save(f, optimize=True)
 | 
					        im2.save(f, optimize=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open(f) as reloaded:
 | 
					    with Image.open(f) as reloaded:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        assert_image_similar(im, reloaded.convert("RGB"), 10)
 | 
					        assert_image_similar(im, reloaded.convert("RGB"), 10)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -321,7 +318,6 @@ def test_palette_434(tmp_path):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    orig = "Tests/images/test.colors.gif"
 | 
					    orig = "Tests/images/test.colors.gif"
 | 
				
			||||||
    with Image.open(orig) as im:
 | 
					    with Image.open(orig) as im:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        with roundtrip(im) as reloaded:
 | 
					        with roundtrip(im) as reloaded:
 | 
				
			||||||
            assert_image_similar(im, reloaded, 1)
 | 
					            assert_image_similar(im, reloaded, 1)
 | 
				
			||||||
        with roundtrip(im, optimize=True) as reloaded:
 | 
					        with roundtrip(im, optimize=True) as reloaded:
 | 
				
			||||||
| 
						 | 
					@ -572,7 +568,6 @@ def test_save_dispose(tmp_path):
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open(out) as img:
 | 
					    with Image.open(out) as img:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        for i in range(2):
 | 
					        for i in range(2):
 | 
				
			||||||
            img.seek(img.tell() + 1)
 | 
					            img.seek(img.tell() + 1)
 | 
				
			||||||
            assert img.disposal_method == i + 1
 | 
					            assert img.disposal_method == i + 1
 | 
				
			||||||
| 
						 | 
					@ -770,7 +765,6 @@ def test_multiple_duration(tmp_path):
 | 
				
			||||||
        out, save_all=True, append_images=im_list[1:], duration=duration_list
 | 
					        out, save_all=True, append_images=im_list[1:], duration=duration_list
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    with Image.open(out) as reread:
 | 
					    with Image.open(out) as reread:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        for duration in duration_list:
 | 
					        for duration in duration_list:
 | 
				
			||||||
            assert reread.info["duration"] == duration
 | 
					            assert reread.info["duration"] == duration
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
| 
						 | 
					@ -783,7 +777,6 @@ def test_multiple_duration(tmp_path):
 | 
				
			||||||
        out, save_all=True, append_images=im_list[1:], duration=tuple(duration_list)
 | 
					        out, save_all=True, append_images=im_list[1:], duration=tuple(duration_list)
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    with Image.open(out) as reread:
 | 
					    with Image.open(out) as reread:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        for duration in duration_list:
 | 
					        for duration in duration_list:
 | 
				
			||||||
            assert reread.info["duration"] == duration
 | 
					            assert reread.info["duration"] == duration
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
| 
						 | 
					@ -841,7 +834,6 @@ def test_identical_frames(tmp_path):
 | 
				
			||||||
        out, save_all=True, append_images=im_list[1:], duration=duration_list
 | 
					        out, save_all=True, append_images=im_list[1:], duration=duration_list
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    with Image.open(out) as reread:
 | 
					    with Image.open(out) as reread:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Assert that the first three frames were combined
 | 
					        # Assert that the first three frames were combined
 | 
				
			||||||
        assert reread.n_frames == 2
 | 
					        assert reread.n_frames == 2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,7 +10,6 @@ TEST_FILE = "Tests/images/WAlaska.wind.7days.grb"
 | 
				
			||||||
def test_open():
 | 
					def test_open():
 | 
				
			||||||
    # Act
 | 
					    # Act
 | 
				
			||||||
    with Image.open(TEST_FILE) as im:
 | 
					    with Image.open(TEST_FILE) as im:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Assert
 | 
					        # Assert
 | 
				
			||||||
        assert im.format == "GRIB"
 | 
					        assert im.format == "GRIB"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -31,7 +30,6 @@ def test_invalid_file():
 | 
				
			||||||
def test_load():
 | 
					def test_load():
 | 
				
			||||||
    # Arrange
 | 
					    # Arrange
 | 
				
			||||||
    with Image.open(TEST_FILE) as im:
 | 
					    with Image.open(TEST_FILE) as im:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Act / Assert: stub cannot load without an implemented handler
 | 
					        # Act / Assert: stub cannot load without an implemented handler
 | 
				
			||||||
        with pytest.raises(OSError):
 | 
					        with pytest.raises(OSError):
 | 
				
			||||||
            im.load()
 | 
					            im.load()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,7 +8,6 @@ TEST_FILE = "Tests/images/hdf5.h5"
 | 
				
			||||||
def test_open():
 | 
					def test_open():
 | 
				
			||||||
    # Act
 | 
					    # Act
 | 
				
			||||||
    with Image.open(TEST_FILE) as im:
 | 
					    with Image.open(TEST_FILE) as im:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Assert
 | 
					        # Assert
 | 
				
			||||||
        assert im.format == "HDF5"
 | 
					        assert im.format == "HDF5"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -29,7 +28,6 @@ def test_invalid_file():
 | 
				
			||||||
def test_load():
 | 
					def test_load():
 | 
				
			||||||
    # Arrange
 | 
					    # Arrange
 | 
				
			||||||
    with Image.open(TEST_FILE) as im:
 | 
					    with Image.open(TEST_FILE) as im:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Act / Assert: stub cannot load without an implemented handler
 | 
					        # Act / Assert: stub cannot load without an implemented handler
 | 
				
			||||||
        with pytest.raises(OSError):
 | 
					        with pytest.raises(OSError):
 | 
				
			||||||
            im.load()
 | 
					            im.load()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -16,7 +16,6 @@ def test_sanity():
 | 
				
			||||||
    # Loading this icon by default should result in the largest size
 | 
					    # Loading this icon by default should result in the largest size
 | 
				
			||||||
    # (512x512@2x) being loaded
 | 
					    # (512x512@2x) being loaded
 | 
				
			||||||
    with Image.open(TEST_FILE) as im:
 | 
					    with Image.open(TEST_FILE) as im:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Assert that there is no unclosed file warning
 | 
					        # Assert that there is no unclosed file warning
 | 
				
			||||||
        with warnings.catch_warnings():
 | 
					        with warnings.catch_warnings():
 | 
				
			||||||
            im.load()
 | 
					            im.load()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -175,7 +175,6 @@ def test_save_256x256(tmp_path):
 | 
				
			||||||
        # Act
 | 
					        # Act
 | 
				
			||||||
        im.save(outfile)
 | 
					        im.save(outfile)
 | 
				
			||||||
    with Image.open(outfile) as im_saved:
 | 
					    with Image.open(outfile) as im_saved:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Assert
 | 
					        # Assert
 | 
				
			||||||
        assert im_saved.size == (256, 256)
 | 
					        assert im_saved.size == (256, 256)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -51,7 +51,6 @@ def test_context_manager():
 | 
				
			||||||
def test_tell():
 | 
					def test_tell():
 | 
				
			||||||
    # Arrange
 | 
					    # Arrange
 | 
				
			||||||
    with Image.open(TEST_IM) as im:
 | 
					    with Image.open(TEST_IM) as im:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Act
 | 
					        # Act
 | 
				
			||||||
        frame = im.tell()
 | 
					        frame = im.tell()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,7 +11,6 @@ TEST_FILE = "Tests/images/iptc.jpg"
 | 
				
			||||||
def test_getiptcinfo_jpg_none():
 | 
					def test_getiptcinfo_jpg_none():
 | 
				
			||||||
    # Arrange
 | 
					    # Arrange
 | 
				
			||||||
    with hopper() as im:
 | 
					    with hopper() as im:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Act
 | 
					        # Act
 | 
				
			||||||
        iptc = IptcImagePlugin.getiptcinfo(im)
 | 
					        iptc = IptcImagePlugin.getiptcinfo(im)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -22,7 +21,6 @@ def test_getiptcinfo_jpg_none():
 | 
				
			||||||
def test_getiptcinfo_jpg_found():
 | 
					def test_getiptcinfo_jpg_found():
 | 
				
			||||||
    # Arrange
 | 
					    # Arrange
 | 
				
			||||||
    with Image.open(TEST_FILE) as im:
 | 
					    with Image.open(TEST_FILE) as im:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Act
 | 
					        # Act
 | 
				
			||||||
        iptc = IptcImagePlugin.getiptcinfo(im)
 | 
					        iptc = IptcImagePlugin.getiptcinfo(im)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -35,7 +33,6 @@ def test_getiptcinfo_jpg_found():
 | 
				
			||||||
def test_getiptcinfo_tiff_none():
 | 
					def test_getiptcinfo_tiff_none():
 | 
				
			||||||
    # Arrange
 | 
					    # Arrange
 | 
				
			||||||
    with Image.open("Tests/images/hopper.tif") as im:
 | 
					    with Image.open("Tests/images/hopper.tif") as im:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Act
 | 
					        # Act
 | 
				
			||||||
        iptc = IptcImagePlugin.getiptcinfo(im)
 | 
					        iptc = IptcImagePlugin.getiptcinfo(im)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -57,7 +57,6 @@ class TestFileJpeg:
 | 
				
			||||||
        return Image.frombytes(mode, size, os.urandom(size[0] * size[1] * len(mode)))
 | 
					        return Image.frombytes(mode, size, os.urandom(size[0] * size[1] * len(mode)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_sanity(self):
 | 
					    def test_sanity(self):
 | 
				
			||||||
 | 
					 | 
				
			||||||
        # internal version number
 | 
					        # internal version number
 | 
				
			||||||
        assert re.search(r"\d+\.\d+$", features.version_codec("jpg"))
 | 
					        assert re.search(r"\d+\.\d+$", features.version_codec("jpg"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -271,7 +270,10 @@ class TestFileJpeg:
 | 
				
			||||||
        # https://github.com/python-pillow/Pillow/issues/148
 | 
					        # https://github.com/python-pillow/Pillow/issues/148
 | 
				
			||||||
        f = str(tmp_path / "temp.jpg")
 | 
					        f = str(tmp_path / "temp.jpg")
 | 
				
			||||||
        im = hopper()
 | 
					        im = hopper()
 | 
				
			||||||
        im.save(f, "JPEG", quality=90, exif=b"1" * 65532)
 | 
					        im.save(f, "JPEG", quality=90, exif=b"1" * 65533)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        with pytest.raises(ValueError):
 | 
				
			||||||
 | 
					            im.save(f, "JPEG", quality=90, exif=b"1" * 65534)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_exif_typeerror(self):
 | 
					    def test_exif_typeerror(self):
 | 
				
			||||||
        with Image.open("Tests/images/exif_typeerror.jpg") as im:
 | 
					        with Image.open("Tests/images/exif_typeerror.jpg") as im:
 | 
				
			||||||
| 
						 | 
					@ -368,7 +370,6 @@ class TestFileJpeg:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_exif_gps_typeerror(self):
 | 
					    def test_exif_gps_typeerror(self):
 | 
				
			||||||
        with Image.open("Tests/images/exif_gps_typeerror.jpg") as im:
 | 
					        with Image.open("Tests/images/exif_gps_typeerror.jpg") as im:
 | 
				
			||||||
 | 
					 | 
				
			||||||
            # Should not raise a TypeError
 | 
					            # Should not raise a TypeError
 | 
				
			||||||
            im._getexif()
 | 
					            im._getexif()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -447,7 +448,7 @@ class TestFileJpeg:
 | 
				
			||||||
            ims = im.get_child_images()
 | 
					            ims = im.get_child_images()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        assert len(ims) == 1
 | 
					        assert len(ims) == 1
 | 
				
			||||||
        assert_image_equal_tofile(ims[0], "Tests/images/flower_thumbnail.png")
 | 
					        assert_image_similar_tofile(ims[0], "Tests/images/flower_thumbnail.png", 2.1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_mp(self):
 | 
					    def test_mp(self):
 | 
				
			||||||
        with Image.open("Tests/images/pil_sample_rgb.jpg") as im:
 | 
					        with Image.open("Tests/images/pil_sample_rgb.jpg") as im:
 | 
				
			||||||
| 
						 | 
					@ -682,7 +683,6 @@ class TestFileJpeg:
 | 
				
			||||||
        # Shouldn't raise error
 | 
					        # Shouldn't raise error
 | 
				
			||||||
        fn = "Tests/images/sugarshack_bad_mpo_header.jpg"
 | 
					        fn = "Tests/images/sugarshack_bad_mpo_header.jpg"
 | 
				
			||||||
        with pytest.warns(UserWarning, Image.open, fn) as im:
 | 
					        with pytest.warns(UserWarning, Image.open, fn) as im:
 | 
				
			||||||
 | 
					 | 
				
			||||||
            # Assert
 | 
					            # Assert
 | 
				
			||||||
            assert im.format == "JPEG"
 | 
					            assert im.format == "JPEG"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -704,7 +704,6 @@ class TestFileJpeg:
 | 
				
			||||||
        # Arrange
 | 
					        # Arrange
 | 
				
			||||||
        outfile = str(tmp_path / "temp.tif")
 | 
					        outfile = str(tmp_path / "temp.tif")
 | 
				
			||||||
        with Image.open("Tests/images/hopper.tif") as im:
 | 
					        with Image.open("Tests/images/hopper.tif") as im:
 | 
				
			||||||
 | 
					 | 
				
			||||||
            # Act
 | 
					            # Act
 | 
				
			||||||
            im.save(outfile, "JPEG", dpi=im.info["dpi"])
 | 
					            im.save(outfile, "JPEG", dpi=im.info["dpi"])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -731,7 +730,6 @@ class TestFileJpeg:
 | 
				
			||||||
        # This Photoshop CC 2017 image has DPI in EXIF not metadata
 | 
					        # This Photoshop CC 2017 image has DPI in EXIF not metadata
 | 
				
			||||||
        # EXIF XResolution is (2000000, 10000)
 | 
					        # EXIF XResolution is (2000000, 10000)
 | 
				
			||||||
        with Image.open("Tests/images/photoshop-200dpi.jpg") as im:
 | 
					        with Image.open("Tests/images/photoshop-200dpi.jpg") as im:
 | 
				
			||||||
 | 
					 | 
				
			||||||
            # Act / Assert
 | 
					            # Act / Assert
 | 
				
			||||||
            assert im.info.get("dpi") == (200, 200)
 | 
					            assert im.info.get("dpi") == (200, 200)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -740,7 +738,6 @@ class TestFileJpeg:
 | 
				
			||||||
        # This image has DPI in EXIF not metadata
 | 
					        # This image has DPI in EXIF not metadata
 | 
				
			||||||
        # EXIF XResolution is 72
 | 
					        # EXIF XResolution is 72
 | 
				
			||||||
        with Image.open("Tests/images/exif-72dpi-int.jpg") as im:
 | 
					        with Image.open("Tests/images/exif-72dpi-int.jpg") as im:
 | 
				
			||||||
 | 
					 | 
				
			||||||
            # Act / Assert
 | 
					            # Act / Assert
 | 
				
			||||||
            assert im.info.get("dpi") == (72, 72)
 | 
					            assert im.info.get("dpi") == (72, 72)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -749,7 +746,6 @@ class TestFileJpeg:
 | 
				
			||||||
        # This is photoshop-200dpi.jpg with EXIF resolution unit set to cm:
 | 
					        # This is photoshop-200dpi.jpg with EXIF resolution unit set to cm:
 | 
				
			||||||
        # exiftool -exif:ResolutionUnit=cm photoshop-200dpi.jpg
 | 
					        # exiftool -exif:ResolutionUnit=cm photoshop-200dpi.jpg
 | 
				
			||||||
        with Image.open("Tests/images/exif-200dpcm.jpg") as im:
 | 
					        with Image.open("Tests/images/exif-200dpcm.jpg") as im:
 | 
				
			||||||
 | 
					 | 
				
			||||||
            # Act / Assert
 | 
					            # Act / Assert
 | 
				
			||||||
            assert im.info.get("dpi") == (508, 508)
 | 
					            assert im.info.get("dpi") == (508, 508)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -758,7 +754,6 @@ class TestFileJpeg:
 | 
				
			||||||
        # This is photoshop-200dpi.jpg with EXIF resolution set to 0/0:
 | 
					        # This is photoshop-200dpi.jpg with EXIF resolution set to 0/0:
 | 
				
			||||||
        # exiftool -XResolution=0/0 -YResolution=0/0 photoshop-200dpi.jpg
 | 
					        # exiftool -XResolution=0/0 -YResolution=0/0 photoshop-200dpi.jpg
 | 
				
			||||||
        with Image.open("Tests/images/exif-dpi-zerodivision.jpg") as im:
 | 
					        with Image.open("Tests/images/exif-dpi-zerodivision.jpg") as im:
 | 
				
			||||||
 | 
					 | 
				
			||||||
            # Act / Assert
 | 
					            # Act / Assert
 | 
				
			||||||
            # This should return the default, and not raise a ZeroDivisionError
 | 
					            # This should return the default, and not raise a ZeroDivisionError
 | 
				
			||||||
            assert im.info.get("dpi") == (72, 72)
 | 
					            assert im.info.get("dpi") == (72, 72)
 | 
				
			||||||
| 
						 | 
					@ -767,7 +762,6 @@ class TestFileJpeg:
 | 
				
			||||||
        # Arrange
 | 
					        # Arrange
 | 
				
			||||||
        # 0x011A tag in this exif contains string '300300\x02'
 | 
					        # 0x011A tag in this exif contains string '300300\x02'
 | 
				
			||||||
        with Image.open("Tests/images/broken_exif_dpi.jpg") as im:
 | 
					        with Image.open("Tests/images/broken_exif_dpi.jpg") as im:
 | 
				
			||||||
 | 
					 | 
				
			||||||
            # Act / Assert
 | 
					            # Act / Assert
 | 
				
			||||||
            # This should return the default
 | 
					            # This should return the default
 | 
				
			||||||
            assert im.info.get("dpi") == (72, 72)
 | 
					            assert im.info.get("dpi") == (72, 72)
 | 
				
			||||||
| 
						 | 
					@ -777,7 +771,6 @@ class TestFileJpeg:
 | 
				
			||||||
        # This is photoshop-200dpi.jpg with resolution removed from EXIF:
 | 
					        # This is photoshop-200dpi.jpg with resolution removed from EXIF:
 | 
				
			||||||
        # exiftool "-*resolution*"= photoshop-200dpi.jpg
 | 
					        # exiftool "-*resolution*"= photoshop-200dpi.jpg
 | 
				
			||||||
        with Image.open("Tests/images/no-dpi-in-exif.jpg") as im:
 | 
					        with Image.open("Tests/images/no-dpi-in-exif.jpg") as im:
 | 
				
			||||||
 | 
					 | 
				
			||||||
            # Act / Assert
 | 
					            # Act / Assert
 | 
				
			||||||
            # "When the image resolution is unknown, 72 [dpi] is designated."
 | 
					            # "When the image resolution is unknown, 72 [dpi] is designated."
 | 
				
			||||||
            # https://exiv2.org/tags.html
 | 
					            # https://exiv2.org/tags.html
 | 
				
			||||||
| 
						 | 
					@ -787,7 +780,6 @@ class TestFileJpeg:
 | 
				
			||||||
        # This is no-dpi-in-exif with the tiff header of the exif block
 | 
					        # This is no-dpi-in-exif with the tiff header of the exif block
 | 
				
			||||||
        # hexedited from MM * to FF FF FF FF
 | 
					        # hexedited from MM * to FF FF FF FF
 | 
				
			||||||
        with Image.open("Tests/images/invalid-exif.jpg") as im:
 | 
					        with Image.open("Tests/images/invalid-exif.jpg") as im:
 | 
				
			||||||
 | 
					 | 
				
			||||||
            # This should return the default, and not a SyntaxError or
 | 
					            # This should return the default, and not a SyntaxError or
 | 
				
			||||||
            # OSError for unidentified image.
 | 
					            # OSError for unidentified image.
 | 
				
			||||||
            assert im.info.get("dpi") == (72, 72)
 | 
					            assert im.info.get("dpi") == (72, 72)
 | 
				
			||||||
| 
						 | 
					@ -810,7 +802,6 @@ class TestFileJpeg:
 | 
				
			||||||
    def test_invalid_exif_x_resolution(self):
 | 
					    def test_invalid_exif_x_resolution(self):
 | 
				
			||||||
        # When no x or y resolution is defined in EXIF
 | 
					        # When no x or y resolution is defined in EXIF
 | 
				
			||||||
        with Image.open("Tests/images/invalid-exif-without-x-resolution.jpg") as im:
 | 
					        with Image.open("Tests/images/invalid-exif-without-x-resolution.jpg") as im:
 | 
				
			||||||
 | 
					 | 
				
			||||||
            # This should return the default, and not a ValueError or
 | 
					            # This should return the default, and not a ValueError or
 | 
				
			||||||
            # OSError for an unidentified image.
 | 
					            # OSError for an unidentified image.
 | 
				
			||||||
            assert im.info.get("dpi") == (72, 72)
 | 
					            assert im.info.get("dpi") == (72, 72)
 | 
				
			||||||
| 
						 | 
					@ -820,7 +811,6 @@ class TestFileJpeg:
 | 
				
			||||||
        # This image has been manually hexedited to have an IFD offset of 10,
 | 
					        # This image has been manually hexedited to have an IFD offset of 10,
 | 
				
			||||||
        # in contrast to normal 8
 | 
					        # in contrast to normal 8
 | 
				
			||||||
        with Image.open("Tests/images/exif-ifd-offset.jpg") as im:
 | 
					        with Image.open("Tests/images/exif-ifd-offset.jpg") as im:
 | 
				
			||||||
 | 
					 | 
				
			||||||
            # Act / Assert
 | 
					            # Act / Assert
 | 
				
			||||||
            assert im._getexif()[306] == "2017:03:13 23:03:09"
 | 
					            assert im._getexif()[306] == "2017:03:13 23:03:09"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -270,7 +270,6 @@ def test_rgba():
 | 
				
			||||||
    # Arrange
 | 
					    # Arrange
 | 
				
			||||||
    with Image.open("Tests/images/rgb_trns_ycbc.j2k") as j2k:
 | 
					    with Image.open("Tests/images/rgb_trns_ycbc.j2k") as j2k:
 | 
				
			||||||
        with Image.open("Tests/images/rgb_trns_ycbc.jp2") as jp2:
 | 
					        with Image.open("Tests/images/rgb_trns_ycbc.jp2") as jp2:
 | 
				
			||||||
 | 
					 | 
				
			||||||
            # Act
 | 
					            # Act
 | 
				
			||||||
            j2k.load()
 | 
					            j2k.load()
 | 
				
			||||||
            jp2.load()
 | 
					            jp2.load()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -645,7 +645,6 @@ class TestFileLibTiff(LibTiffTestCase):
 | 
				
			||||||
        pilim = hopper()
 | 
					        pilim = hopper()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        def save_bytesio(compression=None):
 | 
					        def save_bytesio(compression=None):
 | 
				
			||||||
 | 
					 | 
				
			||||||
            buffer_io = io.BytesIO()
 | 
					            buffer_io = io.BytesIO()
 | 
				
			||||||
            pilim.save(buffer_io, format="tiff", compression=compression)
 | 
					            pilim.save(buffer_io, format="tiff", compression=compression)
 | 
				
			||||||
            buffer_io.seek(0)
 | 
					            buffer_io.seek(0)
 | 
				
			||||||
| 
						 | 
					@ -740,7 +739,6 @@ class TestFileLibTiff(LibTiffTestCase):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_multipage_compression(self):
 | 
					    def test_multipage_compression(self):
 | 
				
			||||||
        with Image.open("Tests/images/compression.tif") as im:
 | 
					        with Image.open("Tests/images/compression.tif") as im:
 | 
				
			||||||
 | 
					 | 
				
			||||||
            im.seek(0)
 | 
					            im.seek(0)
 | 
				
			||||||
            assert im._compression == "tiff_ccitt"
 | 
					            assert im._compression == "tiff_ccitt"
 | 
				
			||||||
            assert im.size == (10, 10)
 | 
					            assert im.size == (10, 10)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -168,8 +168,7 @@ def test_mp_no_data():
 | 
				
			||||||
def test_mp_attribute(test_file):
 | 
					def test_mp_attribute(test_file):
 | 
				
			||||||
    with Image.open(test_file) as im:
 | 
					    with Image.open(test_file) as im:
 | 
				
			||||||
        mpinfo = im._getmp()
 | 
					        mpinfo = im._getmp()
 | 
				
			||||||
    frame_number = 0
 | 
					    for frame_number, mpentry in enumerate(mpinfo[0xB002]):
 | 
				
			||||||
    for mpentry in mpinfo[0xB002]:
 | 
					 | 
				
			||||||
        mpattr = mpentry["Attribute"]
 | 
					        mpattr = mpentry["Attribute"]
 | 
				
			||||||
        if frame_number:
 | 
					        if frame_number:
 | 
				
			||||||
            assert not mpattr["RepresentativeImageFlag"]
 | 
					            assert not mpattr["RepresentativeImageFlag"]
 | 
				
			||||||
| 
						 | 
					@ -180,7 +179,6 @@ def test_mp_attribute(test_file):
 | 
				
			||||||
        assert mpattr["ImageDataFormat"] == "JPEG"
 | 
					        assert mpattr["ImageDataFormat"] == "JPEG"
 | 
				
			||||||
        assert mpattr["MPType"] == "Multi-Frame Image: (Disparity)"
 | 
					        assert mpattr["MPType"] == "Multi-Frame Image: (Disparity)"
 | 
				
			||||||
        assert mpattr["Reserved"] == 0
 | 
					        assert mpattr["Reserved"] == 0
 | 
				
			||||||
        frame_number += 1
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@pytest.mark.parametrize("test_file", test_files)
 | 
					@pytest.mark.parametrize("test_file", test_files)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -44,7 +44,6 @@ def test_open_windows_v1():
 | 
				
			||||||
    # Arrange
 | 
					    # Arrange
 | 
				
			||||||
    # Act
 | 
					    # Act
 | 
				
			||||||
    with Image.open(TEST_FILE) as im:
 | 
					    with Image.open(TEST_FILE) as im:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Assert
 | 
					        # Assert
 | 
				
			||||||
        assert_image_equal(im, hopper("1"))
 | 
					        assert_image_equal(im, hopper("1"))
 | 
				
			||||||
        assert isinstance(im, MspImagePlugin.MspImageFile)
 | 
					        assert isinstance(im, MspImagePlugin.MspImageFile)
 | 
				
			||||||
| 
						 | 
					@ -59,7 +58,6 @@ def _assert_file_image_equal(source_path, target_path):
 | 
				
			||||||
    not os.path.exists(EXTRA_DIR), reason="Extra image files not installed"
 | 
					    not os.path.exists(EXTRA_DIR), reason="Extra image files not installed"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
def test_open_windows_v2():
 | 
					def test_open_windows_v2():
 | 
				
			||||||
 | 
					 | 
				
			||||||
    files = (
 | 
					    files = (
 | 
				
			||||||
        os.path.join(EXTRA_DIR, f)
 | 
					        os.path.join(EXTRA_DIR, f)
 | 
				
			||||||
        for f in os.listdir(EXTRA_DIR)
 | 
					        for f in os.listdir(EXTRA_DIR)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -80,6 +80,34 @@ def test_resolution(tmp_path):
 | 
				
			||||||
    assert size == (61.44, 61.44)
 | 
					    assert size == (61.44, 61.44)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@pytest.mark.parametrize(
 | 
				
			||||||
 | 
					    "params",
 | 
				
			||||||
 | 
					    (
 | 
				
			||||||
 | 
					        {"dpi": (75, 150)},
 | 
				
			||||||
 | 
					        {"dpi": (75, 150), "resolution": 200},
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					def test_dpi(params, tmp_path):
 | 
				
			||||||
 | 
					    im = hopper()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    outfile = str(tmp_path / "temp.pdf")
 | 
				
			||||||
 | 
					    im.save(outfile, **params)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    with open(outfile, "rb") as fp:
 | 
				
			||||||
 | 
					        contents = fp.read()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    size = tuple(
 | 
				
			||||||
 | 
					        float(d)
 | 
				
			||||||
 | 
					        for d in contents.split(b"stream\nq ")[1].split(b" 0 0 cm")[0].split(b" 0 0 ")
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    assert size == (122.88, 61.44)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    size = tuple(
 | 
				
			||||||
 | 
					        float(d) for d in contents.split(b"/MediaBox [ 0 0 ")[1].split(b"]")[0].split()
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    assert size == (122.88, 61.44)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@mark_if_feature_version(
 | 
					@mark_if_feature_version(
 | 
				
			||||||
    pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
 | 
					    pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
| 
						 | 
					@ -89,7 +117,6 @@ def test_save_all(tmp_path):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Multiframe image
 | 
					    # Multiframe image
 | 
				
			||||||
    with Image.open("Tests/images/dispose_bgnd.gif") as im:
 | 
					    with Image.open("Tests/images/dispose_bgnd.gif") as im:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        outfile = str(tmp_path / "temp.pdf")
 | 
					        outfile = str(tmp_path / "temp.pdf")
 | 
				
			||||||
        im.save(outfile, save_all=True)
 | 
					        im.save(outfile, save_all=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -123,7 +150,6 @@ def test_save_all(tmp_path):
 | 
				
			||||||
def test_multiframe_normal_save(tmp_path):
 | 
					def test_multiframe_normal_save(tmp_path):
 | 
				
			||||||
    # Test saving a multiframe image without save_all
 | 
					    # Test saving a multiframe image without save_all
 | 
				
			||||||
    with Image.open("Tests/images/dispose_bgnd.gif") as im:
 | 
					    with Image.open("Tests/images/dispose_bgnd.gif") as im:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        outfile = str(tmp_path / "temp.pdf")
 | 
					        outfile = str(tmp_path / "temp.pdf")
 | 
				
			||||||
        im.save(outfile)
 | 
					        im.save(outfile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -286,6 +312,7 @@ def test_pdf_append_to_bytesio():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@pytest.mark.timeout(1)
 | 
					@pytest.mark.timeout(1)
 | 
				
			||||||
 | 
					@pytest.mark.skipif("PILLOW_VALGRIND_TEST" in os.environ, reason="Valgrind is slower")
 | 
				
			||||||
@pytest.mark.parametrize("newline", (b"\r", b"\n"))
 | 
					@pytest.mark.parametrize("newline", (b"\r", b"\n"))
 | 
				
			||||||
def test_redos(newline):
 | 
					def test_redos(newline):
 | 
				
			||||||
    malicious = b" trailer<<>>" + newline * 3456
 | 
					    malicious = b" trailer<<>>" + newline * 3456
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -78,7 +78,6 @@ class TestFilePng:
 | 
				
			||||||
        return chunks
 | 
					        return chunks
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_sanity(self, tmp_path):
 | 
					    def test_sanity(self, tmp_path):
 | 
				
			||||||
 | 
					 | 
				
			||||||
        # internal version number
 | 
					        # internal version number
 | 
				
			||||||
        assert re.search(r"\d+\.\d+\.\d+(\.\d+)?$", features.version_codec("zlib"))
 | 
					        assert re.search(r"\d+\.\d+\.\d+(\.\d+)?$", features.version_codec("zlib"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -156,7 +155,6 @@ class TestFilePng:
 | 
				
			||||||
        assert im.info == {"spam": "egg"}
 | 
					        assert im.info == {"spam": "egg"}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_bad_itxt(self):
 | 
					    def test_bad_itxt(self):
 | 
				
			||||||
 | 
					 | 
				
			||||||
        im = load(HEAD + chunk(b"iTXt") + TAIL)
 | 
					        im = load(HEAD + chunk(b"iTXt") + TAIL)
 | 
				
			||||||
        assert im.info == {}
 | 
					        assert im.info == {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -201,7 +199,6 @@ class TestFilePng:
 | 
				
			||||||
        assert im.info["spam"].tkey == "Spam"
 | 
					        assert im.info["spam"].tkey == "Spam"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_interlace(self):
 | 
					    def test_interlace(self):
 | 
				
			||||||
 | 
					 | 
				
			||||||
        test_file = "Tests/images/pil123p.png"
 | 
					        test_file = "Tests/images/pil123p.png"
 | 
				
			||||||
        with Image.open(test_file) as im:
 | 
					        with Image.open(test_file) as im:
 | 
				
			||||||
            assert_image(im, "P", (162, 150))
 | 
					            assert_image(im, "P", (162, 150))
 | 
				
			||||||
| 
						 | 
					@ -495,7 +492,6 @@ class TestFilePng:
 | 
				
			||||||
        # Check reading images with null tRNS value, issue #1239
 | 
					        # Check reading images with null tRNS value, issue #1239
 | 
				
			||||||
        test_file = "Tests/images/tRNS_null_1x1.png"
 | 
					        test_file = "Tests/images/tRNS_null_1x1.png"
 | 
				
			||||||
        with Image.open(test_file) as im:
 | 
					        with Image.open(test_file) as im:
 | 
				
			||||||
 | 
					 | 
				
			||||||
            assert im.info["transparency"] == 0
 | 
					            assert im.info["transparency"] == 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_save_icc_profile(self):
 | 
					    def test_save_icc_profile(self):
 | 
				
			||||||
| 
						 | 
					@ -593,7 +589,7 @@ class TestFilePng:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_textual_chunks_after_idat(self):
 | 
					    def test_textual_chunks_after_idat(self):
 | 
				
			||||||
        with Image.open("Tests/images/hopper.png") as im:
 | 
					        with Image.open("Tests/images/hopper.png") as im:
 | 
				
			||||||
            assert "comment" in im.text.keys()
 | 
					            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",
 | 
				
			||||||
                "date:modify": "2014-09-04T09:37:08+03:00",
 | 
					                "date:modify": "2014-09-04T09:37:08+03:00",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -77,7 +77,6 @@ def test_eoferror():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_seek_tell():
 | 
					def test_seek_tell():
 | 
				
			||||||
    with Image.open(test_file) as im:
 | 
					    with Image.open(test_file) as im:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        layer_number = im.tell()
 | 
					        layer_number = im.tell()
 | 
				
			||||||
        assert layer_number == 1
 | 
					        assert layer_number == 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -95,7 +94,6 @@ def test_seek_tell():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_seek_eoferror():
 | 
					def test_seek_eoferror():
 | 
				
			||||||
    with Image.open(test_file) as im:
 | 
					    with Image.open(test_file) as im:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        with pytest.raises(EOFError):
 | 
					        with pytest.raises(EOFError):
 | 
				
			||||||
            im.seek(-1)
 | 
					            im.seek(-1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -79,7 +79,6 @@ def test_is_spider_image():
 | 
				
			||||||
def test_tell():
 | 
					def test_tell():
 | 
				
			||||||
    # Arrange
 | 
					    # Arrange
 | 
				
			||||||
    with Image.open(TEST_FILE) as im:
 | 
					    with Image.open(TEST_FILE) as im:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Act
 | 
					        # Act
 | 
				
			||||||
        index = im.tell()
 | 
					        index = im.tell()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -16,7 +16,6 @@ def test_sanity():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Act
 | 
					    # Act
 | 
				
			||||||
    with Image.open(test_file) as im:
 | 
					    with Image.open(test_file) as im:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Assert
 | 
					        # Assert
 | 
				
			||||||
        assert im.size == (128, 128)
 | 
					        assert im.size == (128, 128)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,18 +10,21 @@ from .helper import is_pypy
 | 
				
			||||||
TEST_TAR_FILE = "Tests/images/hopper.tar"
 | 
					TEST_TAR_FILE = "Tests/images/hopper.tar"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_sanity():
 | 
					@pytest.mark.parametrize(
 | 
				
			||||||
    for codec, test_path, format in [
 | 
					    "codec, test_path, format",
 | 
				
			||||||
        ["zlib", "hopper.png", "PNG"],
 | 
					    (
 | 
				
			||||||
        ["jpg", "hopper.jpg", "JPEG"],
 | 
					        ("zlib", "hopper.png", "PNG"),
 | 
				
			||||||
    ]:
 | 
					        ("jpg", "hopper.jpg", "JPEG"),
 | 
				
			||||||
        if features.check(codec):
 | 
					    ),
 | 
				
			||||||
            with TarIO.TarIO(TEST_TAR_FILE, test_path) as tar:
 | 
					)
 | 
				
			||||||
                with Image.open(tar) as im:
 | 
					def test_sanity(codec, test_path, format):
 | 
				
			||||||
                    im.load()
 | 
					    if features.check(codec):
 | 
				
			||||||
                    assert im.mode == "RGB"
 | 
					        with TarIO.TarIO(TEST_TAR_FILE, test_path) as tar:
 | 
				
			||||||
                    assert im.size == (128, 128)
 | 
					            with Image.open(tar) as im:
 | 
				
			||||||
                    assert im.format == format
 | 
					                im.load()
 | 
				
			||||||
 | 
					                assert im.mode == "RGB"
 | 
				
			||||||
 | 
					                assert im.size == (128, 128)
 | 
				
			||||||
 | 
					                assert im.format == format
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@pytest.mark.skipif(is_pypy(), reason="Requires CPython")
 | 
					@pytest.mark.skipif(is_pypy(), reason="Requires CPython")
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -78,7 +78,6 @@ def test_id_field():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Act
 | 
					    # Act
 | 
				
			||||||
    with Image.open(test_file) as im:
 | 
					    with Image.open(test_file) as im:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Assert
 | 
					        # Assert
 | 
				
			||||||
        assert im.size == (100, 100)
 | 
					        assert im.size == (100, 100)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -89,7 +88,6 @@ def test_id_field_rle():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Act
 | 
					    # Act
 | 
				
			||||||
    with Image.open(test_file) as im:
 | 
					    with Image.open(test_file) as im:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Assert
 | 
					        # Assert
 | 
				
			||||||
        assert im.size == (199, 199)
 | 
					        assert im.size == (199, 199)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -171,7 +169,6 @@ def test_save_id_section(tmp_path):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    test_file = "Tests/images/tga_id_field.tga"
 | 
					    test_file = "Tests/images/tga_id_field.tga"
 | 
				
			||||||
    with Image.open(test_file) as im:
 | 
					    with Image.open(test_file) as im:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Save with no id section
 | 
					        # Save with no id section
 | 
				
			||||||
        im.save(out, id_section="")
 | 
					        im.save(out, id_section="")
 | 
				
			||||||
    with Image.open(out) as test_im:
 | 
					    with Image.open(out) as test_im:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -25,7 +25,6 @@ except ImportError:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TestFileTiff:
 | 
					class TestFileTiff:
 | 
				
			||||||
    def test_sanity(self, tmp_path):
 | 
					    def test_sanity(self, tmp_path):
 | 
				
			||||||
 | 
					 | 
				
			||||||
        filename = str(tmp_path / "temp.tif")
 | 
					        filename = str(tmp_path / "temp.tif")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        hopper("RGB").save(filename)
 | 
					        hopper("RGB").save(filename)
 | 
				
			||||||
| 
						 | 
					@ -157,7 +156,6 @@ class TestFileTiff:
 | 
				
			||||||
    def test_xyres_tiff(self):
 | 
					    def test_xyres_tiff(self):
 | 
				
			||||||
        filename = "Tests/images/pil168.tif"
 | 
					        filename = "Tests/images/pil168.tif"
 | 
				
			||||||
        with Image.open(filename) as im:
 | 
					        with Image.open(filename) as im:
 | 
				
			||||||
 | 
					 | 
				
			||||||
            # legacy api
 | 
					            # legacy api
 | 
				
			||||||
            assert isinstance(im.tag[X_RESOLUTION][0], tuple)
 | 
					            assert isinstance(im.tag[X_RESOLUTION][0], tuple)
 | 
				
			||||||
            assert isinstance(im.tag[Y_RESOLUTION][0], tuple)
 | 
					            assert isinstance(im.tag[Y_RESOLUTION][0], tuple)
 | 
				
			||||||
| 
						 | 
					@ -171,7 +169,6 @@ class TestFileTiff:
 | 
				
			||||||
    def test_xyres_fallback_tiff(self):
 | 
					    def test_xyres_fallback_tiff(self):
 | 
				
			||||||
        filename = "Tests/images/compression.tif"
 | 
					        filename = "Tests/images/compression.tif"
 | 
				
			||||||
        with Image.open(filename) as im:
 | 
					        with Image.open(filename) as im:
 | 
				
			||||||
 | 
					 | 
				
			||||||
            # v2 api
 | 
					            # v2 api
 | 
				
			||||||
            assert isinstance(im.tag_v2[X_RESOLUTION], TiffImagePlugin.IFDRational)
 | 
					            assert isinstance(im.tag_v2[X_RESOLUTION], TiffImagePlugin.IFDRational)
 | 
				
			||||||
            assert isinstance(im.tag_v2[Y_RESOLUTION], TiffImagePlugin.IFDRational)
 | 
					            assert isinstance(im.tag_v2[Y_RESOLUTION], TiffImagePlugin.IFDRational)
 | 
				
			||||||
| 
						 | 
					@ -186,7 +183,6 @@ class TestFileTiff:
 | 
				
			||||||
    def test_int_resolution(self):
 | 
					    def test_int_resolution(self):
 | 
				
			||||||
        filename = "Tests/images/pil168.tif"
 | 
					        filename = "Tests/images/pil168.tif"
 | 
				
			||||||
        with Image.open(filename) as im:
 | 
					        with Image.open(filename) as im:
 | 
				
			||||||
 | 
					 | 
				
			||||||
            # Try to read a file where X,Y_RESOLUTION are ints
 | 
					            # Try to read a file where X,Y_RESOLUTION are ints
 | 
				
			||||||
            im.tag_v2[X_RESOLUTION] = 71
 | 
					            im.tag_v2[X_RESOLUTION] = 71
 | 
				
			||||||
            im.tag_v2[Y_RESOLUTION] = 71
 | 
					            im.tag_v2[Y_RESOLUTION] = 71
 | 
				
			||||||
| 
						 | 
					@ -381,7 +377,6 @@ class TestFileTiff:
 | 
				
			||||||
    def test___str__(self):
 | 
					    def test___str__(self):
 | 
				
			||||||
        filename = "Tests/images/pil136.tiff"
 | 
					        filename = "Tests/images/pil136.tiff"
 | 
				
			||||||
        with Image.open(filename) as im:
 | 
					        with Image.open(filename) as im:
 | 
				
			||||||
 | 
					 | 
				
			||||||
            # Act
 | 
					            # Act
 | 
				
			||||||
            ret = str(im.ifd)
 | 
					            ret = str(im.ifd)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -392,7 +387,6 @@ class TestFileTiff:
 | 
				
			||||||
        # Arrange
 | 
					        # Arrange
 | 
				
			||||||
        filename = "Tests/images/pil136.tiff"
 | 
					        filename = "Tests/images/pil136.tiff"
 | 
				
			||||||
        with Image.open(filename) as im:
 | 
					        with Image.open(filename) as im:
 | 
				
			||||||
 | 
					 | 
				
			||||||
            # v2 interface
 | 
					            # v2 interface
 | 
				
			||||||
            v2_tags = {
 | 
					            v2_tags = {
 | 
				
			||||||
                256: 55,
 | 
					                256: 55,
 | 
				
			||||||
| 
						 | 
					@ -630,7 +624,6 @@ class TestFileTiff:
 | 
				
			||||||
        filename = str(tmp_path / "temp.tif")
 | 
					        filename = str(tmp_path / "temp.tif")
 | 
				
			||||||
        hopper("RGB").save(filename, **kwargs)
 | 
					        hopper("RGB").save(filename, **kwargs)
 | 
				
			||||||
        with Image.open(filename) as im:
 | 
					        with Image.open(filename) as im:
 | 
				
			||||||
 | 
					 | 
				
			||||||
            # legacy interface
 | 
					            # legacy interface
 | 
				
			||||||
            assert im.tag[X_RESOLUTION][0][0] == 72
 | 
					            assert im.tag[X_RESOLUTION][0][0] == 72
 | 
				
			||||||
            assert im.tag[Y_RESOLUTION][0][0] == 36
 | 
					            assert im.tag[Y_RESOLUTION][0][0] == 36
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -54,7 +54,6 @@ def test_rt_metadata(tmp_path):
 | 
				
			||||||
    img.save(f, tiffinfo=info)
 | 
					    img.save(f, tiffinfo=info)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open(f) as loaded:
 | 
					    with Image.open(f) as loaded:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        assert loaded.tag[ImageJMetaDataByteCounts] == (len(bin_data),)
 | 
					        assert loaded.tag[ImageJMetaDataByteCounts] == (len(bin_data),)
 | 
				
			||||||
        assert loaded.tag_v2[ImageJMetaDataByteCounts] == (len(bin_data),)
 | 
					        assert loaded.tag_v2[ImageJMetaDataByteCounts] == (len(bin_data),)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -74,14 +73,12 @@ def test_rt_metadata(tmp_path):
 | 
				
			||||||
    info[ImageJMetaDataByteCounts] = (8, len(bin_data) - 8)
 | 
					    info[ImageJMetaDataByteCounts] = (8, len(bin_data) - 8)
 | 
				
			||||||
    img.save(f, tiffinfo=info)
 | 
					    img.save(f, tiffinfo=info)
 | 
				
			||||||
    with Image.open(f) as loaded:
 | 
					    with Image.open(f) as loaded:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        assert loaded.tag[ImageJMetaDataByteCounts] == (8, len(bin_data) - 8)
 | 
					        assert loaded.tag[ImageJMetaDataByteCounts] == (8, len(bin_data) - 8)
 | 
				
			||||||
        assert loaded.tag_v2[ImageJMetaDataByteCounts] == (8, len(bin_data) - 8)
 | 
					        assert loaded.tag_v2[ImageJMetaDataByteCounts] == (8, len(bin_data) - 8)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_read_metadata():
 | 
					def test_read_metadata():
 | 
				
			||||||
    with Image.open("Tests/images/hopper_g4.tif") as img:
 | 
					    with Image.open("Tests/images/hopper_g4.tif") as img:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        assert {
 | 
					        assert {
 | 
				
			||||||
            "YResolution": IFDRational(4294967295, 113653537),
 | 
					            "YResolution": IFDRational(4294967295, 113653537),
 | 
				
			||||||
            "PlanarConfiguration": 1,
 | 
					            "PlanarConfiguration": 1,
 | 
				
			||||||
| 
						 | 
					@ -202,14 +199,15 @@ def test_writing_other_types_to_ascii(value, expected, tmp_path):
 | 
				
			||||||
        assert reloaded.tag_v2[271] == expected
 | 
					        assert reloaded.tag_v2[271] == expected
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_writing_int_to_bytes(tmp_path):
 | 
					@pytest.mark.parametrize("value", (1, IFDRational(1)))
 | 
				
			||||||
 | 
					def test_writing_other_types_to_bytes(value, tmp_path):
 | 
				
			||||||
    im = hopper()
 | 
					    im = hopper()
 | 
				
			||||||
    info = TiffImagePlugin.ImageFileDirectory_v2()
 | 
					    info = TiffImagePlugin.ImageFileDirectory_v2()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    tag = TiffTags.TAGS_V2[700]
 | 
					    tag = TiffTags.TAGS_V2[700]
 | 
				
			||||||
    assert tag.type == TiffTags.BYTE
 | 
					    assert tag.type == TiffTags.BYTE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    info[700] = 1
 | 
					    info[700] = value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    out = str(tmp_path / "temp.tiff")
 | 
					    out = str(tmp_path / "temp.tiff")
 | 
				
			||||||
    im.save(out, tiffinfo=info)
 | 
					    im.save(out, tiffinfo=info)
 | 
				
			||||||
| 
						 | 
					@ -218,6 +216,22 @@ def test_writing_int_to_bytes(tmp_path):
 | 
				
			||||||
        assert reloaded.tag_v2[700] == b"\x01"
 | 
					        assert reloaded.tag_v2[700] == b"\x01"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_writing_other_types_to_undefined(tmp_path):
 | 
				
			||||||
 | 
					    im = hopper()
 | 
				
			||||||
 | 
					    info = TiffImagePlugin.ImageFileDirectory_v2()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    tag = TiffTags.TAGS_V2[33723]
 | 
				
			||||||
 | 
					    assert tag.type == TiffTags.UNDEFINED
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    info[33723] = 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    out = str(tmp_path / "temp.tiff")
 | 
				
			||||||
 | 
					    im.save(out, tiffinfo=info)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    with Image.open(out) as reloaded:
 | 
				
			||||||
 | 
					        assert reloaded.tag_v2[33723] == b"1"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_undefined_zero(tmp_path):
 | 
					def test_undefined_zero(tmp_path):
 | 
				
			||||||
    # Check that the tag has not been changed since this test was created
 | 
					    # Check that the tag has not been changed since this test was created
 | 
				
			||||||
    tag = TiffTags.TAGS_V2[45059]
 | 
					    tag = TiffTags.TAGS_V2[45059]
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -18,10 +18,8 @@ except ImportError:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_read_exif_metadata():
 | 
					def test_read_exif_metadata():
 | 
				
			||||||
 | 
					 | 
				
			||||||
    file_path = "Tests/images/flower.webp"
 | 
					    file_path = "Tests/images/flower.webp"
 | 
				
			||||||
    with Image.open(file_path) as image:
 | 
					    with Image.open(file_path) as image:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        assert image.format == "WEBP"
 | 
					        assert image.format == "WEBP"
 | 
				
			||||||
        exif_data = image.info.get("exif", None)
 | 
					        exif_data = image.info.get("exif", None)
 | 
				
			||||||
        assert exif_data
 | 
					        assert exif_data
 | 
				
			||||||
| 
						 | 
					@ -64,10 +62,8 @@ def test_write_exif_metadata():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_read_icc_profile():
 | 
					def test_read_icc_profile():
 | 
				
			||||||
 | 
					 | 
				
			||||||
    file_path = "Tests/images/flower2.webp"
 | 
					    file_path = "Tests/images/flower2.webp"
 | 
				
			||||||
    with Image.open(file_path) as image:
 | 
					    with Image.open(file_path) as image:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        assert image.format == "WEBP"
 | 
					        assert image.format == "WEBP"
 | 
				
			||||||
        assert image.info.get("icc_profile", None)
 | 
					        assert image.info.get("icc_profile", None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,7 +6,6 @@ from .helper import assert_image_similar_tofile, hopper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_load_raw():
 | 
					def test_load_raw():
 | 
				
			||||||
 | 
					 | 
				
			||||||
    # Test basic EMF open and rendering
 | 
					    # Test basic EMF open and rendering
 | 
				
			||||||
    with Image.open("Tests/images/drawing.emf") as im:
 | 
					    with Image.open("Tests/images/drawing.emf") as im:
 | 
				
			||||||
        if hasattr(Image.core, "drawwmf"):
 | 
					        if hasattr(Image.core, "drawwmf"):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -44,7 +44,6 @@ def test_open():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Act
 | 
					    # Act
 | 
				
			||||||
    with Image.open(filename) as im:
 | 
					    with Image.open(filename) as im:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Assert
 | 
					        # Assert
 | 
				
			||||||
        assert im.mode == "1"
 | 
					        assert im.mode == "1"
 | 
				
			||||||
        assert im.size == (128, 128)
 | 
					        assert im.size == (128, 128)
 | 
				
			||||||
| 
						 | 
					@ -57,7 +56,6 @@ def test_open_filename_with_underscore():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Act
 | 
					    # Act
 | 
				
			||||||
    with Image.open(filename) as im:
 | 
					    with Image.open(filename) as im:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Assert
 | 
					        # Assert
 | 
				
			||||||
        assert im.mode == "1"
 | 
					        assert im.mode == "1"
 | 
				
			||||||
        assert im.size == (128, 128)
 | 
					        assert im.size == (128, 128)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,7 +10,6 @@ TEST_FILE = "Tests/images/hopper.p7"
 | 
				
			||||||
def test_open():
 | 
					def test_open():
 | 
				
			||||||
    # Act
 | 
					    # Act
 | 
				
			||||||
    with Image.open(TEST_FILE) as im:
 | 
					    with Image.open(TEST_FILE) as im:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Assert
 | 
					        # Assert
 | 
				
			||||||
        assert im.format == "XVThumb"
 | 
					        assert im.format == "XVThumb"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										22
									
								
								Tests/test_font_crash.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								Tests/test_font_crash.py
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,22 @@
 | 
				
			||||||
 | 
					import pytest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from PIL import Image, ImageDraw, ImageFont
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from .helper import skip_unless_feature
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TestFontCrash:
 | 
				
			||||||
 | 
					    def _fuzz_font(self, font):
 | 
				
			||||||
 | 
					        # from fuzzers.fuzz_font
 | 
				
			||||||
 | 
					        font.getbbox("ABC")
 | 
				
			||||||
 | 
					        font.getmask("test text")
 | 
				
			||||||
 | 
					        with Image.new(mode="RGBA", size=(200, 200)) as im:
 | 
				
			||||||
 | 
					            draw = ImageDraw.Draw(im)
 | 
				
			||||||
 | 
					            draw.multiline_textbbox((10, 10), "ABC\nAaaa", font, stroke_width=2)
 | 
				
			||||||
 | 
					            draw.text((10, 10), "Test Text", font=font, fill="#000")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @skip_unless_feature("freetype2")
 | 
				
			||||||
 | 
					    def test_segfault(self):
 | 
				
			||||||
 | 
					        with pytest.raises(OSError):
 | 
				
			||||||
 | 
					            font = ImageFont.truetype("Tests/fonts/fuzz_font-5203009437302784")
 | 
				
			||||||
 | 
					            self._fuzz_font(font)
 | 
				
			||||||
| 
						 | 
					@ -69,7 +69,6 @@ class TestImage:
 | 
				
			||||||
        assert issubclass(UnidentifiedImageError, OSError)
 | 
					        assert issubclass(UnidentifiedImageError, OSError)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_sanity(self):
 | 
					    def test_sanity(self):
 | 
				
			||||||
 | 
					 | 
				
			||||||
        im = Image.new("L", (100, 100))
 | 
					        im = Image.new("L", (100, 100))
 | 
				
			||||||
        assert repr(im)[:45] == "<PIL.Image.Image image mode=L size=100x100 at"
 | 
					        assert repr(im)[:45] == "<PIL.Image.Image image mode=L size=100x100 at"
 | 
				
			||||||
        assert im.mode == "L"
 | 
					        assert im.mode == "L"
 | 
				
			||||||
| 
						 | 
					@ -398,6 +397,17 @@ class TestImage:
 | 
				
			||||||
        with pytest.raises(ValueError):
 | 
					        with pytest.raises(ValueError):
 | 
				
			||||||
            source.alpha_composite(over, (0, 0), (0, -1))
 | 
					            source.alpha_composite(over, (0, 0), (0, -1))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_register_open_duplicates(self):
 | 
				
			||||||
 | 
					        # Arrange
 | 
				
			||||||
 | 
					        factory, accept = Image.OPEN["JPEG"]
 | 
				
			||||||
 | 
					        id_length = len(Image.ID)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Act
 | 
				
			||||||
 | 
					        Image.register_open("JPEG", factory, accept)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Assert
 | 
				
			||||||
 | 
					        assert len(Image.ID) == id_length
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_registered_extensions_uninitialized(self):
 | 
					    def test_registered_extensions_uninitialized(self):
 | 
				
			||||||
        # Arrange
 | 
					        # Arrange
 | 
				
			||||||
        Image._initialized = 0
 | 
					        Image._initialized = 0
 | 
				
			||||||
| 
						 | 
					@ -512,6 +522,14 @@ class TestImage:
 | 
				
			||||||
        i = Image.new("RGB", [1, 1])
 | 
					        i = Image.new("RGB", [1, 1])
 | 
				
			||||||
        assert isinstance(i.size, tuple)
 | 
					        assert isinstance(i.size, tuple)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @pytest.mark.timeout(0.75)
 | 
				
			||||||
 | 
					    @pytest.mark.skipif(
 | 
				
			||||||
 | 
					        "PILLOW_VALGRIND_TEST" in os.environ, reason="Valgrind is slower"
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    @pytest.mark.parametrize("size", ((0, 100000000), (100000000, 0)))
 | 
				
			||||||
 | 
					    def test_empty_image(self, size):
 | 
				
			||||||
 | 
					        Image.new("RGB", size)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_storage_neg(self):
 | 
					    def test_storage_neg(self):
 | 
				
			||||||
        # Storage.c accepted negative values for xsize, ysize.  Was
 | 
					        # Storage.c accepted negative values for xsize, ysize.  Was
 | 
				
			||||||
        # test_neg_ppm, but the core function for that has been
 | 
					        # test_neg_ppm, but the core function for that has been
 | 
				
			||||||
| 
						 | 
					@ -921,12 +939,7 @@ class TestImage:
 | 
				
			||||||
        with pytest.warns(DeprecationWarning):
 | 
					        with pytest.warns(DeprecationWarning):
 | 
				
			||||||
            assert Image.CONTAINER == 2
 | 
					            assert Image.CONTAINER == 2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_constants_deprecation(self):
 | 
					    def test_constants(self):
 | 
				
			||||||
        with pytest.warns(DeprecationWarning):
 | 
					 | 
				
			||||||
            assert Image.NEAREST == 0
 | 
					 | 
				
			||||||
        with pytest.warns(DeprecationWarning):
 | 
					 | 
				
			||||||
            assert Image.NONE == 0
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        with pytest.warns(DeprecationWarning):
 | 
					        with pytest.warns(DeprecationWarning):
 | 
				
			||||||
            assert Image.LINEAR == Image.Resampling.BILINEAR
 | 
					            assert Image.LINEAR == Image.Resampling.BILINEAR
 | 
				
			||||||
        with pytest.warns(DeprecationWarning):
 | 
					        with pytest.warns(DeprecationWarning):
 | 
				
			||||||
| 
						 | 
					@ -943,8 +956,7 @@ class TestImage:
 | 
				
			||||||
            Image.Quantize,
 | 
					            Image.Quantize,
 | 
				
			||||||
        ):
 | 
					        ):
 | 
				
			||||||
            for name in enum.__members__:
 | 
					            for name in enum.__members__:
 | 
				
			||||||
                with pytest.warns(DeprecationWarning):
 | 
					                assert getattr(Image, name) == enum[name]
 | 
				
			||||||
                    assert getattr(Image, name) == enum[name]
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @pytest.mark.parametrize(
 | 
					    @pytest.mark.parametrize(
 | 
				
			||||||
        "path",
 | 
					        "path",
 | 
				
			||||||
| 
						 | 
					@ -994,7 +1006,6 @@ def mock_encode(*args):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TestRegistry:
 | 
					class TestRegistry:
 | 
				
			||||||
    def test_encode_registry(self):
 | 
					    def test_encode_registry(self):
 | 
				
			||||||
 | 
					 | 
				
			||||||
        Image.register_encoder("MOCK", mock_encode)
 | 
					        Image.register_encoder("MOCK", mock_encode)
 | 
				
			||||||
        assert "MOCK" in Image.ENCODERS
 | 
					        assert "MOCK" in Image.ENCODERS
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -45,7 +45,6 @@ def test_unsupported_conversion():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_default():
 | 
					def test_default():
 | 
				
			||||||
 | 
					 | 
				
			||||||
    im = hopper("P")
 | 
					    im = hopper("P")
 | 
				
			||||||
    assert im.mode == "P"
 | 
					    assert im.mode == "P"
 | 
				
			||||||
    converted_im = im.convert()
 | 
					    converted_im = im.convert()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -86,7 +86,6 @@ def test_crop_crash():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_crop_zero():
 | 
					def test_crop_zero():
 | 
				
			||||||
 | 
					 | 
				
			||||||
    im = Image.new("RGB", (0, 0), "white")
 | 
					    im = Image.new("RGB", (0, 0), "white")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    cropped = im.crop((0, 0, 0, 0))
 | 
					    cropped = im.crop((0, 0, 0, 0))
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -24,6 +24,7 @@ from .helper import assert_image_equal, hopper
 | 
				
			||||||
        ImageFilter.ModeFilter,
 | 
					        ImageFilter.ModeFilter,
 | 
				
			||||||
        ImageFilter.GaussianBlur,
 | 
					        ImageFilter.GaussianBlur,
 | 
				
			||||||
        ImageFilter.GaussianBlur(5),
 | 
					        ImageFilter.GaussianBlur(5),
 | 
				
			||||||
 | 
					        ImageFilter.BoxBlur(0),
 | 
				
			||||||
        ImageFilter.BoxBlur(5),
 | 
					        ImageFilter.BoxBlur(5),
 | 
				
			||||||
        ImageFilter.UnsharpMask,
 | 
					        ImageFilter.UnsharpMask,
 | 
				
			||||||
        ImageFilter.UnsharpMask(10),
 | 
					        ImageFilter.UnsharpMask(10),
 | 
				
			||||||
| 
						 | 
					@ -173,3 +174,14 @@ def test_consistency_5x5(mode):
 | 
				
			||||||
                Image.merge(mode, source[: len(mode)]).filter(kernel),
 | 
					                Image.merge(mode, source[: len(mode)]).filter(kernel),
 | 
				
			||||||
                Image.merge(mode, reference[: len(mode)]),
 | 
					                Image.merge(mode, reference[: len(mode)]),
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_invalid_box_blur_filter():
 | 
				
			||||||
 | 
					    with pytest.raises(ValueError):
 | 
				
			||||||
 | 
					        ImageFilter.BoxBlur(-2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    im = hopper()
 | 
				
			||||||
 | 
					    box_blur_filter = ImageFilter.BoxBlur(2)
 | 
				
			||||||
 | 
					    box_blur_filter.radius = -2
 | 
				
			||||||
 | 
					    with pytest.raises(ValueError):
 | 
				
			||||||
 | 
					        im.filter(box_blur_filter)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,10 +1,17 @@
 | 
				
			||||||
 | 
					import pytest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from PIL import Image
 | 
					from PIL import Image
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .helper import assert_image_equal, hopper
 | 
					from .helper import assert_image_equal, hopper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_sanity():
 | 
					@pytest.mark.parametrize("data_type", ("bytes", "memoryview"))
 | 
				
			||||||
 | 
					def test_sanity(data_type):
 | 
				
			||||||
    im1 = hopper()
 | 
					    im1 = hopper()
 | 
				
			||||||
    im2 = Image.frombytes(im1.mode, im1.size, im1.tobytes())
 | 
					
 | 
				
			||||||
 | 
					    data = im1.tobytes()
 | 
				
			||||||
 | 
					    if data_type == "memoryview":
 | 
				
			||||||
 | 
					        data = memoryview(data)
 | 
				
			||||||
 | 
					    im2 = Image.frombytes(im1.mode, im1.size, data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    assert_image_equal(im1, im2)
 | 
					    assert_image_equal(im1, im2)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,7 +4,6 @@ from .helper import hopper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_sanity():
 | 
					def test_sanity():
 | 
				
			||||||
 | 
					 | 
				
			||||||
    bbox = hopper().getbbox()
 | 
					    bbox = hopper().getbbox()
 | 
				
			||||||
    assert isinstance(bbox, tuple)
 | 
					    assert isinstance(bbox, tuple)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,10 +1,11 @@
 | 
				
			||||||
 | 
					import pytest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from PIL import Image, ImageMode
 | 
					from PIL import Image, ImageMode
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .helper import hopper
 | 
					from .helper import hopper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_sanity():
 | 
					def test_sanity():
 | 
				
			||||||
 | 
					 | 
				
			||||||
    with hopper() as im:
 | 
					    with hopper() as im:
 | 
				
			||||||
        im.mode
 | 
					        im.mode
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -49,23 +50,25 @@ def test_sanity():
 | 
				
			||||||
    assert m.typestr == "|u1"
 | 
					    assert m.typestr == "|u1"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_properties():
 | 
					@pytest.mark.parametrize(
 | 
				
			||||||
    def check(mode, *result):
 | 
					    "mode, expected_base, expected_type, expected_bands, expected_band_names",
 | 
				
			||||||
        signature = (
 | 
					    (
 | 
				
			||||||
            Image.getmodebase(mode),
 | 
					        ("1", "L", "L", 1, ("1",)),
 | 
				
			||||||
            Image.getmodetype(mode),
 | 
					        ("L", "L", "L", 1, ("L",)),
 | 
				
			||||||
            Image.getmodebands(mode),
 | 
					        ("P", "P", "L", 1, ("P",)),
 | 
				
			||||||
            Image.getmodebandnames(mode),
 | 
					        ("I", "L", "I", 1, ("I",)),
 | 
				
			||||||
        )
 | 
					        ("F", "L", "F", 1, ("F",)),
 | 
				
			||||||
        assert signature == result
 | 
					        ("RGB", "RGB", "L", 3, ("R", "G", "B")),
 | 
				
			||||||
 | 
					        ("RGBA", "RGB", "L", 4, ("R", "G", "B", "A")),
 | 
				
			||||||
    check("1", "L", "L", 1, ("1",))
 | 
					        ("RGBX", "RGB", "L", 4, ("R", "G", "B", "X")),
 | 
				
			||||||
    check("L", "L", "L", 1, ("L",))
 | 
					        ("CMYK", "RGB", "L", 4, ("C", "M", "Y", "K")),
 | 
				
			||||||
    check("P", "P", "L", 1, ("P",))
 | 
					        ("YCbCr", "RGB", "L", 3, ("Y", "Cb", "Cr")),
 | 
				
			||||||
    check("I", "L", "I", 1, ("I",))
 | 
					    ),
 | 
				
			||||||
    check("F", "L", "F", 1, ("F",))
 | 
					)
 | 
				
			||||||
    check("RGB", "RGB", "L", 3, ("R", "G", "B"))
 | 
					def test_properties(
 | 
				
			||||||
    check("RGBA", "RGB", "L", 4, ("R", "G", "B", "A"))
 | 
					    mode, expected_base, expected_type, expected_bands, expected_band_names
 | 
				
			||||||
    check("RGBX", "RGB", "L", 4, ("R", "G", "B", "X"))
 | 
					):
 | 
				
			||||||
    check("CMYK", "RGB", "L", 4, ("C", "M", "Y", "K"))
 | 
					    assert Image.getmodebase(mode) == expected_base
 | 
				
			||||||
    check("YCbCr", "RGB", "L", 3, ("Y", "Cb", "Cr"))
 | 
					    assert Image.getmodetype(mode) == expected_type
 | 
				
			||||||
 | 
					    assert Image.getmodebands(mode) == expected_bands
 | 
				
			||||||
 | 
					    assert Image.getmodebandnames(mode) == expected_band_names
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -135,16 +135,15 @@ class TestImagingCoreResampleAccuracy:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L"))
 | 
					    @pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L"))
 | 
				
			||||||
    def test_reduce_bicubic(self, mode):
 | 
					    def test_reduce_bicubic(self, mode):
 | 
				
			||||||
        for mode in ["RGBX", "RGB", "La", "L"]:
 | 
					        case = self.make_case(mode, (12, 12), 0xE1)
 | 
				
			||||||
            case = self.make_case(mode, (12, 12), 0xE1)
 | 
					        case = case.resize((6, 6), Image.Resampling.BICUBIC)
 | 
				
			||||||
            case = case.resize((6, 6), Image.Resampling.BICUBIC)
 | 
					        # fmt: off
 | 
				
			||||||
            # fmt: off
 | 
					        data = ("e1 e3 d4"
 | 
				
			||||||
            data = ("e1 e3 d4"
 | 
					                "e3 e5 d6"
 | 
				
			||||||
                    "e3 e5 d6"
 | 
					                "d4 d6 c9")
 | 
				
			||||||
                    "d4 d6 c9")
 | 
					        # fmt: on
 | 
				
			||||||
            # fmt: on
 | 
					        for channel in case.split():
 | 
				
			||||||
            for channel in case.split():
 | 
					            self.check_case(channel, self.make_sample(data, (6, 6)))
 | 
				
			||||||
                self.check_case(channel, self.make_sample(data, (6, 6)))
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L"))
 | 
					    @pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L"))
 | 
				
			||||||
    def test_reduce_lanczos(self, mode):
 | 
					    def test_reduce_lanczos(self, mode):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -52,7 +52,7 @@ def test_resample():
 | 
				
			||||||
    # >>> im.save('Tests/images/hopper_45.png')
 | 
					    # >>> im.save('Tests/images/hopper_45.png')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open("Tests/images/hopper_45.png") as target:
 | 
					    with Image.open("Tests/images/hopper_45.png") as target:
 | 
				
			||||||
        for (resample, epsilon) in (
 | 
					        for resample, epsilon in (
 | 
				
			||||||
            (Image.Resampling.NEAREST, 10),
 | 
					            (Image.Resampling.NEAREST, 10),
 | 
				
			||||||
            (Image.Resampling.BILINEAR, 5),
 | 
					            (Image.Resampling.BILINEAR, 5),
 | 
				
			||||||
            (Image.Resampling.BICUBIC, 0),
 | 
					            (Image.Resampling.BICUBIC, 0),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,7 +4,6 @@ from .helper import assert_image_equal, fromstring, hopper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_sanity():
 | 
					def test_sanity():
 | 
				
			||||||
 | 
					 | 
				
			||||||
    with pytest.raises(ValueError):
 | 
					    with pytest.raises(ValueError):
 | 
				
			||||||
        hopper().tobitmap()
 | 
					        hopper().tobitmap()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -42,12 +42,12 @@ class TestImageTransform:
 | 
				
			||||||
    def test_extent(self):
 | 
					    def test_extent(self):
 | 
				
			||||||
        im = hopper("RGB")
 | 
					        im = hopper("RGB")
 | 
				
			||||||
        (w, h) = im.size
 | 
					        (w, h) = im.size
 | 
				
			||||||
        # fmt: off
 | 
					        transformed = im.transform(
 | 
				
			||||||
        transformed = im.transform(im.size, Image.Transform.EXTENT,
 | 
					            im.size,
 | 
				
			||||||
                                   (0, 0,
 | 
					            Image.Transform.EXTENT,
 | 
				
			||||||
                                    w//2, h//2),  # ul -> lr
 | 
					            (0, 0, w // 2, h // 2),  # ul -> lr
 | 
				
			||||||
                                   Image.Resampling.BILINEAR)
 | 
					            Image.Resampling.BILINEAR,
 | 
				
			||||||
        # fmt: on
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        scaled = im.resize((w * 2, h * 2), Image.Resampling.BILINEAR).crop((0, 0, w, h))
 | 
					        scaled = im.resize((w * 2, h * 2), Image.Resampling.BILINEAR).crop((0, 0, w, h))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -58,13 +58,12 @@ class TestImageTransform:
 | 
				
			||||||
        # one simple quad transform, equivalent to scale & crop upper left quad
 | 
					        # one simple quad transform, equivalent to scale & crop upper left quad
 | 
				
			||||||
        im = hopper("RGB")
 | 
					        im = hopper("RGB")
 | 
				
			||||||
        (w, h) = im.size
 | 
					        (w, h) = im.size
 | 
				
			||||||
        # fmt: off
 | 
					        transformed = im.transform(
 | 
				
			||||||
        transformed = im.transform(im.size, Image.Transform.QUAD,
 | 
					            im.size,
 | 
				
			||||||
                                   (0, 0, 0, h//2,
 | 
					            Image.Transform.QUAD,
 | 
				
			||||||
                                    # ul -> ccw around quad:
 | 
					            (0, 0, 0, h // 2, w // 2, h // 2, w // 2, 0),  # ul -> ccw around quad
 | 
				
			||||||
                                    w//2, h//2, w//2, 0),
 | 
					            Image.Resampling.BILINEAR,
 | 
				
			||||||
                                   Image.Resampling.BILINEAR)
 | 
					        )
 | 
				
			||||||
        # fmt: on
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        scaled = im.transform(
 | 
					        scaled = im.transform(
 | 
				
			||||||
            (w, h),
 | 
					            (w, h),
 | 
				
			||||||
| 
						 | 
					@ -99,16 +98,21 @@ class TestImageTransform:
 | 
				
			||||||
        # this should be a checkerboard of halfsized hoppers in ul, lr
 | 
					        # this should be a checkerboard of halfsized hoppers in ul, lr
 | 
				
			||||||
        im = hopper("RGBA")
 | 
					        im = hopper("RGBA")
 | 
				
			||||||
        (w, h) = im.size
 | 
					        (w, h) = im.size
 | 
				
			||||||
        # fmt: off
 | 
					        transformed = im.transform(
 | 
				
			||||||
        transformed = im.transform(im.size, Image.Transform.MESH,
 | 
					            im.size,
 | 
				
			||||||
                                   [((0, 0, w//2, h//2),  # box
 | 
					            Image.Transform.MESH,
 | 
				
			||||||
                                    (0, 0, 0, h,
 | 
					            (
 | 
				
			||||||
                                     w, h, w, 0)),  # ul -> ccw around quad
 | 
					                (
 | 
				
			||||||
                                    ((w//2, h//2, w, h),  # box
 | 
					                    (0, 0, w // 2, h // 2),  # box
 | 
				
			||||||
                                    (0, 0, 0, h,
 | 
					                    (0, 0, 0, h, w, h, w, 0),  # ul -> ccw around quad
 | 
				
			||||||
                                     w, h, w, 0))],  # ul -> ccw around quad
 | 
					                ),
 | 
				
			||||||
                                   Image.Resampling.BILINEAR)
 | 
					                (
 | 
				
			||||||
        # fmt: on
 | 
					                    (w // 2, h // 2, w, h),  # box
 | 
				
			||||||
 | 
					                    (0, 0, 0, h, w, h, w, 0),  # ul -> ccw around quad
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            Image.Resampling.BILINEAR,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        scaled = im.transform(
 | 
					        scaled = im.transform(
 | 
				
			||||||
            (w // 2, h // 2),
 | 
					            (w // 2, h // 2),
 | 
				
			||||||
| 
						 | 
					@ -174,11 +178,13 @@ class TestImageTransform:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        im = op(im, (40, 10))
 | 
					        im = op(im, (40, 10))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        colors = im.getcolors()
 | 
					        colors = sorted(im.getcolors())
 | 
				
			||||||
        assert colors == [
 | 
					        assert colors == sorted(
 | 
				
			||||||
            (20 * 10, opaque),
 | 
					            (
 | 
				
			||||||
            (20 * 10, transparent),
 | 
					                (20 * 10, opaque),
 | 
				
			||||||
        ]
 | 
					                (20 * 10, transparent),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @pytest.mark.parametrize("mode", ("RGBA", "LA"))
 | 
					    @pytest.mark.parametrize("mode", ("RGBA", "LA"))
 | 
				
			||||||
    def test_nearest_resize(self, mode):
 | 
					    def test_nearest_resize(self, mode):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -50,7 +50,6 @@ def test_add():
 | 
				
			||||||
    # Arrange
 | 
					    # Arrange
 | 
				
			||||||
    with Image.open("Tests/images/imagedraw_ellipse_RGB.png") as im1:
 | 
					    with Image.open("Tests/images/imagedraw_ellipse_RGB.png") as im1:
 | 
				
			||||||
        with Image.open("Tests/images/imagedraw_floodfill_RGB.png") as im2:
 | 
					        with Image.open("Tests/images/imagedraw_floodfill_RGB.png") as im2:
 | 
				
			||||||
 | 
					 | 
				
			||||||
            # Act
 | 
					            # Act
 | 
				
			||||||
            new = ImageChops.add(im1, im2)
 | 
					            new = ImageChops.add(im1, im2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -63,7 +62,6 @@ def test_add_scale_offset():
 | 
				
			||||||
    # Arrange
 | 
					    # Arrange
 | 
				
			||||||
    with Image.open("Tests/images/imagedraw_ellipse_RGB.png") as im1:
 | 
					    with Image.open("Tests/images/imagedraw_ellipse_RGB.png") as im1:
 | 
				
			||||||
        with Image.open("Tests/images/imagedraw_floodfill_RGB.png") as im2:
 | 
					        with Image.open("Tests/images/imagedraw_floodfill_RGB.png") as im2:
 | 
				
			||||||
 | 
					 | 
				
			||||||
            # Act
 | 
					            # Act
 | 
				
			||||||
            new = ImageChops.add(im1, im2, scale=2.5, offset=100)
 | 
					            new = ImageChops.add(im1, im2, scale=2.5, offset=100)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -87,7 +85,6 @@ def test_add_modulo():
 | 
				
			||||||
    # Arrange
 | 
					    # Arrange
 | 
				
			||||||
    with Image.open("Tests/images/imagedraw_ellipse_RGB.png") as im1:
 | 
					    with Image.open("Tests/images/imagedraw_ellipse_RGB.png") as im1:
 | 
				
			||||||
        with Image.open("Tests/images/imagedraw_floodfill_RGB.png") as im2:
 | 
					        with Image.open("Tests/images/imagedraw_floodfill_RGB.png") as im2:
 | 
				
			||||||
 | 
					 | 
				
			||||||
            # Act
 | 
					            # Act
 | 
				
			||||||
            new = ImageChops.add_modulo(im1, im2)
 | 
					            new = ImageChops.add_modulo(im1, im2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -111,7 +108,6 @@ def test_blend():
 | 
				
			||||||
    # Arrange
 | 
					    # Arrange
 | 
				
			||||||
    with Image.open("Tests/images/imagedraw_ellipse_RGB.png") as im1:
 | 
					    with Image.open("Tests/images/imagedraw_ellipse_RGB.png") as im1:
 | 
				
			||||||
        with Image.open("Tests/images/imagedraw_floodfill_RGB.png") as im2:
 | 
					        with Image.open("Tests/images/imagedraw_floodfill_RGB.png") as im2:
 | 
				
			||||||
 | 
					 | 
				
			||||||
            # Act
 | 
					            # Act
 | 
				
			||||||
            new = ImageChops.blend(im1, im2, 0.5)
 | 
					            new = ImageChops.blend(im1, im2, 0.5)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -137,7 +133,6 @@ def test_darker_image():
 | 
				
			||||||
    # Arrange
 | 
					    # Arrange
 | 
				
			||||||
    with Image.open("Tests/images/imagedraw_chord_RGB.png") as im1:
 | 
					    with Image.open("Tests/images/imagedraw_chord_RGB.png") as im1:
 | 
				
			||||||
        with Image.open("Tests/images/imagedraw_outline_chord_RGB.png") as im2:
 | 
					        with Image.open("Tests/images/imagedraw_outline_chord_RGB.png") as im2:
 | 
				
			||||||
 | 
					 | 
				
			||||||
            # Act
 | 
					            # Act
 | 
				
			||||||
            new = ImageChops.darker(im1, im2)
 | 
					            new = ImageChops.darker(im1, im2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -149,7 +144,6 @@ def test_darker_pixel():
 | 
				
			||||||
    # Arrange
 | 
					    # Arrange
 | 
				
			||||||
    im1 = hopper()
 | 
					    im1 = hopper()
 | 
				
			||||||
    with Image.open("Tests/images/imagedraw_chord_RGB.png") as im2:
 | 
					    with Image.open("Tests/images/imagedraw_chord_RGB.png") as im2:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Act
 | 
					        # Act
 | 
				
			||||||
        new = ImageChops.darker(im1, im2)
 | 
					        new = ImageChops.darker(im1, im2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -161,7 +155,6 @@ def test_difference():
 | 
				
			||||||
    # Arrange
 | 
					    # Arrange
 | 
				
			||||||
    with Image.open("Tests/images/imagedraw_arc_end_le_start.png") as im1:
 | 
					    with Image.open("Tests/images/imagedraw_arc_end_le_start.png") as im1:
 | 
				
			||||||
        with Image.open("Tests/images/imagedraw_arc_no_loops.png") as im2:
 | 
					        with Image.open("Tests/images/imagedraw_arc_no_loops.png") as im2:
 | 
				
			||||||
 | 
					 | 
				
			||||||
            # Act
 | 
					            # Act
 | 
				
			||||||
            new = ImageChops.difference(im1, im2)
 | 
					            new = ImageChops.difference(im1, im2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -173,7 +166,6 @@ def test_difference_pixel():
 | 
				
			||||||
    # Arrange
 | 
					    # Arrange
 | 
				
			||||||
    im1 = hopper()
 | 
					    im1 = hopper()
 | 
				
			||||||
    with Image.open("Tests/images/imagedraw_polygon_kite_RGB.png") as im2:
 | 
					    with Image.open("Tests/images/imagedraw_polygon_kite_RGB.png") as im2:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Act
 | 
					        # Act
 | 
				
			||||||
        new = ImageChops.difference(im1, im2)
 | 
					        new = ImageChops.difference(im1, im2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -195,7 +187,6 @@ def test_duplicate():
 | 
				
			||||||
def test_invert():
 | 
					def test_invert():
 | 
				
			||||||
    # Arrange
 | 
					    # Arrange
 | 
				
			||||||
    with Image.open("Tests/images/imagedraw_floodfill_RGB.png") as im:
 | 
					    with Image.open("Tests/images/imagedraw_floodfill_RGB.png") as im:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Act
 | 
					        # Act
 | 
				
			||||||
        new = ImageChops.invert(im)
 | 
					        new = ImageChops.invert(im)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -209,7 +200,6 @@ def test_lighter_image():
 | 
				
			||||||
    # Arrange
 | 
					    # Arrange
 | 
				
			||||||
    with Image.open("Tests/images/imagedraw_chord_RGB.png") as im1:
 | 
					    with Image.open("Tests/images/imagedraw_chord_RGB.png") as im1:
 | 
				
			||||||
        with Image.open("Tests/images/imagedraw_outline_chord_RGB.png") as im2:
 | 
					        with Image.open("Tests/images/imagedraw_outline_chord_RGB.png") as im2:
 | 
				
			||||||
 | 
					 | 
				
			||||||
            # Act
 | 
					            # Act
 | 
				
			||||||
            new = ImageChops.lighter(im1, im2)
 | 
					            new = ImageChops.lighter(im1, im2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -221,7 +211,6 @@ def test_lighter_pixel():
 | 
				
			||||||
    # Arrange
 | 
					    # Arrange
 | 
				
			||||||
    im1 = hopper()
 | 
					    im1 = hopper()
 | 
				
			||||||
    with Image.open("Tests/images/imagedraw_chord_RGB.png") as im2:
 | 
					    with Image.open("Tests/images/imagedraw_chord_RGB.png") as im2:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Act
 | 
					        # Act
 | 
				
			||||||
        new = ImageChops.lighter(im1, im2)
 | 
					        new = ImageChops.lighter(im1, im2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -275,7 +264,6 @@ def test_offset():
 | 
				
			||||||
    xoffset = 45
 | 
					    xoffset = 45
 | 
				
			||||||
    yoffset = 20
 | 
					    yoffset = 20
 | 
				
			||||||
    with Image.open("Tests/images/imagedraw_ellipse_RGB.png") as im:
 | 
					    with Image.open("Tests/images/imagedraw_ellipse_RGB.png") as im:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Act
 | 
					        # Act
 | 
				
			||||||
        new = ImageChops.offset(im, xoffset, yoffset)
 | 
					        new = ImageChops.offset(im, xoffset, yoffset)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -292,7 +280,6 @@ def test_screen():
 | 
				
			||||||
    # Arrange
 | 
					    # Arrange
 | 
				
			||||||
    with Image.open("Tests/images/imagedraw_ellipse_RGB.png") as im1:
 | 
					    with Image.open("Tests/images/imagedraw_ellipse_RGB.png") as im1:
 | 
				
			||||||
        with Image.open("Tests/images/imagedraw_floodfill_RGB.png") as im2:
 | 
					        with Image.open("Tests/images/imagedraw_floodfill_RGB.png") as im2:
 | 
				
			||||||
 | 
					 | 
				
			||||||
            # Act
 | 
					            # Act
 | 
				
			||||||
            new = ImageChops.screen(im1, im2)
 | 
					            new = ImageChops.screen(im1, im2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -305,7 +292,6 @@ def test_subtract():
 | 
				
			||||||
    # Arrange
 | 
					    # Arrange
 | 
				
			||||||
    with Image.open("Tests/images/imagedraw_chord_RGB.png") as im1:
 | 
					    with Image.open("Tests/images/imagedraw_chord_RGB.png") as im1:
 | 
				
			||||||
        with Image.open("Tests/images/imagedraw_outline_chord_RGB.png") as im2:
 | 
					        with Image.open("Tests/images/imagedraw_outline_chord_RGB.png") as im2:
 | 
				
			||||||
 | 
					 | 
				
			||||||
            # Act
 | 
					            # Act
 | 
				
			||||||
            new = ImageChops.subtract(im1, im2)
 | 
					            new = ImageChops.subtract(im1, im2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -319,7 +305,6 @@ def test_subtract_scale_offset():
 | 
				
			||||||
    # Arrange
 | 
					    # Arrange
 | 
				
			||||||
    with Image.open("Tests/images/imagedraw_chord_RGB.png") as im1:
 | 
					    with Image.open("Tests/images/imagedraw_chord_RGB.png") as im1:
 | 
				
			||||||
        with Image.open("Tests/images/imagedraw_outline_chord_RGB.png") as im2:
 | 
					        with Image.open("Tests/images/imagedraw_outline_chord_RGB.png") as im2:
 | 
				
			||||||
 | 
					 | 
				
			||||||
            # Act
 | 
					            # Act
 | 
				
			||||||
            new = ImageChops.subtract(im1, im2, scale=2.5, offset=100)
 | 
					            new = ImageChops.subtract(im1, im2, scale=2.5, offset=100)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -332,7 +317,6 @@ def test_subtract_clip():
 | 
				
			||||||
    # Arrange
 | 
					    # Arrange
 | 
				
			||||||
    im1 = hopper()
 | 
					    im1 = hopper()
 | 
				
			||||||
    with Image.open("Tests/images/imagedraw_chord_RGB.png") as im2:
 | 
					    with Image.open("Tests/images/imagedraw_chord_RGB.png") as im2:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Act
 | 
					        # Act
 | 
				
			||||||
        new = ImageChops.subtract(im1, im2)
 | 
					        new = ImageChops.subtract(im1, im2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -344,7 +328,6 @@ def test_subtract_modulo():
 | 
				
			||||||
    # Arrange
 | 
					    # Arrange
 | 
				
			||||||
    with Image.open("Tests/images/imagedraw_chord_RGB.png") as im1:
 | 
					    with Image.open("Tests/images/imagedraw_chord_RGB.png") as im1:
 | 
				
			||||||
        with Image.open("Tests/images/imagedraw_outline_chord_RGB.png") as im2:
 | 
					        with Image.open("Tests/images/imagedraw_outline_chord_RGB.png") as im2:
 | 
				
			||||||
 | 
					 | 
				
			||||||
            # Act
 | 
					            # Act
 | 
				
			||||||
            new = ImageChops.subtract_modulo(im1, im2)
 | 
					            new = ImageChops.subtract_modulo(im1, im2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -358,7 +341,6 @@ def test_subtract_modulo_no_clip():
 | 
				
			||||||
    # Arrange
 | 
					    # Arrange
 | 
				
			||||||
    im1 = hopper()
 | 
					    im1 = hopper()
 | 
				
			||||||
    with Image.open("Tests/images/imagedraw_chord_RGB.png") as im2:
 | 
					    with Image.open("Tests/images/imagedraw_chord_RGB.png") as im2:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Act
 | 
					        # Act
 | 
				
			||||||
        new = ImageChops.subtract_modulo(im1, im2)
 | 
					        new = ImageChops.subtract_modulo(im1, im2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -370,7 +352,6 @@ def test_soft_light():
 | 
				
			||||||
    # Arrange
 | 
					    # Arrange
 | 
				
			||||||
    with Image.open("Tests/images/hopper.png") as im1:
 | 
					    with Image.open("Tests/images/hopper.png") as im1:
 | 
				
			||||||
        with Image.open("Tests/images/hopper-XYZ.png") as im2:
 | 
					        with Image.open("Tests/images/hopper-XYZ.png") as im2:
 | 
				
			||||||
 | 
					 | 
				
			||||||
            # Act
 | 
					            # Act
 | 
				
			||||||
            new = ImageChops.soft_light(im1, im2)
 | 
					            new = ImageChops.soft_light(im1, im2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -383,7 +364,6 @@ def test_hard_light():
 | 
				
			||||||
    # Arrange
 | 
					    # Arrange
 | 
				
			||||||
    with Image.open("Tests/images/hopper.png") as im1:
 | 
					    with Image.open("Tests/images/hopper.png") as im1:
 | 
				
			||||||
        with Image.open("Tests/images/hopper-XYZ.png") as im2:
 | 
					        with Image.open("Tests/images/hopper-XYZ.png") as im2:
 | 
				
			||||||
 | 
					 | 
				
			||||||
            # Act
 | 
					            # Act
 | 
				
			||||||
            new = ImageChops.hard_light(im1, im2)
 | 
					            new = ImageChops.hard_light(im1, im2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -396,7 +376,6 @@ def test_overlay():
 | 
				
			||||||
    # Arrange
 | 
					    # Arrange
 | 
				
			||||||
    with Image.open("Tests/images/hopper.png") as im1:
 | 
					    with Image.open("Tests/images/hopper.png") as im1:
 | 
				
			||||||
        with Image.open("Tests/images/hopper-XYZ.png") as im2:
 | 
					        with Image.open("Tests/images/hopper-XYZ.png") as im2:
 | 
				
			||||||
 | 
					 | 
				
			||||||
            # Act
 | 
					            # Act
 | 
				
			||||||
            new = ImageChops.overlay(im1, im2)
 | 
					            new = ImageChops.overlay(im1, im2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -52,7 +52,6 @@ def test_sanity():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_valueerror():
 | 
					def test_valueerror():
 | 
				
			||||||
    with Image.open("Tests/images/chi.gif") as im:
 | 
					    with Image.open("Tests/images/chi.gif") as im:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        draw = ImageDraw.Draw(im)
 | 
					        draw = ImageDraw.Draw(im)
 | 
				
			||||||
        draw.line((0, 0), fill=(0, 0, 0))
 | 
					        draw.line((0, 0), fill=(0, 0, 0))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -30,7 +30,6 @@ SAFEBLOCK = ImageFile.SAFEBLOCK
 | 
				
			||||||
class TestImageFile:
 | 
					class TestImageFile:
 | 
				
			||||||
    def test_parser(self):
 | 
					    def test_parser(self):
 | 
				
			||||||
        def roundtrip(format):
 | 
					        def roundtrip(format):
 | 
				
			||||||
 | 
					 | 
				
			||||||
            im = hopper("L").resize((1000, 1000), Image.Resampling.NEAREST)
 | 
					            im = hopper("L").resize((1000, 1000), Image.Resampling.NEAREST)
 | 
				
			||||||
            if format in ("MSP", "XBM"):
 | 
					            if format in ("MSP", "XBM"):
 | 
				
			||||||
                im = im.convert("1")
 | 
					                im = im.convert("1")
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -64,7 +64,7 @@ $bmp = New-Object Drawing.Bitmap 200, 200
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            p.communicate()
 | 
					            p.communicate()
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            if not shutil.which("wl-paste"):
 | 
					            if not shutil.which("wl-paste") and not shutil.which("xclip"):
 | 
				
			||||||
                with pytest.raises(
 | 
					                with pytest.raises(
 | 
				
			||||||
                    NotImplementedError,
 | 
					                    NotImplementedError,
 | 
				
			||||||
                    match="wl-paste or xclip is required for"
 | 
					                    match="wl-paste or xclip is required for"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -21,7 +21,6 @@ deformer = Deformer()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_sanity():
 | 
					def test_sanity():
 | 
				
			||||||
 | 
					 | 
				
			||||||
    ImageOps.autocontrast(hopper("L"))
 | 
					    ImageOps.autocontrast(hopper("L"))
 | 
				
			||||||
    ImageOps.autocontrast(hopper("RGB"))
 | 
					    ImageOps.autocontrast(hopper("RGB"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -419,7 +418,6 @@ def test_autocontrast_cutoff():
 | 
				
			||||||
def test_autocontrast_mask_toy_input():
 | 
					def test_autocontrast_mask_toy_input():
 | 
				
			||||||
    # Test the mask argument of autocontrast
 | 
					    # Test the mask argument of autocontrast
 | 
				
			||||||
    with Image.open("Tests/images/bw_gradient.png") as img:
 | 
					    with Image.open("Tests/images/bw_gradient.png") as img:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        rect_mask = Image.new("L", img.size, 0)
 | 
					        rect_mask = Image.new("L", img.size, 0)
 | 
				
			||||||
        draw = ImageDraw.Draw(rect_mask)
 | 
					        draw = ImageDraw.Draw(rect_mask)
 | 
				
			||||||
        x0 = img.size[0] // 4
 | 
					        x0 = img.size[0] // 4
 | 
				
			||||||
| 
						 | 
					@ -439,7 +437,6 @@ def test_autocontrast_mask_toy_input():
 | 
				
			||||||
def test_autocontrast_mask_real_input():
 | 
					def test_autocontrast_mask_real_input():
 | 
				
			||||||
    # Test the autocontrast with a rectangular mask
 | 
					    # Test the autocontrast with a rectangular mask
 | 
				
			||||||
    with Image.open("Tests/images/iptc.jpg") as img:
 | 
					    with Image.open("Tests/images/iptc.jpg") as img:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        rect_mask = Image.new("L", img.size, 0)
 | 
					        rect_mask = Image.new("L", img.size, 0)
 | 
				
			||||||
        draw = ImageDraw.Draw(rect_mask)
 | 
					        draw = ImageDraw.Draw(rect_mask)
 | 
				
			||||||
        x0, y0 = img.size[0] // 2, img.size[1] // 2
 | 
					        x0, y0 = img.size[0] // 2, img.size[1] // 2
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,7 +6,6 @@ from .helper import assert_image_equal, assert_image_equal_tofile
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_sanity():
 | 
					def test_sanity():
 | 
				
			||||||
 | 
					 | 
				
			||||||
    palette = ImagePalette.ImagePalette("RGB", list(range(256)) * 3)
 | 
					    palette = ImagePalette.ImagePalette("RGB", list(range(256)) * 3)
 | 
				
			||||||
    assert len(palette.colors) == 256
 | 
					    assert len(palette.colors) == 256
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -23,7 +22,6 @@ def test_reload():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_getcolor():
 | 
					def test_getcolor():
 | 
				
			||||||
 | 
					 | 
				
			||||||
    palette = ImagePalette.ImagePalette()
 | 
					    palette = ImagePalette.ImagePalette()
 | 
				
			||||||
    assert len(palette.palette) == 0
 | 
					    assert len(palette.palette) == 0
 | 
				
			||||||
    assert len(palette.colors) == 0
 | 
					    assert len(palette.colors) == 0
 | 
				
			||||||
| 
						 | 
					@ -84,7 +82,6 @@ def test_getcolor_not_special(index, palette):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_file(tmp_path):
 | 
					def test_file(tmp_path):
 | 
				
			||||||
 | 
					 | 
				
			||||||
    palette = ImagePalette.ImagePalette("RGB", list(range(256)) * 3)
 | 
					    palette = ImagePalette.ImagePalette("RGB", list(range(256)) * 3)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    f = str(tmp_path / "temp.lut")
 | 
					    f = str(tmp_path / "temp.lut")
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,7 +8,6 @@ from PIL import Image, ImagePath
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_path():
 | 
					def test_path():
 | 
				
			||||||
 | 
					 | 
				
			||||||
    p = ImagePath.Path(list(range(10)))
 | 
					    p = ImagePath.Path(list(range(10)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # sequence interface
 | 
					    # sequence interface
 | 
				
			||||||
| 
						 | 
					@ -58,10 +57,7 @@ def test_path():
 | 
				
			||||||
    assert list(p) == [(0.0, 1.0)]
 | 
					    assert list(p) == [(0.0, 1.0)]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    arr = array.array("f", [0, 1])
 | 
					    arr = array.array("f", [0, 1])
 | 
				
			||||||
    if hasattr(arr, "tobytes"):
 | 
					    p = ImagePath.Path(arr.tobytes())
 | 
				
			||||||
        p = ImagePath.Path(arr.tobytes())
 | 
					 | 
				
			||||||
    else:
 | 
					 | 
				
			||||||
        p = ImagePath.Path(arr.tostring())
 | 
					 | 
				
			||||||
    assert list(p) == [(0.0, 1.0)]
 | 
					    assert list(p) == [(0.0, 1.0)]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,7 +6,6 @@ from .helper import assert_image_equal, hopper, skip_unless_feature
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_sanity(tmp_path):
 | 
					def test_sanity(tmp_path):
 | 
				
			||||||
 | 
					 | 
				
			||||||
    test_file = str(tmp_path / "temp.im")
 | 
					    test_file = str(tmp_path / "temp.im")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    im = hopper("RGB")
 | 
					    im = hopper("RGB")
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,7 +6,6 @@ from .helper import hopper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_sanity():
 | 
					def test_sanity():
 | 
				
			||||||
 | 
					 | 
				
			||||||
    im = hopper()
 | 
					    im = hopper()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    st = ImageStat.Stat(im)
 | 
					    st = ImageStat.Stat(im)
 | 
				
			||||||
| 
						 | 
					@ -31,7 +30,6 @@ def test_sanity():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_hopper():
 | 
					def test_hopper():
 | 
				
			||||||
 | 
					 | 
				
			||||||
    im = hopper()
 | 
					    im = hopper()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    st = ImageStat.Stat(im)
 | 
					    st = ImageStat.Stat(im)
 | 
				
			||||||
| 
						 | 
					@ -45,7 +43,6 @@ def test_hopper():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_constant():
 | 
					def test_constant():
 | 
				
			||||||
 | 
					 | 
				
			||||||
    im = Image.new("L", (128, 128), 128)
 | 
					    im = Image.new("L", (128, 128), 128)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    st = ImageStat.Stat(im)
 | 
					    st = ImageStat.Stat(im)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,7 +4,6 @@ from PIL import Image
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_setmode():
 | 
					def test_setmode():
 | 
				
			||||||
 | 
					 | 
				
			||||||
    im = Image.new("L", (1, 1), 255)
 | 
					    im = Image.new("L", (1, 1), 255)
 | 
				
			||||||
    im.im.setmode("1")
 | 
					    im.im.setmode("1")
 | 
				
			||||||
    assert im.im.getpixel((0, 0)) == 255
 | 
					    assert im.im.getpixel((0, 0)) == 255
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -42,7 +42,6 @@ def test_basic(tmp_path, mode):
 | 
				
			||||||
    im_in.save(filename)
 | 
					    im_in.save(filename)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open(filename) as im_out:
 | 
					    with Image.open(filename) as im_out:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        verify(im_in)
 | 
					        verify(im_in)
 | 
				
			||||||
        verify(im_out)
 | 
					        verify(im_out)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -87,7 +86,6 @@ def test_tobytes():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_convert():
 | 
					def test_convert():
 | 
				
			||||||
 | 
					 | 
				
			||||||
    im = original.copy()
 | 
					    im = original.copy()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    verify(im.convert("I;16"))
 | 
					    verify(im.convert("I;16"))
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -235,7 +235,6 @@ def test_no_resource_warning_for_numpy_array():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    test_file = "Tests/images/hopper.png"
 | 
					    test_file = "Tests/images/hopper.png"
 | 
				
			||||||
    with Image.open(test_file) as im:
 | 
					    with Image.open(test_file) as im:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Act/Assert
 | 
					        # Act/Assert
 | 
				
			||||||
        with warnings.catch_warnings():
 | 
					        with warnings.catch_warnings():
 | 
				
			||||||
            array(im)
 | 
					            array(im)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -88,9 +88,8 @@ def test_parsing():
 | 
				
			||||||
            b"D:20180729214124+08'00'": "20180729134124",
 | 
					            b"D:20180729214124+08'00'": "20180729134124",
 | 
				
			||||||
            b"D:20180729214124-05'00'": "20180730024124",
 | 
					            b"D:20180729214124-05'00'": "20180730024124",
 | 
				
			||||||
        }.items():
 | 
					        }.items():
 | 
				
			||||||
            d = PdfParser.get_value(b"<</" + name.encode() + b" (" + date + b")>>", 0)[
 | 
					            b = b"<</" + name.encode() + b" (" + date + b")>>"
 | 
				
			||||||
                0
 | 
					            d = PdfParser.get_value(b, 0)[0]
 | 
				
			||||||
            ]
 | 
					 | 
				
			||||||
            assert time.strftime("%Y%m%d%H%M%S", getattr(d, name)) == value
 | 
					            assert time.strftime("%Y%m%d%H%M%S", getattr(d, name)) == value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -89,7 +89,6 @@ def test_pickle_la_mode_with_palette(tmp_path):
 | 
				
			||||||
def test_pickle_tell():
 | 
					def test_pickle_tell():
 | 
				
			||||||
    # Arrange
 | 
					    # Arrange
 | 
				
			||||||
    with Image.open("Tests/images/hopper.webp") as image:
 | 
					    with Image.open("Tests/images/hopper.webp") as image:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Act: roundtrip
 | 
					        # Act: roundtrip
 | 
				
			||||||
        unpickled_image = pickle.loads(pickle.dumps(image))
 | 
					        unpickled_image = pickle.loads(pickle.dumps(image))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,7 +6,7 @@ with warnings.catch_warnings():
 | 
				
			||||||
    warnings.simplefilter("ignore", category=DeprecationWarning)
 | 
					    warnings.simplefilter("ignore", category=DeprecationWarning)
 | 
				
			||||||
    from PIL import ImageQt
 | 
					    from PIL import ImageQt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .helper import assert_image_equal, assert_image_equal_tofile, hopper
 | 
					from .helper import assert_image_equal_tofile, assert_image_similar, hopper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if ImageQt.qt_is_installed:
 | 
					if ImageQt.qt_is_installed:
 | 
				
			||||||
    from PIL.ImageQt import QPixmap
 | 
					    from PIL.ImageQt import QPixmap
 | 
				
			||||||
| 
						 | 
					@ -48,7 +48,7 @@ if ImageQt.qt_is_installed:
 | 
				
			||||||
def roundtrip(expected):
 | 
					def roundtrip(expected):
 | 
				
			||||||
    result = ImageQt.fromqpixmap(ImageQt.toqpixmap(expected))
 | 
					    result = ImageQt.fromqpixmap(ImageQt.toqpixmap(expected))
 | 
				
			||||||
    # Qt saves all pixmaps as rgb
 | 
					    # Qt saves all pixmaps as rgb
 | 
				
			||||||
    assert_image_equal(result, expected.convert("RGB"))
 | 
					    assert_image_similar(result, expected.convert("RGB"), 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@pytest.mark.skipif(not ImageQt.qt_is_installed, reason="Qt bindings are not installed")
 | 
					@pytest.mark.skipif(not ImageQt.qt_is_installed, reason="Qt bindings are not installed")
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,7 +7,6 @@ from .helper import hopper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def _test_equal(num, denom, target):
 | 
					def _test_equal(num, denom, target):
 | 
				
			||||||
 | 
					 | 
				
			||||||
    t = IFDRational(num, denom)
 | 
					    t = IFDRational(num, denom)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    assert target == t
 | 
					    assert target == t
 | 
				
			||||||
| 
						 | 
					@ -15,7 +14,6 @@ def _test_equal(num, denom, target):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_sanity():
 | 
					def test_sanity():
 | 
				
			||||||
 | 
					 | 
				
			||||||
    _test_equal(1, 1, 1)
 | 
					    _test_equal(1, 1, 1)
 | 
				
			||||||
    _test_equal(1, 1, Fraction(1, 1))
 | 
					    _test_equal(1, 1, Fraction(1, 1))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,7 +9,6 @@ test_file = "Tests/images/hopper.webp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@skip_unless_feature("webp")
 | 
					@skip_unless_feature("webp")
 | 
				
			||||||
class TestWebPLeaks(PillowLeakTestCase):
 | 
					class TestWebPLeaks(PillowLeakTestCase):
 | 
				
			||||||
 | 
					 | 
				
			||||||
    mem_limit = 3 * 1024  # kb
 | 
					    mem_limit = 3 * 1024  # kb
 | 
				
			||||||
    iterations = 100
 | 
					    iterations = 100
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,5 +8,5 @@ if [ ! -f $archive.tar.gz ]; then
 | 
				
			||||||
    wget -O $archive.tar.gz $url
 | 
					    wget -O $archive.tar.gz $url
 | 
				
			||||||
fi
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
rm -r $archive
 | 
					rmdir $archive
 | 
				
			||||||
tar -xvzf $archive.tar.gz
 | 
					tar -xvzf $archive.tar.gz
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,15 +1,12 @@
 | 
				
			||||||
#!/bin/bash
 | 
					#!/usr/bin/env bash
 | 
				
			||||||
# install extra test images
 | 
					# install extra test images
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Use SVN to just fetch a single Git subdirectory
 | 
					archive=test-images-main
 | 
				
			||||||
svn_export()
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    if [ ! -z $1 ]; then
 | 
					 | 
				
			||||||
        echo ""
 | 
					 | 
				
			||||||
        echo "Retrying svn export..."
 | 
					 | 
				
			||||||
        echo ""
 | 
					 | 
				
			||||||
    fi
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    svn export --force https://github.com/python-pillow/pillow-depends/trunk/test_images ../Tests/images
 | 
					./download-and-extract.sh $archive https://github.com/python-pillow/test-images/archive/main.tar.gz
 | 
				
			||||||
}
 | 
					
 | 
				
			||||||
svn_export || svn_export retry || svn_export retry || svn_export retry
 | 
					mv $archive/* ../Tests/images/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Cleanup old tarball and empty directory
 | 
				
			||||||
 | 
					rm $archive.tar.gz
 | 
				
			||||||
 | 
					rmdir $archive
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
#!/bin/bash
 | 
					#!/bin/bash
 | 
				
			||||||
# install libimagequant
 | 
					# install libimagequant
 | 
				
			||||||
 | 
					
 | 
				
			||||||
archive=libimagequant-4.0.4
 | 
					archive=libimagequant-4.1.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz
 | 
					./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,7 +2,7 @@
 | 
				
			||||||
# install raqm
 | 
					# install raqm
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
archive=libraqm-0.9.0
 | 
					archive=libraqm-0.10.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz
 | 
					./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
#!/bin/bash
 | 
					#!/bin/bash
 | 
				
			||||||
# install webp
 | 
					# install webp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
archive=libwebp-1.2.4
 | 
					archive=libwebp-1.3.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz
 | 
					./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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-2022 by Alex Clark and contributors
 | 
					    Copyright © 2010-2023 by Jeffrey A. Clark (Alex) and contributors
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Like PIL, Pillow is licensed under the open source PIL
 | 
					Like PIL, Pillow is licensed under the open source PIL
 | 
				
			||||||
Software License:
 | 
					Software License:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										10
									
								
								docs/conf.py
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								docs/conf.py
									
									
									
									
									
								
							| 
						 | 
					@ -52,8 +52,10 @@ master_doc = "index"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# General information about the project.
 | 
					# General information about the project.
 | 
				
			||||||
project = "Pillow (PIL Fork)"
 | 
					project = "Pillow (PIL Fork)"
 | 
				
			||||||
copyright = "1995-2011 Fredrik Lundh, 2010-2022 Alex Clark and Contributors"
 | 
					copyright = (
 | 
				
			||||||
author = "Fredrik Lundh, Alex Clark and Contributors"
 | 
					    "1995-2011 Fredrik Lundh, 2010-2023 Jeffrey A. Clark (Alex) and contributors"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					author = "Fredrik Lundh, Jeffrey A. Clark (Alex), contributors"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# The version info for the project you're documenting, acts as replacement for
 | 
					# The version info for the project you're documenting, acts as replacement for
 | 
				
			||||||
# |version| and |release|, also used in various other places throughout the
 | 
					# |version| and |release|, also used in various other places throughout the
 | 
				
			||||||
| 
						 | 
					@ -243,7 +245,7 @@ latex_documents = [
 | 
				
			||||||
        master_doc,
 | 
					        master_doc,
 | 
				
			||||||
        "PillowPILFork.tex",
 | 
					        "PillowPILFork.tex",
 | 
				
			||||||
        "Pillow (PIL Fork) Documentation",
 | 
					        "Pillow (PIL Fork) Documentation",
 | 
				
			||||||
        "Alex Clark",
 | 
					        "Jeffrey A. Clark (Alex)",
 | 
				
			||||||
        "manual",
 | 
					        "manual",
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
| 
						 | 
					@ -293,7 +295,7 @@ texinfo_documents = [
 | 
				
			||||||
        "Pillow (PIL Fork) Documentation",
 | 
					        "Pillow (PIL Fork) Documentation",
 | 
				
			||||||
        author,
 | 
					        author,
 | 
				
			||||||
        "PillowPILFork",
 | 
					        "PillowPILFork",
 | 
				
			||||||
        "Pillow is the friendly PIL fork by Alex Clark and Contributors.",
 | 
					        "Pillow is the friendly PIL fork by Jeffrey A. Clark (Alex) and contributors.",
 | 
				
			||||||
        "Miscellaneous",
 | 
					        "Miscellaneous",
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -74,40 +74,18 @@ Constants
 | 
				
			||||||
A number of constants have been deprecated and will be removed in Pillow 10.0.0
 | 
					A number of constants have been deprecated and will be removed in Pillow 10.0.0
 | 
				
			||||||
(2023-07-01). Instead, ``enum.IntEnum`` classes have been added.
 | 
					(2023-07-01). Instead, ``enum.IntEnum`` classes have been added.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. note::
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Additional ``Image`` constants were deprecated in Pillow 9.1.0, but that
 | 
				
			||||||
 | 
					    was reversed in Pillow 9.4.0 and those constants will now remain available.
 | 
				
			||||||
 | 
					    See :ref:`restored-image-constants`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
=====================================================  ============================================================
 | 
					=====================================================  ============================================================
 | 
				
			||||||
Deprecated                                             Use instead
 | 
					Deprecated                                             Use instead
 | 
				
			||||||
=====================================================  ============================================================
 | 
					=====================================================  ============================================================
 | 
				
			||||||
``Image.NONE``                                         Either ``Image.Dither.NONE`` or ``Image.Resampling.NEAREST``
 | 
					``Image.LINEAR``                                       ``Image.BILINEAR`` or ``Image.Resampling.BILINEAR``
 | 
				
			||||||
``Image.NEAREST``                                      Either ``Image.Dither.NONE`` or ``Image.Resampling.NEAREST``
 | 
					``Image.CUBIC``                                        ``Image.BICUBIC`` or ``Image.Resampling.BICUBIC``
 | 
				
			||||||
``Image.ORDERED``                                      ``Image.Dither.ORDERED``
 | 
					``Image.ANTIALIAS``                                    ``Image.LANCZOS`` or ``Image.Resampling.LANCZOS``
 | 
				
			||||||
``Image.RASTERIZE``                                    ``Image.Dither.RASTERIZE``
 | 
					 | 
				
			||||||
``Image.FLOYDSTEINBERG``                               ``Image.Dither.FLOYDSTEINBERG``
 | 
					 | 
				
			||||||
``Image.WEB``                                          ``Image.Palette.WEB``
 | 
					 | 
				
			||||||
``Image.ADAPTIVE``                                     ``Image.Palette.ADAPTIVE``
 | 
					 | 
				
			||||||
``Image.AFFINE``                                       ``Image.Transform.AFFINE``
 | 
					 | 
				
			||||||
``Image.EXTENT``                                       ``Image.Transform.EXTENT``
 | 
					 | 
				
			||||||
``Image.PERSPECTIVE``                                  ``Image.Transform.PERSPECTIVE``
 | 
					 | 
				
			||||||
``Image.QUAD``                                         ``Image.Transform.QUAD``
 | 
					 | 
				
			||||||
``Image.MESH``                                         ``Image.Transform.MESH``
 | 
					 | 
				
			||||||
``Image.FLIP_LEFT_RIGHT``                              ``Image.Transpose.FLIP_LEFT_RIGHT``
 | 
					 | 
				
			||||||
``Image.FLIP_TOP_BOTTOM``                              ``Image.Transpose.FLIP_TOP_BOTTOM``
 | 
					 | 
				
			||||||
``Image.ROTATE_90``                                    ``Image.Transpose.ROTATE_90``
 | 
					 | 
				
			||||||
``Image.ROTATE_180``                                   ``Image.Transpose.ROTATE_180``
 | 
					 | 
				
			||||||
``Image.ROTATE_270``                                   ``Image.Transpose.ROTATE_270``
 | 
					 | 
				
			||||||
``Image.TRANSPOSE``                                    ``Image.Transpose.TRANSPOSE``
 | 
					 | 
				
			||||||
``Image.TRANSVERSE``                                   ``Image.Transpose.TRANSVERSE``
 | 
					 | 
				
			||||||
``Image.BOX``                                          ``Image.Resampling.BOX``
 | 
					 | 
				
			||||||
``Image.BILINEAR``                                     ``Image.Resampling.BILINEAR``
 | 
					 | 
				
			||||||
``Image.LINEAR``                                       ``Image.Resampling.BILINEAR``
 | 
					 | 
				
			||||||
``Image.HAMMING``                                      ``Image.Resampling.HAMMING``
 | 
					 | 
				
			||||||
``Image.BICUBIC``                                      ``Image.Resampling.BICUBIC``
 | 
					 | 
				
			||||||
``Image.CUBIC``                                        ``Image.Resampling.BICUBIC``
 | 
					 | 
				
			||||||
``Image.LANCZOS``                                      ``Image.Resampling.LANCZOS``
 | 
					 | 
				
			||||||
``Image.ANTIALIAS``                                    ``Image.Resampling.LANCZOS``
 | 
					 | 
				
			||||||
``Image.MEDIANCUT``                                    ``Image.Quantize.MEDIANCUT``
 | 
					 | 
				
			||||||
``Image.MAXCOVERAGE``                                  ``Image.Quantize.MAXCOVERAGE``
 | 
					 | 
				
			||||||
``Image.FASTOCTREE``                                   ``Image.Quantize.FASTOCTREE``
 | 
					 | 
				
			||||||
``Image.LIBIMAGEQUANT``                                ``Image.Quantize.LIBIMAGEQUANT``
 | 
					 | 
				
			||||||
``ImageCms.INTENT_PERCEPTUAL``                         ``ImageCms.Intent.PERCEPTUAL``
 | 
					``ImageCms.INTENT_PERCEPTUAL``                         ``ImageCms.Intent.PERCEPTUAL``
 | 
				
			||||||
``ImageCms.INTENT_RELATIVE_COLORMETRIC``               ``ImageCms.Intent.RELATIVE_COLORMETRIC``
 | 
					``ImageCms.INTENT_RELATIVE_COLORMETRIC``               ``ImageCms.Intent.RELATIVE_COLORMETRIC``
 | 
				
			||||||
``ImageCms.INTENT_SATURATION``                         ``ImageCms.Intent.SATURATION``
 | 
					``ImageCms.INTENT_SATURATION``                         ``ImageCms.Intent.SATURATION``
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -31,7 +31,7 @@ INT32 and a 32-bit floating point pixel has the range of FLOAT32. The current re
 | 
				
			||||||
supports the following standard modes:
 | 
					supports the following standard modes:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    * ``1`` (1-bit pixels, black and white, stored with one pixel per byte)
 | 
					    * ``1`` (1-bit pixels, black and white, stored with one pixel per byte)
 | 
				
			||||||
    * ``L`` (8-bit pixels, black and white)
 | 
					    * ``L`` (8-bit pixels, grayscale)
 | 
				
			||||||
    * ``P`` (8-bit pixels, mapped to any other mode using a color palette)
 | 
					    * ``P`` (8-bit pixels, mapped to any other mode using a color palette)
 | 
				
			||||||
    * ``RGB`` (3x8-bit pixels, true color)
 | 
					    * ``RGB`` (3x8-bit pixels, true color)
 | 
				
			||||||
    * ``RGBA`` (4x8-bit pixels, true color with transparency mask)
 | 
					    * ``RGBA`` (4x8-bit pixels, true color with transparency mask)
 | 
				
			||||||
| 
						 | 
					@ -148,44 +148,44 @@ pixel, the Python Imaging Library provides different resampling *filters*.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.. py:currentmodule:: PIL.Image
 | 
					.. py:currentmodule:: PIL.Image
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.. data:: NEAREST
 | 
					.. data:: Resampling.NEAREST
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Pick one nearest pixel from the input image. Ignore all other input pixels.
 | 
					    Pick one nearest pixel from the input image. Ignore all other input pixels.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.. data:: BOX
 | 
					.. data:: Resampling.BOX
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Each pixel of source image contributes to one pixel of the
 | 
					    Each pixel of source image contributes to one pixel of the
 | 
				
			||||||
    destination image with identical weights.
 | 
					    destination image with identical weights.
 | 
				
			||||||
    For upscaling is equivalent of :data:`NEAREST`.
 | 
					    For upscaling is equivalent of :data:`Resampling.NEAREST`.
 | 
				
			||||||
    This filter can only be used with the :py:meth:`~PIL.Image.Image.resize`
 | 
					    This filter can only be used with the :py:meth:`~PIL.Image.Image.resize`
 | 
				
			||||||
    and :py:meth:`~PIL.Image.Image.thumbnail` methods.
 | 
					    and :py:meth:`~PIL.Image.Image.thumbnail` methods.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    .. versionadded:: 3.4.0
 | 
					    .. versionadded:: 3.4.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.. data:: BILINEAR
 | 
					.. data:: Resampling.BILINEAR
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    For resize calculate the output pixel value using linear interpolation
 | 
					    For resize calculate the output pixel value using linear interpolation
 | 
				
			||||||
    on all pixels that may contribute to the output value.
 | 
					    on all pixels that may contribute to the output value.
 | 
				
			||||||
    For other transformations linear interpolation over a 2x2 environment
 | 
					    For other transformations linear interpolation over a 2x2 environment
 | 
				
			||||||
    in the input image is used.
 | 
					    in the input image is used.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.. data:: HAMMING
 | 
					.. data:: Resampling.HAMMING
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Produces a sharper image than :data:`BILINEAR`, doesn't have dislocations
 | 
					    Produces a sharper image than :data:`Resampling.BILINEAR`, doesn't have
 | 
				
			||||||
    on local level like with :data:`BOX`.
 | 
					    dislocations on local level like with :data:`Resampling.BOX`.
 | 
				
			||||||
    This filter can only be used with the :py:meth:`~PIL.Image.Image.resize`
 | 
					    This filter can only be used with the :py:meth:`~PIL.Image.Image.resize`
 | 
				
			||||||
    and :py:meth:`~PIL.Image.Image.thumbnail` methods.
 | 
					    and :py:meth:`~PIL.Image.Image.thumbnail` methods.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    .. versionadded:: 3.4.0
 | 
					    .. versionadded:: 3.4.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.. data:: BICUBIC
 | 
					.. data:: Resampling.BICUBIC
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    For resize calculate the output pixel value using cubic interpolation
 | 
					    For resize calculate the output pixel value using cubic interpolation
 | 
				
			||||||
    on all pixels that may contribute to the output value.
 | 
					    on all pixels that may contribute to the output value.
 | 
				
			||||||
    For other transformations cubic interpolation over a 4x4 environment
 | 
					    For other transformations cubic interpolation over a 4x4 environment
 | 
				
			||||||
    in the input image is used.
 | 
					    in the input image is used.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.. data:: LANCZOS
 | 
					.. data:: Resampling.LANCZOS
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Calculate the output pixel value using a high-quality Lanczos filter (a
 | 
					    Calculate the output pixel value using a high-quality Lanczos filter (a
 | 
				
			||||||
    truncated sinc) on all pixels that may contribute to the output value.
 | 
					    truncated sinc) on all pixels that may contribute to the output value.
 | 
				
			||||||
| 
						 | 
					@ -198,19 +198,19 @@ pixel, the Python Imaging Library provides different resampling *filters*.
 | 
				
			||||||
Filters comparison table
 | 
					Filters comparison table
 | 
				
			||||||
~~~~~~~~~~~~~~~~~~~~~~~~
 | 
					~~~~~~~~~~~~~~~~~~~~~~~~
 | 
				
			||||||
 | 
					
 | 
				
			||||||
+----------------+-------------+-----------+-------------+
 | 
					+---------------------------+-------------+-----------+-------------+
 | 
				
			||||||
| Filter         | Downscaling | Upscaling | Performance |
 | 
					| Filter                    | Downscaling | Upscaling | Performance |
 | 
				
			||||||
|                | quality     | quality   |             |
 | 
					|                           | quality     | quality   |             |
 | 
				
			||||||
+================+=============+===========+=============+
 | 
					+===========================+=============+===========+=============+
 | 
				
			||||||
|:data:`NEAREST` |             |           | ⭐⭐⭐⭐⭐  |
 | 
					|:data:`Resampling.NEAREST` |             |           | ⭐⭐⭐⭐⭐  |
 | 
				
			||||||
+----------------+-------------+-----------+-------------+
 | 
					+---------------------------+-------------+-----------+-------------+
 | 
				
			||||||
|:data:`BOX`     | ⭐          |           | ⭐⭐⭐⭐    |
 | 
					|:data:`Resampling.BOX`     | ⭐          |           | ⭐⭐⭐⭐    |
 | 
				
			||||||
+----------------+-------------+-----------+-------------+
 | 
					+---------------------------+-------------+-----------+-------------+
 | 
				
			||||||
|:data:`BILINEAR`| ⭐          | ⭐        | ⭐⭐⭐      |
 | 
					|:data:`Resampling.BILINEAR`| ⭐          | ⭐        | ⭐⭐⭐      |
 | 
				
			||||||
+----------------+-------------+-----------+-------------+
 | 
					+---------------------------+-------------+-----------+-------------+
 | 
				
			||||||
|:data:`HAMMING` | ⭐⭐        |           | ⭐⭐⭐      |
 | 
					|:data:`Resampling.HAMMING` | ⭐⭐        |           | ⭐⭐⭐      |
 | 
				
			||||||
+----------------+-------------+-----------+-------------+
 | 
					+---------------------------+-------------+-----------+-------------+
 | 
				
			||||||
|:data:`BICUBIC` | ⭐⭐⭐      | ⭐⭐⭐    | ⭐⭐        |
 | 
					|:data:`Resampling.BICUBIC` | ⭐⭐⭐      | ⭐⭐⭐    | ⭐⭐        |
 | 
				
			||||||
+----------------+-------------+-----------+-------------+
 | 
					+---------------------------+-------------+-----------+-------------+
 | 
				
			||||||
|:data:`LANCZOS` | ⭐⭐⭐⭐    | ⭐⭐⭐⭐  | ⭐          |
 | 
					|:data:`Resampling.LANCZOS` | ⭐⭐⭐⭐    | ⭐⭐⭐⭐  | ⭐          |
 | 
				
			||||||
+----------------+-------------+-----------+-------------+
 | 
					+---------------------------+-------------+-----------+-------------+
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1104,7 +1104,7 @@ using the general tags available through tiffinfo.
 | 
				
			||||||
    Either an integer or a float.
 | 
					    Either an integer or a float.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**dpi**
 | 
					**dpi**
 | 
				
			||||||
    A tuple of (x_resolution, y_resolution), with inches as the resolution
 | 
					    A tuple of ``(x_resolution, y_resolution)``, with inches as the resolution
 | 
				
			||||||
    unit. For consistency with other image formats, the x and y resolutions
 | 
					    unit. For consistency with other image formats, the x and y resolutions
 | 
				
			||||||
    of the dpi will be rounded to the nearest integer.
 | 
					    of the dpi will be rounded to the nearest integer.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1126,7 +1126,7 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options:
 | 
				
			||||||
    If present and true, instructs the WebP writer to use lossless compression.
 | 
					    If present and true, instructs the WebP writer to use lossless compression.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**quality**
 | 
					**quality**
 | 
				
			||||||
    Integer, 1-100, Defaults to 80. For lossy, 0 gives the smallest
 | 
					    Integer, 0-100, Defaults to 80. For lossy, 0 gives the smallest
 | 
				
			||||||
    size and 100 the largest. For lossless, this parameter is the amount
 | 
					    size and 100 the largest. For lossless, this parameter is the amount
 | 
				
			||||||
    of effort put into the compression: 0 is the fastest, but gives larger
 | 
					    of effort put into the compression: 0 is the fastest, but gives larger
 | 
				
			||||||
    files compared to the slowest, but best, 100.
 | 
					    files compared to the slowest, but best, 100.
 | 
				
			||||||
| 
						 | 
					@ -1147,6 +1147,10 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options:
 | 
				
			||||||
    The exif data to include in the saved file. Only supported if
 | 
					    The exif data to include in the saved file. Only supported if
 | 
				
			||||||
    the system WebP library was built with webpmux support.
 | 
					    the system WebP library was built with webpmux support.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**xmp**
 | 
				
			||||||
 | 
					    The XMP data to include in the saved file. Only supported if
 | 
				
			||||||
 | 
					    the system WebP library was built with webpmux support.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Saving sequences
 | 
					Saving sequences
 | 
				
			||||||
~~~~~~~~~~~~~~~~
 | 
					~~~~~~~~~~~~~~~~
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1493,6 +1497,11 @@ The :py:meth:`~PIL.Image.Image.save` method can take the following keyword argum
 | 
				
			||||||
    image, will determine the physical dimensions of the page that will be
 | 
					    image, will determine the physical dimensions of the page that will be
 | 
				
			||||||
    saved in the PDF.
 | 
					    saved in the PDF.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**dpi**
 | 
				
			||||||
 | 
					    A tuple of ``(x_resolution, y_resolution)``, with inches as the resolution
 | 
				
			||||||
 | 
					    unit. If both the ``resolution`` parameter and the ``dpi`` parameter are
 | 
				
			||||||
 | 
					    present, ``resolution`` will be ignored.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**title**
 | 
					**title**
 | 
				
			||||||
    The document’s title. If not appending to an existing PDF file, this will
 | 
					    The document’s title. If not appending to an existing PDF file, this will
 | 
				
			||||||
    default to the filename.
 | 
					    default to the filename.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
Pillow
 | 
					Pillow
 | 
				
			||||||
======
 | 
					======
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Pillow is the friendly PIL fork by `Alex Clark and Contributors <https://github.com/python-pillow/Pillow/graphs/contributors>`_. PIL is the Python Imaging Library by Fredrik Lundh and Contributors.
 | 
					Pillow is the friendly PIL fork by `Jeffrey A. Clark (Alex) and contributors <https://github.com/python-pillow/Pillow/graphs/contributors>`_. PIL is the Python Imaging Library by Fredrik Lundh and contributors.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Pillow for enterprise is available via the Tidelift Subscription. `Learn more <https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=docs&utm_campaign=enterprise>`_.
 | 
					Pillow for enterprise is available via the Tidelift Subscription. `Learn more <https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=docs&utm_campaign=enterprise>`_.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -73,6 +73,22 @@ Pillow for enterprise is available via the Tidelift Subscription. `Learn more <h
 | 
				
			||||||
   :target: https://bestpractices.coreinfrastructure.org/projects/6331
 | 
					   :target: https://bestpractices.coreinfrastructure.org/projects/6331
 | 
				
			||||||
   :alt: OpenSSF Best Practices
 | 
					   :alt: OpenSSF Best Practices
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. image:: https://badges.gitter.im/python-pillow/Pillow.svg
 | 
				
			||||||
 | 
					   :target: https://gitter.im/python-pillow/Pillow?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
 | 
				
			||||||
 | 
					   :alt: Join the chat at https://gitter.im/python-pillow/Pillow
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. image:: https://img.shields.io/badge/tweet-on%20Twitter-00aced.svg
 | 
				
			||||||
 | 
					   :target: https://twitter.com/PythonPillow
 | 
				
			||||||
 | 
					   :alt: Follow on https://twitter.com/PythonPillow
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. image:: https://img.shields.io/badge/publish-on%20Mastodon-595aff.svg
 | 
				
			||||||
 | 
					   :target: https://fosstodon.org/@pillow
 | 
				
			||||||
 | 
					   :alt: Follow on https://fosstodon.org/@pillow
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. raw:: html
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   <link rel="me" href="https://fosstodon.org/@pillow">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Overview
 | 
					Overview
 | 
				
			||||||
========
 | 
					========
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -169,7 +169,7 @@ Many of Pillow's features require external libraries:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* **libimagequant** provides improved color quantization
 | 
					* **libimagequant** provides improved color quantization
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  * Pillow has been tested with libimagequant **2.6-4.0.4**
 | 
					  * Pillow has been tested with libimagequant **2.6-4.1**
 | 
				
			||||||
  * Libimagequant is licensed GPLv3, which is more restrictive than
 | 
					  * Libimagequant is licensed GPLv3, which is more restrictive than
 | 
				
			||||||
    the Pillow license, therefore we will not be distributing binaries
 | 
					    the Pillow license, therefore we will not be distributing binaries
 | 
				
			||||||
    with libimagequant support enabled.
 | 
					    with libimagequant support enabled.
 | 
				
			||||||
| 
						 | 
					@ -369,21 +369,21 @@ Build Options
 | 
				
			||||||
  available, as many as are present.
 | 
					  available, as many as are present.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* Build flags: ``--disable-zlib``, ``--disable-jpeg``,
 | 
					* Build flags: ``--disable-zlib``, ``--disable-jpeg``,
 | 
				
			||||||
  ``--disable-tiff``, ``--disable-freetype``, ``--disable-lcms``,
 | 
					  ``--disable-tiff``, ``--disable-freetype``, ``--disable-raqm``,
 | 
				
			||||||
  ``--disable-webp``, ``--disable-webpmux``, ``--disable-jpeg2000``,
 | 
					  ``--disable-lcms``, ``--disable-webp``, ``--disable-webpmux``,
 | 
				
			||||||
  ``--disable-imagequant``, ``--disable-xcb``.
 | 
					  ``--disable-jpeg2000``, ``--disable-imagequant``, ``--disable-xcb``.
 | 
				
			||||||
  Disable building the corresponding feature even if the development
 | 
					  Disable building the corresponding feature even if the development
 | 
				
			||||||
  libraries are present on the building machine.
 | 
					  libraries are present on the building machine.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* Build flags: ``--enable-zlib``, ``--enable-jpeg``,
 | 
					* Build flags: ``--enable-zlib``, ``--enable-jpeg``,
 | 
				
			||||||
  ``--enable-tiff``, ``--enable-freetype``, ``--enable-lcms``,
 | 
					  ``--enable-tiff``, ``--enable-freetype``, ``--enable-raqm``,
 | 
				
			||||||
  ``--enable-webp``, ``--enable-webpmux``, ``--enable-jpeg2000``,
 | 
					  ``--enable-lcms``, ``--enable-webp``, ``--enable-webpmux``,
 | 
				
			||||||
  ``--enable-imagequant``, ``--enable-xcb``.
 | 
					  ``--enable-jpeg2000``, ``--enable-imagequant``, ``--enable-xcb``.
 | 
				
			||||||
  Require that the corresponding feature is built. The build will raise
 | 
					  Require that the corresponding feature is built. The build will raise
 | 
				
			||||||
  an exception if the libraries are not found. Webpmux (WebP metadata)
 | 
					  an exception if the libraries are not found. Webpmux (WebP metadata)
 | 
				
			||||||
  relies on WebP support. Tcl and Tk also must be used together.
 | 
					  relies on WebP support. Tcl and Tk also must be used together.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* Build flags: ``--vendor-raqm --vendor-fribidi``
 | 
					* Build flags: ``--vendor-raqm``, ``--vendor-fribidi``.
 | 
				
			||||||
  These flags are used to compile a modified version of libraqm and
 | 
					  These flags are used to compile a modified version of libraqm and
 | 
				
			||||||
  a shim that dynamically loads libfribidi at runtime. These are
 | 
					  a shim that dynamically loads libfribidi at runtime. These are
 | 
				
			||||||
  used to compile the standard Pillow wheels. Compiling libraqm requires
 | 
					  used to compile the standard Pillow wheels. Compiling libraqm requires
 | 
				
			||||||
| 
						 | 
					@ -442,21 +442,23 @@ These platforms are built and tested for every change.
 | 
				
			||||||
+----------------------------------+----------------------------+---------------------+
 | 
					+----------------------------------+----------------------------+---------------------+
 | 
				
			||||||
| Gentoo                           | 3.9                        | x86-64              |
 | 
					| Gentoo                           | 3.9                        | x86-64              |
 | 
				
			||||||
+----------------------------------+----------------------------+---------------------+
 | 
					+----------------------------------+----------------------------+---------------------+
 | 
				
			||||||
| macOS 11 Big Sur                 | 3.7, 3.8, 3.9, 3.10, 3.11, | x86-64              |
 | 
					| macOS 12 Monterey                | 3.7, 3.8, 3.9, 3.10, 3.11, | x86-64              |
 | 
				
			||||||
|                                  | PyPy3                      |                     |
 | 
					|                                  | 3.12, PyPy3                |                     |
 | 
				
			||||||
+----------------------------------+----------------------------+---------------------+
 | 
					+----------------------------------+----------------------------+---------------------+
 | 
				
			||||||
| Ubuntu Linux 18.04 LTS (Bionic)  | 3.9                        | x86-64              |
 | 
					| Ubuntu Linux 18.04 LTS (Bionic)  | 3.9                        | x86-64              |
 | 
				
			||||||
+----------------------------------+----------------------------+---------------------+
 | 
					+----------------------------------+----------------------------+---------------------+
 | 
				
			||||||
| Ubuntu Linux 20.04 LTS (Focal)   | 3.7, 3.8, 3.9, 3.10, 3.11, | x86-64              |
 | 
					| Ubuntu Linux 20.04 LTS (Focal)   | 3.8                        | x86-64              |
 | 
				
			||||||
|                                  | PyPy3                      |                     |
 | 
					 | 
				
			||||||
+----------------------------------+----------------------------+---------------------+
 | 
					+----------------------------------+----------------------------+---------------------+
 | 
				
			||||||
| Ubuntu Linux 22.04 LTS (Jammy)   | 3.10                       | arm64v8, ppc64le,   |
 | 
					| Ubuntu Linux 22.04 LTS (Jammy)   | 3.7, 3.8, 3.9, 3.10, 3.11, | x86-64              |
 | 
				
			||||||
|                                  |                            | s390x, x86-64       |
 | 
					|                                  | 3.12, PyPy3                |                     |
 | 
				
			||||||
 | 
					|                                  +----------------------------+---------------------+
 | 
				
			||||||
 | 
					|                                  | 3.10                       | arm64v8, ppc64le,   |
 | 
				
			||||||
 | 
					|                                  |                            | s390x               |
 | 
				
			||||||
+----------------------------------+----------------------------+---------------------+
 | 
					+----------------------------------+----------------------------+---------------------+
 | 
				
			||||||
| Windows Server 2016              | 3.7                        | x86-64              |
 | 
					| Windows Server 2016              | 3.7                        | x86-64              |
 | 
				
			||||||
+----------------------------------+----------------------------+---------------------+
 | 
					+----------------------------------+----------------------------+---------------------+
 | 
				
			||||||
| Windows Server 2022              | 3.7, 3.8, 3.9, 3.10, 3.11, | x86, x86-64         |
 | 
					| Windows Server 2022              | 3.7, 3.8, 3.9, 3.10, 3.11, | x86, x86-64         |
 | 
				
			||||||
|                                  | PyPy3                      |                     |
 | 
					|                                  | 3.12, PyPy3                |                     |
 | 
				
			||||||
|                                  +----------------------------+---------------------+
 | 
					|                                  +----------------------------+---------------------+
 | 
				
			||||||
|                                  | 3.9 (MinGW)                | x86, x86-64         |
 | 
					|                                  | 3.9 (MinGW)                | x86, x86-64         |
 | 
				
			||||||
|                                  +----------------------------+---------------------+
 | 
					|                                  +----------------------------+---------------------+
 | 
				
			||||||
| 
						 | 
					@ -478,13 +480,13 @@ These platforms have been reported to work at the versions mentioned.
 | 
				
			||||||
| Operating system                 | | Tested Python           | | Latest tested  | | Tested     |
 | 
					| Operating system                 | | Tested Python           | | Latest tested  | | Tested     |
 | 
				
			||||||
|                                  | | versions                | | Pillow version | | processors |
 | 
					|                                  | | versions                | | Pillow version | | processors |
 | 
				
			||||||
+==================================+===========================+==================+==============+
 | 
					+==================================+===========================+==================+==============+
 | 
				
			||||||
| macOS 13 Ventura                 | 3.7, 3.8, 3.9, 3.10, 3.11 | 9.3.0            |arm           |
 | 
					| macOS 13 Ventura                 | 3.7, 3.8, 3.9, 3.10, 3.11 | 9.4.0            |arm           |
 | 
				
			||||||
+----------------------------------+---------------------------+------------------+--------------+
 | 
					+----------------------------------+---------------------------+------------------+--------------+
 | 
				
			||||||
| macOS 12 Big Sur                 | 3.7, 3.8, 3.9, 3.10, 3.11 | 9.3.0            |arm           |
 | 
					| macOS 12 Big Sur                 | 3.7, 3.8, 3.9, 3.10, 3.11 | 9.3.0            |arm           |
 | 
				
			||||||
+----------------------------------+---------------------------+------------------+--------------+
 | 
					+----------------------------------+---------------------------+------------------+--------------+
 | 
				
			||||||
| macOS 11 Big Sur                 | 3.7, 3.8, 3.9, 3.10       | 8.4.0            |arm           |
 | 
					| macOS 11 Big Sur                 | 3.7, 3.8, 3.9, 3.10       | 8.4.0            |arm           |
 | 
				
			||||||
|                                  +---------------------------+------------------+--------------+
 | 
					|                                  +---------------------------+------------------+--------------+
 | 
				
			||||||
|                                  | 3.7, 3.8, 3.9, 3.10, 3.11 | 9.3.0            |x86-64        |
 | 
					|                                  | 3.7, 3.8, 3.9, 3.10, 3.11 | 9.4.0            |x86-64        |
 | 
				
			||||||
|                                  +---------------------------+------------------+              |
 | 
					|                                  +---------------------------+------------------+              |
 | 
				
			||||||
|                                  | 3.6                       | 8.4.0            |              |
 | 
					|                                  | 3.6                       | 8.4.0            |              |
 | 
				
			||||||
+----------------------------------+---------------------------+------------------+--------------+
 | 
					+----------------------------------+---------------------------+------------------+--------------+
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -430,6 +430,7 @@ See :ref:`concept-filters` for details.
 | 
				
			||||||
.. autoclass:: Resampling
 | 
					.. autoclass:: Resampling
 | 
				
			||||||
    :members:
 | 
					    :members:
 | 
				
			||||||
    :undoc-members:
 | 
					    :undoc-members:
 | 
				
			||||||
 | 
					    :noindex:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Some deprecated filters are also available under the following names:
 | 
					Some deprecated filters are also available under the following names:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -29,84 +29,78 @@ Image resizing filters
 | 
				
			||||||
Image resizing methods :py:meth:`~PIL.Image.Image.resize` and
 | 
					Image resizing methods :py:meth:`~PIL.Image.Image.resize` and
 | 
				
			||||||
:py:meth:`~PIL.Image.Image.thumbnail` take a ``resample`` argument, which tells
 | 
					:py:meth:`~PIL.Image.Image.thumbnail` take a ``resample`` argument, which tells
 | 
				
			||||||
which filter should be used for resampling. Possible values are:
 | 
					which filter should be used for resampling. Possible values are:
 | 
				
			||||||
:py:data:`PIL.Image.NEAREST`, :py:data:`PIL.Image.BILINEAR`,
 | 
					``NEAREST``, ``BILINEAR``, ``BICUBIC`` and ``ANTIALIAS``. Almost all of them
 | 
				
			||||||
:py:data:`PIL.Image.BICUBIC` and :py:data:`PIL.Image.ANTIALIAS`.
 | 
					were changed in this version.
 | 
				
			||||||
Almost all of them were changed in this version.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
Bicubic and bilinear downscaling
 | 
					Bicubic and bilinear downscaling
 | 
				
			||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 | 
					^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 | 
				
			||||||
 | 
					
 | 
				
			||||||
From the beginning :py:data:`~PIL.Image.BILINEAR` and
 | 
					From the beginning ``BILINEAR`` and ``BICUBIC`` filters were based on affine
 | 
				
			||||||
:py:data:`~PIL.Image.BICUBIC` filters were based on affine transformations
 | 
					transformations and used a fixed number of pixels from the source image for
 | 
				
			||||||
and used a fixed number of pixels from the source image for every destination
 | 
					every destination pixel (2x2 pixels for ``BILINEAR`` and 4x4 for ``BICUBIC``).
 | 
				
			||||||
pixel (2x2 pixels for :py:data:`~PIL.Image.BILINEAR` and 4x4 for
 | 
					This gave an unsatisfactory result for downscaling. At the same time, a high
 | 
				
			||||||
:py:data:`~PIL.Image.BICUBIC`). This gave an unsatisfactory result for
 | 
					quality convolutions-based algorithm with flexible kernel was used for
 | 
				
			||||||
downscaling. At the same time, a high quality convolutions-based algorithm with
 | 
					``ANTIALIAS`` filter.
 | 
				
			||||||
flexible kernel was used for :py:data:`~PIL.Image.ANTIALIAS` filter.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
Starting from Pillow 2.7.0, a high quality convolutions-based algorithm is used
 | 
					Starting from Pillow 2.7.0, a high quality convolutions-based algorithm is used
 | 
				
			||||||
for all of these three filters.
 | 
					for all of these three filters.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
If you have previously used any tricks to maintain quality when downscaling with
 | 
					If you have previously used any tricks to maintain quality when downscaling with
 | 
				
			||||||
:py:data:`~PIL.Image.BILINEAR` and :py:data:`~PIL.Image.BICUBIC` filters
 | 
					``BILINEAR`` and ``BICUBIC`` filters (for example, reducing within several
 | 
				
			||||||
(for example, reducing within several steps), they are unnecessary now.
 | 
					steps), they are unnecessary now.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Antialias renamed to Lanczos
 | 
					Antialias renamed to Lanczos
 | 
				
			||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 | 
					^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 | 
				
			||||||
 | 
					
 | 
				
			||||||
A new :py:data:`PIL.Image.LANCZOS` constant was added instead of
 | 
					A new ``LANCZOS`` constant was added instead of ``ANTIALIAS``.
 | 
				
			||||||
:py:data:`~PIL.Image.ANTIALIAS`.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
When :py:data:`~PIL.Image.ANTIALIAS` was initially added, it was the only
 | 
					When ``ANTIALIAS`` was initially added, it was the only high-quality filter
 | 
				
			||||||
high-quality filter based on convolutions. It's name was supposed to reflect
 | 
					based on convolutions. It's name was supposed to reflect this. Starting from
 | 
				
			||||||
this. Starting from Pillow 2.7.0 all resize method are based on convolutions.
 | 
					Pillow 2.7.0 all resize method are based on convolutions. All of them are
 | 
				
			||||||
All of them are antialias from now on. And the real name of the
 | 
					antialias from now on. And the real name of the ``ANTIALIAS`` filter is Lanczos
 | 
				
			||||||
:py:data:`~PIL.Image.ANTIALIAS` filter is Lanczos filter.
 | 
					filter.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
The :py:data:`~PIL.Image.ANTIALIAS` constant is left for backward compatibility
 | 
					The ``ANTIALIAS`` constant is left for backward compatibility and is an alias
 | 
				
			||||||
and is an alias for :py:data:`~PIL.Image.LANCZOS`.
 | 
					for ``LANCZOS``.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Lanczos upscaling quality
 | 
					Lanczos upscaling quality
 | 
				
			||||||
^^^^^^^^^^^^^^^^^^^^^^^^^
 | 
					^^^^^^^^^^^^^^^^^^^^^^^^^
 | 
				
			||||||
 | 
					
 | 
				
			||||||
The image upscaling quality with :py:data:`~PIL.Image.LANCZOS` filter was
 | 
					The image upscaling quality with ``LANCZOS`` filter was almost the same as
 | 
				
			||||||
almost the same as :py:data:`~PIL.Image.BILINEAR` due to bug. This has been fixed.
 | 
					``BILINEAR`` due to a bug. This has been fixed.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Bicubic upscaling quality
 | 
					Bicubic upscaling quality
 | 
				
			||||||
^^^^^^^^^^^^^^^^^^^^^^^^^
 | 
					^^^^^^^^^^^^^^^^^^^^^^^^^
 | 
				
			||||||
 | 
					
 | 
				
			||||||
The :py:data:`~PIL.Image.BICUBIC` filter for affine transformations produced
 | 
					The ``BICUBIC`` filter for affine transformations produced sharp, slightly
 | 
				
			||||||
sharp, slightly pixelated image for upscaling. Bicubic for convolutions is
 | 
					pixelated image for upscaling. Bicubic for convolutions is more soft.
 | 
				
			||||||
more soft.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
Resize performance
 | 
					Resize performance
 | 
				
			||||||
^^^^^^^^^^^^^^^^^^
 | 
					^^^^^^^^^^^^^^^^^^
 | 
				
			||||||
 | 
					
 | 
				
			||||||
In most cases, convolution is more a expensive algorithm for downscaling
 | 
					In most cases, convolution is more a expensive algorithm for downscaling
 | 
				
			||||||
because it takes into account all the pixels of source image. Therefore
 | 
					because it takes into account all the pixels of source image. Therefore
 | 
				
			||||||
:py:data:`~PIL.Image.BILINEAR` and :py:data:`~PIL.Image.BICUBIC` filters'
 | 
					``BILINEAR`` and ``BICUBIC`` filters' performance can be lower than before.
 | 
				
			||||||
performance can be lower than before. On the other hand the quality of
 | 
					On the other hand the quality of ``BILINEAR`` and ``BICUBIC`` was close to
 | 
				
			||||||
:py:data:`~PIL.Image.BILINEAR` and :py:data:`~PIL.Image.BICUBIC` was close to
 | 
					``NEAREST``. So if such quality is suitable for your tasks you can switch to
 | 
				
			||||||
:py:data:`~PIL.Image.NEAREST`. So if such quality is suitable for your tasks
 | 
					``NEAREST`` filter for downscaling, which will give a huge improvement in
 | 
				
			||||||
you can switch to :py:data:`~PIL.Image.NEAREST` filter for downscaling,
 | 
					performance.
 | 
				
			||||||
which will give a huge improvement in performance.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
At the same time performance of convolution resampling for downscaling has been
 | 
					At the same time performance of convolution resampling for downscaling has been
 | 
				
			||||||
improved by around a factor of two compared to the previous version.
 | 
					improved by around a factor of two compared to the previous version.
 | 
				
			||||||
The upscaling performance of the :py:data:`~PIL.Image.LANCZOS` filter has
 | 
					The upscaling performance of the ``LANCZOS`` filter has remained the same. For
 | 
				
			||||||
remained the same. For :py:data:`~PIL.Image.BILINEAR` filter it has improved by
 | 
					``BILINEAR`` filter it has improved by 1.5 times and for ``BICUBIC`` by four
 | 
				
			||||||
1.5 times and for :py:data:`~PIL.Image.BICUBIC` by four times.
 | 
					times.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Default filter for thumbnails
 | 
					Default filter for thumbnails
 | 
				
			||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 | 
					^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 | 
				
			||||||
 | 
					
 | 
				
			||||||
In Pillow 2.5 the default filter for :py:meth:`~PIL.Image.Image.thumbnail` was
 | 
					In Pillow 2.5 the default filter for :py:meth:`~PIL.Image.Image.thumbnail` was
 | 
				
			||||||
changed from :py:data:`~PIL.Image.NEAREST` to :py:data:`~PIL.Image.ANTIALIAS`.
 | 
					changed from ``NEAREST`` to ``ANTIALIAS``. Antialias was chosen because all the
 | 
				
			||||||
Antialias was chosen because all the other filters gave poor quality for
 | 
					other filters gave poor quality for reduction. Starting from Pillow 2.7.0,
 | 
				
			||||||
reduction. Starting from Pillow 2.7.0, :py:data:`~PIL.Image.ANTIALIAS` has been
 | 
					``ANTIALIAS`` has been replaced with ``BICUBIC``, because it's faster and
 | 
				
			||||||
replaced with :py:data:`~PIL.Image.BICUBIC`, because it's faster and
 | 
					``ANTIALIAS`` doesn't give any advantages after downscaling with libjpeg, which
 | 
				
			||||||
:py:data:`~PIL.Image.ANTIALIAS` doesn't give any advantages after
 | 
					uses supersampling internally, not convolutions.
 | 
				
			||||||
downscaling with libjpeg, which uses supersampling internally, not convolutions.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
Image transposition
 | 
					Image transposition
 | 
				
			||||||
-------------------
 | 
					-------------------
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -53,6 +53,11 @@ Constants
 | 
				
			||||||
A number of constants have been deprecated and will be removed in Pillow 10.0.0
 | 
					A number of constants have been deprecated and will be removed in Pillow 10.0.0
 | 
				
			||||||
(2023-07-01). Instead, ``enum.IntEnum`` classes have been added.
 | 
					(2023-07-01). Instead, ``enum.IntEnum`` classes have been added.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. note::
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Some of these deprecations were restored in Pillow 9.4.0. See
 | 
				
			||||||
 | 
					    :ref:`restored-image-constants`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
=====================================================  ============================================================
 | 
					=====================================================  ============================================================
 | 
				
			||||||
Deprecated                                             Use instead
 | 
					Deprecated                                             Use instead
 | 
				
			||||||
=====================================================  ============================================================
 | 
					=====================================================  ============================================================
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,30 +1,6 @@
 | 
				
			||||||
9.4.0
 | 
					9.4.0
 | 
				
			||||||
-----
 | 
					-----
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Backwards Incompatible Changes
 | 
					 | 
				
			||||||
==============================
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
TODO
 | 
					 | 
				
			||||||
^^^^
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
TODO
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Deprecations
 | 
					 | 
				
			||||||
============
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
TODO
 | 
					 | 
				
			||||||
^^^^
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
TODO
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
API Changes
 | 
					 | 
				
			||||||
===========
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
TODO
 | 
					 | 
				
			||||||
^^^^
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
TODO
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
API Additions
 | 
					API Additions
 | 
				
			||||||
=============
 | 
					=============
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -96,10 +72,21 @@ When saving a JPEG image, a comment can now be written from
 | 
				
			||||||
Security
 | 
					Security
 | 
				
			||||||
========
 | 
					========
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TODO
 | 
					Fix memory DOS in ImageFont
 | 
				
			||||||
^^^^
 | 
					^^^^^^^^^^^^^^^^^^^^^^^^^^^
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TODO
 | 
					A corrupt or specially crafted TTF font could have font metrics that lead to
 | 
				
			||||||
 | 
					unreasonably large sizes when rendering text in font. ``ImageFont.py`` did not
 | 
				
			||||||
 | 
					check the image size before allocating memory for it. This dates to the PIL
 | 
				
			||||||
 | 
					fork. Pillow 8.2.0 added a check for large sizes, but did not consider the
 | 
				
			||||||
 | 
					case where one dimension is zero.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Null pointer dereference crash in ImageFont
 | 
				
			||||||
 | 
					^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Pillow attempted to dereference a null pointer in ``ImageFont``, leading to a
 | 
				
			||||||
 | 
					crash. An error is now raised instead. This has been present since
 | 
				
			||||||
 | 
					Pillow 8.0.0.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Other Changes
 | 
					Other Changes
 | 
				
			||||||
=============
 | 
					=============
 | 
				
			||||||
| 
						 | 
					@ -109,3 +96,40 @@ Added support for DDS L and LA images
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Support has been added to read and write L and LA DDS images in the uncompressed
 | 
					Support has been added to read and write L and LA DDS images in the uncompressed
 | 
				
			||||||
format, known as "luminance" textures.
 | 
					format, known as "luminance" textures.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. _restored-image-constants:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Constants
 | 
				
			||||||
 | 
					^^^^^^^^^
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					In Pillow 9.1.0, the following constants were deprecated. That has been reversed and
 | 
				
			||||||
 | 
					these constants will now remain available.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- ``Image.NONE``
 | 
				
			||||||
 | 
					- ``Image.NEAREST``
 | 
				
			||||||
 | 
					- ``Image.ORDERED``
 | 
				
			||||||
 | 
					- ``Image.RASTERIZE``
 | 
				
			||||||
 | 
					- ``Image.FLOYDSTEINBERG``
 | 
				
			||||||
 | 
					- ``Image.WEB``
 | 
				
			||||||
 | 
					- ``Image.ADAPTIVE``
 | 
				
			||||||
 | 
					- ``Image.AFFINE``
 | 
				
			||||||
 | 
					- ``Image.EXTENT``
 | 
				
			||||||
 | 
					- ``Image.PERSPECTIVE``
 | 
				
			||||||
 | 
					- ``Image.QUAD``
 | 
				
			||||||
 | 
					- ``Image.MESH``
 | 
				
			||||||
 | 
					- ``Image.FLIP_LEFT_RIGHT``
 | 
				
			||||||
 | 
					- ``Image.FLIP_TOP_BOTTOM``
 | 
				
			||||||
 | 
					- ``Image.ROTATE_90``
 | 
				
			||||||
 | 
					- ``Image.ROTATE_180``
 | 
				
			||||||
 | 
					- ``Image.ROTATE_270``
 | 
				
			||||||
 | 
					- ``Image.TRANSPOSE``
 | 
				
			||||||
 | 
					- ``Image.TRANSVERSE``
 | 
				
			||||||
 | 
					- ``Image.BOX``
 | 
				
			||||||
 | 
					- ``Image.BILINEAR``
 | 
				
			||||||
 | 
					- ``Image.HAMMING``
 | 
				
			||||||
 | 
					- ``Image.BICUBIC``
 | 
				
			||||||
 | 
					- ``Image.LANCZOS``
 | 
				
			||||||
 | 
					- ``Image.MEDIANCUT``
 | 
				
			||||||
 | 
					- ``Image.MAXCOVERAGE``
 | 
				
			||||||
 | 
					- ``Image.FASTOCTREE``
 | 
				
			||||||
 | 
					- ``Image.LIBIMAGEQUANT``
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,8 +4,8 @@ description = Python Imaging Library (Fork)
 | 
				
			||||||
long_description = file: README.md
 | 
					long_description = file: README.md
 | 
				
			||||||
long_description_content_type = text/markdown
 | 
					long_description_content_type = text/markdown
 | 
				
			||||||
url = https://python-pillow.org
 | 
					url = https://python-pillow.org
 | 
				
			||||||
author = Alex Clark (PIL Fork Author)
 | 
					author = Jeffrey A. Clark (Alex)
 | 
				
			||||||
author_email = aclark@python-pillow.org
 | 
					author_email = aclark@aclark.net
 | 
				
			||||||
license = HPND
 | 
					license = HPND
 | 
				
			||||||
classifiers =
 | 
					classifiers =
 | 
				
			||||||
    Development Status :: 6 - Mature
 | 
					    Development Status :: 6 - Mature
 | 
				
			||||||
| 
						 | 
					@ -32,6 +32,7 @@ project_urls =
 | 
				
			||||||
    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/blob/main/CHANGES.rst
 | 
				
			||||||
    Twitter=https://twitter.com/PythonPillow
 | 
					    Twitter=https://twitter.com/PythonPillow
 | 
				
			||||||
 | 
					    Mastodon=https://fosstodon.org/@pillow
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[options]
 | 
					[options]
 | 
				
			||||||
packages = PIL
 | 
					packages = PIL
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										28
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										28
									
								
								setup.py
									
									
									
									
									
								
							| 
						 | 
					@ -263,18 +263,18 @@ def _pkg_config(name):
 | 
				
			||||||
            if not DEBUG:
 | 
					            if not DEBUG:
 | 
				
			||||||
                command_libs.append("--silence-errors")
 | 
					                command_libs.append("--silence-errors")
 | 
				
			||||||
                command_cflags.append("--silence-errors")
 | 
					                command_cflags.append("--silence-errors")
 | 
				
			||||||
            libs = (
 | 
					            libs = re.split(
 | 
				
			||||||
 | 
					                r"(^|\s+)-L",
 | 
				
			||||||
                subprocess.check_output(command_libs, stderr=stderr)
 | 
					                subprocess.check_output(command_libs, stderr=stderr)
 | 
				
			||||||
                .decode("utf8")
 | 
					                .decode("utf8")
 | 
				
			||||||
                .strip()
 | 
					                .strip(),
 | 
				
			||||||
                .replace("-L", "")
 | 
					            )[::2][1:]
 | 
				
			||||||
            )
 | 
					            cflags = re.split(
 | 
				
			||||||
            cflags = (
 | 
					                r"(^|\s+)-I",
 | 
				
			||||||
                subprocess.check_output(command_cflags)
 | 
					                subprocess.check_output(command_cflags, stderr=stderr)
 | 
				
			||||||
                .decode("utf8")
 | 
					                .decode("utf8")
 | 
				
			||||||
                .strip()
 | 
					                .strip(),
 | 
				
			||||||
                .replace("-I", "")
 | 
					            )[::2][1:]
 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            return libs, cflags
 | 
					            return libs, cflags
 | 
				
			||||||
        except Exception:
 | 
					        except Exception:
 | 
				
			||||||
            pass
 | 
					            pass
 | 
				
			||||||
| 
						 | 
					@ -430,7 +430,6 @@ class pil_build_ext(build_ext):
 | 
				
			||||||
        return sdk_path
 | 
					        return sdk_path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def build_extensions(self):
 | 
					    def build_extensions(self):
 | 
				
			||||||
 | 
					 | 
				
			||||||
        library_dirs = []
 | 
					        library_dirs = []
 | 
				
			||||||
        include_dirs = []
 | 
					        include_dirs = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -473,8 +472,12 @@ class pil_build_ext(build_ext):
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                lib_root = include_root = root
 | 
					                lib_root = include_root = root
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            _add_directory(library_dirs, lib_root)
 | 
					            if lib_root is not None:
 | 
				
			||||||
            _add_directory(include_dirs, include_root)
 | 
					                for lib_dir in lib_root:
 | 
				
			||||||
 | 
					                    _add_directory(library_dirs, lib_dir)
 | 
				
			||||||
 | 
					            if include_root is not None:
 | 
				
			||||||
 | 
					                for include_dir in include_root:
 | 
				
			||||||
 | 
					                    _add_directory(include_dirs, include_dir)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # respect CFLAGS/CPPFLAGS/LDFLAGS
 | 
					        # respect CFLAGS/CPPFLAGS/LDFLAGS
 | 
				
			||||||
        for k in ("CFLAGS", "CPPFLAGS", "LDFLAGS"):
 | 
					        for k in ("CFLAGS", "CPPFLAGS", "LDFLAGS"):
 | 
				
			||||||
| 
						 | 
					@ -913,7 +916,6 @@ class pil_build_ext(build_ext):
 | 
				
			||||||
        self.summary_report(feature)
 | 
					        self.summary_report(feature)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def summary_report(self, feature):
 | 
					    def summary_report(self, feature):
 | 
				
			||||||
 | 
					 | 
				
			||||||
        print("-" * 68)
 | 
					        print("-" * 68)
 | 
				
			||||||
        print("PIL SETUP SUMMARY")
 | 
					        print("PIL SETUP SUMMARY")
 | 
				
			||||||
        print("-" * 68)
 | 
					        print("-" * 68)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -226,7 +226,6 @@ class BmpImageFile(ImageFile.ImageFile):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # --------------- Once the header is processed, process the palette/LUT
 | 
					        # --------------- Once the header is processed, process the palette/LUT
 | 
				
			||||||
        if self.mode == "P":  # Paletted for 1, 4 and 8 bit images
 | 
					        if self.mode == "P":  # Paletted for 1, 4 and 8 bit images
 | 
				
			||||||
 | 
					 | 
				
			||||||
            # ---------------------------------------------------- 1-bit images
 | 
					            # ---------------------------------------------------- 1-bit images
 | 
				
			||||||
            if not (0 < file_info["colors"] <= 65536):
 | 
					            if not (0 < file_info["colors"] <= 65536):
 | 
				
			||||||
                msg = f"Unsupported BMP Palette size ({file_info['colors']})"
 | 
					                msg = f"Unsupported BMP Palette size ({file_info['colors']})"
 | 
				
			||||||
| 
						 | 
					@ -363,7 +362,6 @@ class BmpRleDecoder(ImageFile.PyDecoder):
 | 
				
			||||||
# Image plugin for the DIB format (BMP alias)
 | 
					# Image plugin for the DIB format (BMP alias)
 | 
				
			||||||
# =============================================================================
 | 
					# =============================================================================
 | 
				
			||||||
class DibImageFile(BmpImageFile):
 | 
					class DibImageFile(BmpImageFile):
 | 
				
			||||||
 | 
					 | 
				
			||||||
    format = "DIB"
 | 
					    format = "DIB"
 | 
				
			||||||
    format_description = "Windows Bitmap"
 | 
					    format_description = "Windows Bitmap"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
		Reference in New Issue
	
	Block a user