mirror of
				https://github.com/python-pillow/Pillow.git
				synced 2025-11-04 01:47:47 +03:00 
			
		
		
		
	Merge branch 'main' into jpeg_xmp
This commit is contained in:
		
						commit
						f24222a954
					
				| 
						 | 
					@ -21,13 +21,11 @@ environment:
 | 
				
			||||||
install:
 | 
					install:
 | 
				
			||||||
- '%PYTHON%\%EXECUTABLE% --version'
 | 
					- '%PYTHON%\%EXECUTABLE% --version'
 | 
				
			||||||
- '%PYTHON%\%EXECUTABLE% -m pip install --upgrade pip'
 | 
					- '%PYTHON%\%EXECUTABLE% -m pip install --upgrade pip'
 | 
				
			||||||
- 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
 | 
					- 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-test-images.zip -oc:\
 | 
					- 7z x pillow-test-images.zip -oc:\
 | 
				
			||||||
- mv c:\pillow-depends-main c:\pillow-depends
 | 
					 | 
				
			||||||
- xcopy /S /Y c:\test-images-main\* c:\pillow\tests\images
 | 
					- xcopy /S /Y c:\test-images-main\* c:\pillow\tests\images
 | 
				
			||||||
- 7z x ..\pillow-depends\nasm-2.16.01-win64.zip -oc:\
 | 
					- curl -fsSL -o nasm-win64.zip https://raw.githubusercontent.com/python-pillow/pillow-depends/main/nasm-2.16.01-win64.zip
 | 
				
			||||||
 | 
					- 7z x nasm-win64.zip -oc:\
 | 
				
			||||||
- choco install ghostscript --version=10.0.0.20230317
 | 
					- choco install ghostscript --version=10.0.0.20230317
 | 
				
			||||||
- path c:\nasm-2.16.01;C:\Program Files\gs\gs10.00.0\bin;%PATH%
 | 
					- path c:\nasm-2.16.01;C:\Program Files\gs\gs10.00.0\bin;%PATH%
 | 
				
			||||||
- cd c:\pillow\winbuild\
 | 
					- cd c:\pillow\winbuild\
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -23,7 +23,7 @@ if [[ $(uname) != CYGWIN* ]]; then
 | 
				
			||||||
    sudo apt-get -qq install libfreetype6-dev liblcms2-dev python3-tk\
 | 
					    sudo apt-get -qq install libfreetype6-dev liblcms2-dev python3-tk\
 | 
				
			||||||
                             ghostscript libffi-dev libjpeg-turbo-progs libopenjp2-7-dev\
 | 
					                             ghostscript libffi-dev libjpeg-turbo-progs libopenjp2-7-dev\
 | 
				
			||||||
                             cmake meson imagemagick libharfbuzz-dev libfribidi-dev\
 | 
					                             cmake meson imagemagick libharfbuzz-dev libfribidi-dev\
 | 
				
			||||||
                             sway wl-clipboard
 | 
					                             sway wl-clipboard libopenblas-dev
 | 
				
			||||||
fi
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
python3 -m pip install --upgrade pip
 | 
					python3 -m pip install --upgrade pip
 | 
				
			||||||
| 
						 | 
					@ -38,11 +38,10 @@ 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
 | 
				
			||||||
    # TODO Remove condition when NumPy supports 3.12
 | 
					    python3 -m pip install numpy
 | 
				
			||||||
    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.12-dev" && $GHA_PYTHON_VERSION == 3.* ]]; then
 | 
					    if [[ $GHA_PYTHON_VERSION == 3.* ]]; then
 | 
				
			||||||
        sudo apt-get -qq install libegl1 libxcb-cursor0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-shape0 libxkbcommon-x11-0
 | 
					        sudo apt-get -qq install libegl1 libxcb-cursor0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-shape0 libxkbcommon-x11-0
 | 
				
			||||||
        python3 -m pip install pyqt6
 | 
					        python3 -m pip install pyqt6
 | 
				
			||||||
    fi
 | 
					    fi
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										2
									
								
								.github/workflows/docs.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/docs.yml
									
									
									
									
										vendored
									
									
								
							| 
						 | 
					@ -28,7 +28,7 @@ jobs:
 | 
				
			||||||
    name: Docs
 | 
					    name: Docs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
    - uses: actions/checkout@v3
 | 
					    - uses: actions/checkout@v4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    - name: Set up Python
 | 
					    - name: Set up Python
 | 
				
			||||||
      uses: actions/setup-python@v4
 | 
					      uses: actions/setup-python@v4
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										2
									
								
								.github/workflows/lint.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/lint.yml
									
									
									
									
										vendored
									
									
								
							| 
						 | 
					@ -17,7 +17,7 @@ jobs:
 | 
				
			||||||
    name: Lint
 | 
					    name: Lint
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
    - uses: actions/checkout@v3
 | 
					    - uses: actions/checkout@v4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    - name: pre-commit cache
 | 
					    - name: pre-commit cache
 | 
				
			||||||
      uses: actions/cache@v3
 | 
					      uses: actions/cache@v3
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										4
									
								
								.github/workflows/macos-install.sh
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/macos-install.sh
									
									
									
									
										vendored
									
									
								
							| 
						 | 
					@ -3,6 +3,7 @@
 | 
				
			||||||
set -e
 | 
					set -e
 | 
				
			||||||
 | 
					
 | 
				
			||||||
brew install libtiff libjpeg openjpeg libimagequant webp little-cms2 freetype libraqm
 | 
					brew install libtiff libjpeg openjpeg libimagequant webp little-cms2 freetype libraqm
 | 
				
			||||||
 | 
					export PKG_CONFIG_PATH="/usr/local/opt/openblas/lib/pkgconfig"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
PYTHONOPTIMIZE=0 python3 -m pip install cffi
 | 
					PYTHONOPTIMIZE=0 python3 -m pip install cffi
 | 
				
			||||||
python3 -m pip install coverage
 | 
					python3 -m pip install coverage
 | 
				
			||||||
| 
						 | 
					@ -13,8 +14,7 @@ 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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# TODO Remove condition when NumPy supports 3.12
 | 
					python3 -m pip install numpy
 | 
				
			||||||
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
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										32
									
								
								.github/workflows/test-cygwin.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										32
									
								
								.github/workflows/test-cygwin.yml
									
									
									
									
										vendored
									
									
								
							| 
						 | 
					@ -4,11 +4,19 @@ on:
 | 
				
			||||||
  push:
 | 
					  push:
 | 
				
			||||||
    paths-ignore:
 | 
					    paths-ignore:
 | 
				
			||||||
      - ".github/workflows/docs.yml"
 | 
					      - ".github/workflows/docs.yml"
 | 
				
			||||||
 | 
					      - ".github/workflows/wheels*"
 | 
				
			||||||
 | 
					      - ".gitmodules"
 | 
				
			||||||
 | 
					      - ".travis.yml"
 | 
				
			||||||
      - "docs/**"
 | 
					      - "docs/**"
 | 
				
			||||||
 | 
					      - "wheels/**"
 | 
				
			||||||
  pull_request:
 | 
					  pull_request:
 | 
				
			||||||
    paths-ignore:
 | 
					    paths-ignore:
 | 
				
			||||||
      - ".github/workflows/docs.yml"
 | 
					      - ".github/workflows/docs.yml"
 | 
				
			||||||
 | 
					      - ".github/workflows/wheels*"
 | 
				
			||||||
 | 
					      - ".gitmodules"
 | 
				
			||||||
 | 
					      - ".travis.yml"
 | 
				
			||||||
      - "docs/**"
 | 
					      - "docs/**"
 | 
				
			||||||
 | 
					      - "wheels/**"
 | 
				
			||||||
  workflow_dispatch:
 | 
					  workflow_dispatch:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
permissions:
 | 
					permissions:
 | 
				
			||||||
| 
						 | 
					@ -36,7 +44,7 @@ jobs:
 | 
				
			||||||
          git config --global core.autocrlf input
 | 
					          git config --global core.autocrlf input
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Checkout Pillow
 | 
					      - name: Checkout Pillow
 | 
				
			||||||
        uses: actions/checkout@v3
 | 
					        uses: actions/checkout@v4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Install Cygwin
 | 
					      - name: Install Cygwin
 | 
				
			||||||
        uses: cygwin/cygwin-install-action@v4
 | 
					        uses: cygwin/cygwin-install-action@v4
 | 
				
			||||||
| 
						 | 
					@ -76,17 +84,23 @@ jobs:
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          dirs: 'C:\cygwin\bin;C:\cygwin\lib\lapack'
 | 
					          dirs: 'C:\cygwin\bin;C:\cygwin\lib\lapack'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      - name: Select Python version
 | 
				
			||||||
 | 
					        run: |
 | 
				
			||||||
 | 
					          ln -sf c:/cygwin/bin/python3.${{ matrix.python-minor-version }} c:/cygwin/bin/python3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      - name: Get latest NumPy version
 | 
				
			||||||
 | 
					        id: latest-numpy
 | 
				
			||||||
 | 
					        shell: bash.exe -eo pipefail -o igncr "{0}"
 | 
				
			||||||
 | 
					        run: |
 | 
				
			||||||
 | 
					          python3 -m pip list --outdated | grep numpy | sed -r 's/ +/ /g' | cut -d ' ' -f 3 | sed 's/^/version=/' >> $GITHUB_OUTPUT
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: pip cache
 | 
					      - name: pip cache
 | 
				
			||||||
        uses: actions/cache@v3
 | 
					        uses: actions/cache@v3
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          path: 'C:\cygwin\home\runneradmin\.cache\pip'
 | 
					          path: 'C:\cygwin\home\runneradmin\.cache\pip'
 | 
				
			||||||
          key: ${{ runner.os }}-cygwin-pip3.${{ matrix.python-minor-version }}-${{ hashFiles('.ci/install.sh') }}
 | 
					          key: ${{ runner.os }}-cygwin-pip3.${{ matrix.python-minor-version }}-numpy${{ steps.latest-numpy.outputs.version }}-${{ hashFiles('.ci/install.sh') }}
 | 
				
			||||||
          restore-keys: |
 | 
					          restore-keys: |
 | 
				
			||||||
            ${{ runner.os }}-cygwin-pip3.${{ matrix.python-minor-version }}-
 | 
					            ${{ runner.os }}-cygwin-pip3.${{ matrix.python-minor-version }}-numpy${{ steps.latest-numpy.outputs.version }}-
 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Select Python version
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          ln -sf c:/cygwin/bin/python3.${{ matrix.python-minor-version }} c:/cygwin/bin/python3
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Build system information
 | 
					      - name: Build system information
 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
| 
						 | 
					@ -96,10 +110,10 @@ jobs:
 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
          bash.exe .ci/install.sh
 | 
					          bash.exe .ci/install.sh
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Install a different NumPy
 | 
					      - name: Upgrade NumPy
 | 
				
			||||||
        shell: dash.exe -l "{0}"
 | 
					        shell: dash.exe -l "{0}"
 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
          python3 -m pip install -U numpy
 | 
					          python3 -m pip install -U "numpy<1.26"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Build
 | 
					      - name: Build
 | 
				
			||||||
        shell: bash.exe -eo pipefail -o igncr "{0}"
 | 
					        shell: bash.exe -eo pipefail -o igncr "{0}"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										10
									
								
								.github/workflows/test-docker.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								.github/workflows/test-docker.yml
									
									
									
									
										vendored
									
									
								
							| 
						 | 
					@ -4,11 +4,19 @@ on:
 | 
				
			||||||
  push:
 | 
					  push:
 | 
				
			||||||
    paths-ignore:
 | 
					    paths-ignore:
 | 
				
			||||||
      - ".github/workflows/docs.yml"
 | 
					      - ".github/workflows/docs.yml"
 | 
				
			||||||
 | 
					      - ".github/workflows/wheels*"
 | 
				
			||||||
 | 
					      - ".gitmodules"
 | 
				
			||||||
 | 
					      - ".travis.yml"
 | 
				
			||||||
      - "docs/**"
 | 
					      - "docs/**"
 | 
				
			||||||
 | 
					      - "wheels/**"
 | 
				
			||||||
  pull_request:
 | 
					  pull_request:
 | 
				
			||||||
    paths-ignore:
 | 
					    paths-ignore:
 | 
				
			||||||
      - ".github/workflows/docs.yml"
 | 
					      - ".github/workflows/docs.yml"
 | 
				
			||||||
 | 
					      - ".github/workflows/wheels*"
 | 
				
			||||||
 | 
					      - ".gitmodules"
 | 
				
			||||||
 | 
					      - ".travis.yml"
 | 
				
			||||||
      - "docs/**"
 | 
					      - "docs/**"
 | 
				
			||||||
 | 
					      - "wheels/**"
 | 
				
			||||||
  workflow_dispatch:
 | 
					  workflow_dispatch:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
permissions:
 | 
					permissions:
 | 
				
			||||||
| 
						 | 
					@ -59,7 +67,7 @@ jobs:
 | 
				
			||||||
    name: ${{ matrix.docker }}
 | 
					    name: ${{ matrix.docker }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
    - uses: actions/checkout@v3
 | 
					    - uses: actions/checkout@v4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    - name: Build system information
 | 
					    - name: Build system information
 | 
				
			||||||
      run: python3 .github/workflows/system-info.py
 | 
					      run: python3 .github/workflows/system-info.py
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										10
									
								
								.github/workflows/test-mingw.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								.github/workflows/test-mingw.yml
									
									
									
									
										vendored
									
									
								
							| 
						 | 
					@ -4,11 +4,19 @@ on:
 | 
				
			||||||
  push:
 | 
					  push:
 | 
				
			||||||
    paths-ignore:
 | 
					    paths-ignore:
 | 
				
			||||||
      - ".github/workflows/docs.yml"
 | 
					      - ".github/workflows/docs.yml"
 | 
				
			||||||
 | 
					      - ".github/workflows/wheels*"
 | 
				
			||||||
 | 
					      - ".gitmodules"
 | 
				
			||||||
 | 
					      - ".travis.yml"
 | 
				
			||||||
      - "docs/**"
 | 
					      - "docs/**"
 | 
				
			||||||
 | 
					      - "wheels/**"
 | 
				
			||||||
  pull_request:
 | 
					  pull_request:
 | 
				
			||||||
    paths-ignore:
 | 
					    paths-ignore:
 | 
				
			||||||
      - ".github/workflows/docs.yml"
 | 
					      - ".github/workflows/docs.yml"
 | 
				
			||||||
 | 
					      - ".github/workflows/wheels*"
 | 
				
			||||||
 | 
					      - ".gitmodules"
 | 
				
			||||||
 | 
					      - ".travis.yml"
 | 
				
			||||||
      - "docs/**"
 | 
					      - "docs/**"
 | 
				
			||||||
 | 
					      - "wheels/**"
 | 
				
			||||||
  workflow_dispatch:
 | 
					  workflow_dispatch:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
permissions:
 | 
					permissions:
 | 
				
			||||||
| 
						 | 
					@ -34,7 +42,7 @@ jobs:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - name: Checkout Pillow
 | 
					      - name: Checkout Pillow
 | 
				
			||||||
        uses: actions/checkout@v3
 | 
					        uses: actions/checkout@v4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Set up shell
 | 
					      - name: Set up shell
 | 
				
			||||||
        run: echo "C:\msys64\usr\bin\" >> $env:GITHUB_PATH
 | 
					        run: echo "C:\msys64\usr\bin\" >> $env:GITHUB_PATH
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										2
									
								
								.github/workflows/test-valgrind.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/test-valgrind.yml
									
									
									
									
										vendored
									
									
								
							| 
						 | 
					@ -37,7 +37,7 @@ jobs:
 | 
				
			||||||
    name: ${{ matrix.docker }}
 | 
					    name: ${{ matrix.docker }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
    - uses: actions/checkout@v3
 | 
					    - uses: actions/checkout@v4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    - name: Build system information
 | 
					    - name: Build system information
 | 
				
			||||||
      run: python3 .github/workflows/system-info.py
 | 
					      run: python3 .github/workflows/system-info.py
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										16
									
								
								.github/workflows/test-windows.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										16
									
								
								.github/workflows/test-windows.yml
									
									
									
									
										vendored
									
									
								
							| 
						 | 
					@ -4,11 +4,19 @@ on:
 | 
				
			||||||
  push:
 | 
					  push:
 | 
				
			||||||
    paths-ignore:
 | 
					    paths-ignore:
 | 
				
			||||||
      - ".github/workflows/docs.yml"
 | 
					      - ".github/workflows/docs.yml"
 | 
				
			||||||
 | 
					      - ".github/workflows/wheels*"
 | 
				
			||||||
 | 
					      - ".gitmodules"
 | 
				
			||||||
 | 
					      - ".travis.yml"
 | 
				
			||||||
      - "docs/**"
 | 
					      - "docs/**"
 | 
				
			||||||
 | 
					      - "wheels/**"
 | 
				
			||||||
  pull_request:
 | 
					  pull_request:
 | 
				
			||||||
    paths-ignore:
 | 
					    paths-ignore:
 | 
				
			||||||
      - ".github/workflows/docs.yml"
 | 
					      - ".github/workflows/docs.yml"
 | 
				
			||||||
 | 
					      - ".github/workflows/wheels*"
 | 
				
			||||||
 | 
					      - ".gitmodules"
 | 
				
			||||||
 | 
					      - ".travis.yml"
 | 
				
			||||||
      - "docs/**"
 | 
					      - "docs/**"
 | 
				
			||||||
 | 
					      - "wheels/**"
 | 
				
			||||||
  workflow_dispatch:
 | 
					  workflow_dispatch:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
permissions:
 | 
					permissions:
 | 
				
			||||||
| 
						 | 
					@ -24,7 +32,7 @@ jobs:
 | 
				
			||||||
    strategy:
 | 
					    strategy:
 | 
				
			||||||
      fail-fast: false
 | 
					      fail-fast: false
 | 
				
			||||||
      matrix:
 | 
					      matrix:
 | 
				
			||||||
        python-version: ["pypy3.10", "pypy3.9", "3.8", "3.9", "3.10", "3.11", "3.12-dev"]
 | 
					        python-version: ["pypy3.10", "pypy3.9", "3.8", "3.9", "3.10", "3.11", "3.12"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    timeout-minutes: 30
 | 
					    timeout-minutes: 30
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -32,16 +40,16 @@ jobs:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
    - name: Checkout Pillow
 | 
					    - name: Checkout Pillow
 | 
				
			||||||
      uses: actions/checkout@v3
 | 
					      uses: actions/checkout@v4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    - name: Checkout cached dependencies
 | 
					    - name: Checkout cached dependencies
 | 
				
			||||||
      uses: actions/checkout@v3
 | 
					      uses: actions/checkout@v4
 | 
				
			||||||
      with:
 | 
					      with:
 | 
				
			||||||
        repository: python-pillow/pillow-depends
 | 
					        repository: python-pillow/pillow-depends
 | 
				
			||||||
        path: winbuild\depends
 | 
					        path: winbuild\depends
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    - name: Checkout extra test images
 | 
					    - name: Checkout extra test images
 | 
				
			||||||
      uses: actions/checkout@v3
 | 
					      uses: actions/checkout@v4
 | 
				
			||||||
      with:
 | 
					      with:
 | 
				
			||||||
        repository: python-pillow/test-images
 | 
					        repository: python-pillow/test-images
 | 
				
			||||||
        path: Tests\test-images
 | 
					        path: Tests\test-images
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										12
									
								
								.github/workflows/test.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										12
									
								
								.github/workflows/test.yml
									
									
									
									
										vendored
									
									
								
							| 
						 | 
					@ -4,11 +4,19 @@ on:
 | 
				
			||||||
  push:
 | 
					  push:
 | 
				
			||||||
    paths-ignore:
 | 
					    paths-ignore:
 | 
				
			||||||
      - ".github/workflows/docs.yml"
 | 
					      - ".github/workflows/docs.yml"
 | 
				
			||||||
 | 
					      - ".github/workflows/wheels*"
 | 
				
			||||||
 | 
					      - ".gitmodules"
 | 
				
			||||||
 | 
					      - ".travis.yml"
 | 
				
			||||||
      - "docs/**"
 | 
					      - "docs/**"
 | 
				
			||||||
 | 
					      - "wheels/**"
 | 
				
			||||||
  pull_request:
 | 
					  pull_request:
 | 
				
			||||||
    paths-ignore:
 | 
					    paths-ignore:
 | 
				
			||||||
      - ".github/workflows/docs.yml"
 | 
					      - ".github/workflows/docs.yml"
 | 
				
			||||||
 | 
					      - ".github/workflows/wheels*"
 | 
				
			||||||
 | 
					      - ".gitmodules"
 | 
				
			||||||
 | 
					      - ".travis.yml"
 | 
				
			||||||
      - "docs/**"
 | 
					      - "docs/**"
 | 
				
			||||||
 | 
					      - "wheels/**"
 | 
				
			||||||
  workflow_dispatch:
 | 
					  workflow_dispatch:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
permissions:
 | 
					permissions:
 | 
				
			||||||
| 
						 | 
					@ -31,7 +39,7 @@ jobs:
 | 
				
			||||||
        python-version: [
 | 
					        python-version: [
 | 
				
			||||||
          "pypy3.10",
 | 
					          "pypy3.10",
 | 
				
			||||||
          "pypy3.9",
 | 
					          "pypy3.9",
 | 
				
			||||||
          "3.12-dev",
 | 
					          "3.12",
 | 
				
			||||||
          "3.11",
 | 
					          "3.11",
 | 
				
			||||||
          "3.10",
 | 
					          "3.10",
 | 
				
			||||||
          "3.9",
 | 
					          "3.9",
 | 
				
			||||||
| 
						 | 
					@ -48,7 +56,7 @@ jobs:
 | 
				
			||||||
    name: ${{ matrix.os }} Python ${{ matrix.python-version }}
 | 
					    name: ${{ matrix.os }} Python ${{ matrix.python-version }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
    - uses: actions/checkout@v3
 | 
					    - uses: actions/checkout@v4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    - name: Set up Python ${{ matrix.python-version }}
 | 
					    - name: Set up Python ${{ matrix.python-version }}
 | 
				
			||||||
      uses: actions/setup-python@v4
 | 
					      uses: actions/setup-python@v4
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										40
									
								
								.github/workflows/wheels-build.sh
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										40
									
								
								.github/workflows/wheels-build.sh
									
									
									
									
										vendored
									
									
										Executable file
									
								
							| 
						 | 
					@ -0,0 +1,40 @@
 | 
				
			||||||
 | 
					#!/bin/bash
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
 | 
				
			||||||
 | 
					  # webp, zstd, xz, libtiff, libxcb cause a conflict with building webp, libtiff, libxcb
 | 
				
			||||||
 | 
					  # libxdmcp causes an issue on macOS < 11
 | 
				
			||||||
 | 
					  # curl from brew requires zstd, use system curl
 | 
				
			||||||
 | 
					  # if php is installed, brew tries to reinstall these after installing openblas
 | 
				
			||||||
 | 
					  # remove lcms2 and libpng to fix building openjpeg on arm64
 | 
				
			||||||
 | 
					  brew remove --ignore-dependencies webp zstd xz libpng libtiff libxcb libxdmcp curl php lcms2 ghostscript
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  brew install pkg-config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if [[ "$PLAT" == "arm64" ]]; then
 | 
				
			||||||
 | 
					    export MACOSX_DEPLOYMENT_TARGET="11.0"
 | 
				
			||||||
 | 
					  else
 | 
				
			||||||
 | 
					    export MACOSX_DEPLOYMENT_TARGET="10.10"
 | 
				
			||||||
 | 
					  fi
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if [[ "$MB_PYTHON_VERSION" == pypy3* ]]; then
 | 
				
			||||||
 | 
					  MB_PYTHON_OSX_VER="10.9"
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					echo "::group::Install a virtualenv"
 | 
				
			||||||
 | 
					  source wheels/multibuild/common_utils.sh
 | 
				
			||||||
 | 
					  source wheels/multibuild/travis_steps.sh
 | 
				
			||||||
 | 
					  python3 -m pip install virtualenv
 | 
				
			||||||
 | 
					  before_install
 | 
				
			||||||
 | 
					echo "::endgroup::"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					echo "::group::Build wheel"
 | 
				
			||||||
 | 
					  build_wheel
 | 
				
			||||||
 | 
					  ls -l "${GITHUB_WORKSPACE}/${WHEEL_SDIR}/"
 | 
				
			||||||
 | 
					echo "::endgroup::"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if [[ $MACOSX_DEPLOYMENT_TARGET != "11.0" ]]; then
 | 
				
			||||||
 | 
					  echo "::group::Test wheel"
 | 
				
			||||||
 | 
					    install_run
 | 
				
			||||||
 | 
					  echo "::endgroup::"
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
							
								
								
									
										69
									
								
								.github/workflows/wheels-linux.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								.github/workflows/wheels-linux.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,69 @@
 | 
				
			||||||
 | 
					name: Build Linux wheels
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					on:
 | 
				
			||||||
 | 
					  workflow_call:
 | 
				
			||||||
 | 
					    inputs:
 | 
				
			||||||
 | 
					      artifacts-name:
 | 
				
			||||||
 | 
					        required: true
 | 
				
			||||||
 | 
					        type: string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					env:
 | 
				
			||||||
 | 
					  CONFIG_PATH: "wheels/config.sh"
 | 
				
			||||||
 | 
					  REPO_DIR: "."
 | 
				
			||||||
 | 
					  TEST_DEPENDS: "pytest pytest-timeout"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					jobs:
 | 
				
			||||||
 | 
					  build:
 | 
				
			||||||
 | 
					    name: ${{ matrix.python }} ${{ matrix.mb-ml-libc }}${{ matrix.mb-ml-ver }}
 | 
				
			||||||
 | 
					    runs-on: "ubuntu-latest"
 | 
				
			||||||
 | 
					    strategy:
 | 
				
			||||||
 | 
					      fail-fast: false
 | 
				
			||||||
 | 
					      matrix:
 | 
				
			||||||
 | 
					        python: [
 | 
				
			||||||
 | 
					          "pypy3.9-7.3.13",
 | 
				
			||||||
 | 
					          "pypy3.10-7.3.13",
 | 
				
			||||||
 | 
					          "3.8",
 | 
				
			||||||
 | 
					          "3.9",
 | 
				
			||||||
 | 
					          "3.10",
 | 
				
			||||||
 | 
					          "3.11",
 | 
				
			||||||
 | 
					          "3.12",
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					        mb-ml-libc: [ "manylinux" ]
 | 
				
			||||||
 | 
					        mb-ml-ver: [ 2014, "_2_28" ]
 | 
				
			||||||
 | 
					        include:
 | 
				
			||||||
 | 
					          - python: "3.8"
 | 
				
			||||||
 | 
					            mb-ml-libc: "musllinux"
 | 
				
			||||||
 | 
					            mb-ml-ver: "_1_1"
 | 
				
			||||||
 | 
					          - python: "3.9"
 | 
				
			||||||
 | 
					            mb-ml-libc: "musllinux"
 | 
				
			||||||
 | 
					            mb-ml-ver: "_1_1"
 | 
				
			||||||
 | 
					          - python: "3.10"
 | 
				
			||||||
 | 
					            mb-ml-libc: "musllinux"
 | 
				
			||||||
 | 
					            mb-ml-ver: "_1_1"
 | 
				
			||||||
 | 
					          - python: "3.11"
 | 
				
			||||||
 | 
					            mb-ml-libc: "musllinux"
 | 
				
			||||||
 | 
					            mb-ml-ver: "_1_1"
 | 
				
			||||||
 | 
					          - python: "3.12"
 | 
				
			||||||
 | 
					            mb-ml-libc: "musllinux"
 | 
				
			||||||
 | 
					            mb-ml-ver: "_1_1"
 | 
				
			||||||
 | 
					    env:
 | 
				
			||||||
 | 
					      MB_PYTHON_VERSION: ${{ matrix.python }}
 | 
				
			||||||
 | 
					      MB_ML_LIBC: ${{ matrix.mb-ml-libc }}
 | 
				
			||||||
 | 
					      MB_ML_VER: ${{ matrix.mb-ml-ver }}
 | 
				
			||||||
 | 
					    steps:
 | 
				
			||||||
 | 
					      - uses: actions/checkout@v4
 | 
				
			||||||
 | 
					        with:
 | 
				
			||||||
 | 
					          submodules: true
 | 
				
			||||||
 | 
					      - uses: actions/setup-python@v4
 | 
				
			||||||
 | 
					        with:
 | 
				
			||||||
 | 
					          python-version: "3.x"
 | 
				
			||||||
 | 
					      - name: Build Wheel
 | 
				
			||||||
 | 
					        run: .github/workflows/wheels-build.sh
 | 
				
			||||||
 | 
					      - uses: actions/upload-artifact@v3
 | 
				
			||||||
 | 
					        with:
 | 
				
			||||||
 | 
					          name: ${{ inputs.artifacts-name }}
 | 
				
			||||||
 | 
					          path: wheelhouse/*.whl
 | 
				
			||||||
 | 
					      # Uncomment to get SSH access for testing
 | 
				
			||||||
 | 
					      # - name: Setup tmate session
 | 
				
			||||||
 | 
					      #   if: failure()
 | 
				
			||||||
 | 
					      #   uses: mxschmitt/action-tmate@v3
 | 
				
			||||||
							
								
								
									
										57
									
								
								.github/workflows/wheels-macos.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								.github/workflows/wheels-macos.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,57 @@
 | 
				
			||||||
 | 
					name: Build macOS wheels
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					on:
 | 
				
			||||||
 | 
					  workflow_call:
 | 
				
			||||||
 | 
					    inputs:
 | 
				
			||||||
 | 
					      artifacts-name:
 | 
				
			||||||
 | 
					        required: true
 | 
				
			||||||
 | 
					        type: string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					env:
 | 
				
			||||||
 | 
					  CONFIG_PATH: "wheels/config.sh"
 | 
				
			||||||
 | 
					  REPO_DIR: "."
 | 
				
			||||||
 | 
					  TEST_DEPENDS: "pytest pytest-timeout"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					jobs:
 | 
				
			||||||
 | 
					  build:
 | 
				
			||||||
 | 
					    name: ${{ matrix.python }} ${{ matrix.platform }}
 | 
				
			||||||
 | 
					    runs-on: "macos-latest"
 | 
				
			||||||
 | 
					    strategy:
 | 
				
			||||||
 | 
					      fail-fast: false
 | 
				
			||||||
 | 
					      matrix:
 | 
				
			||||||
 | 
					        python: [
 | 
				
			||||||
 | 
					          "pypy3.9-7.3.13",
 | 
				
			||||||
 | 
					          "pypy3.10-7.3.13",
 | 
				
			||||||
 | 
					          "3.8",
 | 
				
			||||||
 | 
					          "3.9",
 | 
				
			||||||
 | 
					          "3.10",
 | 
				
			||||||
 | 
					          "3.11",
 | 
				
			||||||
 | 
					          "3.12",
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					        platform: [ "x86_64", "arm64" ]
 | 
				
			||||||
 | 
					        exclude:
 | 
				
			||||||
 | 
					          - python: "pypy3.9-7.3.13"
 | 
				
			||||||
 | 
					            platform: "arm64"
 | 
				
			||||||
 | 
					          - python: "pypy3.10-7.3.13"
 | 
				
			||||||
 | 
					            platform: "arm64"
 | 
				
			||||||
 | 
					    env:
 | 
				
			||||||
 | 
					      PLAT: ${{ matrix.platform }}
 | 
				
			||||||
 | 
					      MB_PYTHON_VERSION: ${{ matrix.python }}
 | 
				
			||||||
 | 
					      TRAVIS_OS_NAME: "osx"
 | 
				
			||||||
 | 
					    steps:
 | 
				
			||||||
 | 
					      - uses: actions/checkout@v4
 | 
				
			||||||
 | 
					        with:
 | 
				
			||||||
 | 
					          submodules: true
 | 
				
			||||||
 | 
					      - uses: actions/setup-python@v4
 | 
				
			||||||
 | 
					        with:
 | 
				
			||||||
 | 
					          python-version: "3.x"
 | 
				
			||||||
 | 
					      - name: Build Wheel
 | 
				
			||||||
 | 
					        run: .github/workflows/wheels-build.sh
 | 
				
			||||||
 | 
					      - uses: actions/upload-artifact@v3
 | 
				
			||||||
 | 
					        with:
 | 
				
			||||||
 | 
					          name: ${{ inputs.artifacts-name }}
 | 
				
			||||||
 | 
					          path: wheelhouse/*.whl
 | 
				
			||||||
 | 
					      # Uncomment to get SSH access for testing
 | 
				
			||||||
 | 
					      # - name: Setup tmate session
 | 
				
			||||||
 | 
					      #   if: failure()
 | 
				
			||||||
 | 
					      #   uses: mxschmitt/action-tmate@v3
 | 
				
			||||||
							
								
								
									
										42
									
								
								.github/workflows/wheels.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								.github/workflows/wheels.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,42 @@
 | 
				
			||||||
 | 
					name: Wheels
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					on:
 | 
				
			||||||
 | 
					  push:
 | 
				
			||||||
 | 
					    paths:
 | 
				
			||||||
 | 
					      - ".github/workflows/wheels*.yml"
 | 
				
			||||||
 | 
					      - "wheels/*"
 | 
				
			||||||
 | 
					    tags:
 | 
				
			||||||
 | 
					      - "*"
 | 
				
			||||||
 | 
					  pull_request:
 | 
				
			||||||
 | 
					    paths:
 | 
				
			||||||
 | 
					      - ".github/workflows/wheels*.yml"
 | 
				
			||||||
 | 
					      - "wheels/*"
 | 
				
			||||||
 | 
					  workflow_dispatch:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					permissions:
 | 
				
			||||||
 | 
					  contents: read
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					concurrency:
 | 
				
			||||||
 | 
					  group: ${{ github.workflow }}-${{ github.ref }}
 | 
				
			||||||
 | 
					  cancel-in-progress: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					jobs:
 | 
				
			||||||
 | 
					  macos:
 | 
				
			||||||
 | 
					    uses: ./.github/workflows/wheels-macos.yml
 | 
				
			||||||
 | 
					    with:
 | 
				
			||||||
 | 
					      artifacts-name: "wheels"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  linux:
 | 
				
			||||||
 | 
					    uses: ./.github/workflows/wheels-linux.yml
 | 
				
			||||||
 | 
					    with:
 | 
				
			||||||
 | 
					      artifacts-name: "wheels"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  success:
 | 
				
			||||||
 | 
					    permissions:
 | 
				
			||||||
 | 
					      contents: none
 | 
				
			||||||
 | 
					    needs: [macos, linux]
 | 
				
			||||||
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
 | 
					    name: Wheels Successful
 | 
				
			||||||
 | 
					    steps:
 | 
				
			||||||
 | 
					      - name: Success
 | 
				
			||||||
 | 
					        run: echo Wheels Successful
 | 
				
			||||||
							
								
								
									
										3
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,3 @@
 | 
				
			||||||
 | 
					[submodule "multibuild"]
 | 
				
			||||||
 | 
					    path = wheels/multibuild
 | 
				
			||||||
 | 
					    url = https://github.com/multi-build/multibuild.git
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,12 @@
 | 
				
			||||||
repos:
 | 
					repos:
 | 
				
			||||||
  - repo: https://github.com/psf/black
 | 
					  - repo: https://github.com/asottile/pyupgrade
 | 
				
			||||||
    rev: 23.3.0
 | 
					    rev: v3.13.0
 | 
				
			||||||
 | 
					    hooks:
 | 
				
			||||||
 | 
					      - id: pyupgrade
 | 
				
			||||||
 | 
					        args: [--py38-plus]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - repo: https://github.com/psf/black-pre-commit-mirror
 | 
				
			||||||
 | 
					    rev: 23.9.1
 | 
				
			||||||
    hooks:
 | 
					    hooks:
 | 
				
			||||||
      - id: black
 | 
					      - id: black
 | 
				
			||||||
        args: [--target-version=py38]
 | 
					        args: [--target-version=py38]
 | 
				
			||||||
| 
						 | 
					@ -23,17 +29,17 @@ 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.5.1
 | 
					    rev: v1.5.4
 | 
				
			||||||
    hooks:
 | 
					    hooks:
 | 
				
			||||||
      - id: remove-tabs
 | 
					      - id: remove-tabs
 | 
				
			||||||
        exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$)
 | 
					        exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - repo: https://github.com/PyCQA/flake8
 | 
					  - repo: https://github.com/PyCQA/flake8
 | 
				
			||||||
    rev: 6.0.0
 | 
					    rev: 6.1.0
 | 
				
			||||||
    hooks:
 | 
					    hooks:
 | 
				
			||||||
      - id: flake8
 | 
					      - id: flake8
 | 
				
			||||||
        additional_dependencies:
 | 
					        additional_dependencies:
 | 
				
			||||||
          [flake8-2020, flake8-errmsg, flake8-implicit-str-concat]
 | 
					          [flake8-2020, flake8-errmsg, flake8-implicit-str-concat, flake8-logging]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - repo: https://github.com/pre-commit/pygrep-hooks
 | 
					  - repo: https://github.com/pre-commit/pygrep-hooks
 | 
				
			||||||
    rev: v1.10.0
 | 
					    rev: v1.10.0
 | 
				
			||||||
| 
						 | 
					@ -44,23 +50,28 @@ repos:
 | 
				
			||||||
  - repo: https://github.com/pre-commit/pre-commit-hooks
 | 
					  - repo: https://github.com/pre-commit/pre-commit-hooks
 | 
				
			||||||
    rev: v4.4.0
 | 
					    rev: v4.4.0
 | 
				
			||||||
    hooks:
 | 
					    hooks:
 | 
				
			||||||
 | 
					      - id: check-executables-have-shebangs
 | 
				
			||||||
      - id: check-merge-conflict
 | 
					      - id: check-merge-conflict
 | 
				
			||||||
      - id: check-json
 | 
					      - id: check-json
 | 
				
			||||||
      - id: check-toml
 | 
					      - id: check-toml
 | 
				
			||||||
      - id: check-yaml
 | 
					      - id: check-yaml
 | 
				
			||||||
 | 
					      - id: end-of-file-fixer
 | 
				
			||||||
 | 
					        exclude: ^Tests/images/
 | 
				
			||||||
 | 
					      - id: trailing-whitespace
 | 
				
			||||||
 | 
					        exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - repo: https://github.com/sphinx-contrib/sphinx-lint
 | 
					  - repo: https://github.com/sphinx-contrib/sphinx-lint
 | 
				
			||||||
    rev: v0.6.7
 | 
					    rev: v0.6.8
 | 
				
			||||||
    hooks:
 | 
					    hooks:
 | 
				
			||||||
      - id: sphinx-lint
 | 
					      - id: sphinx-lint
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - repo: https://github.com/tox-dev/pyproject-fmt
 | 
					  - repo: https://github.com/tox-dev/pyproject-fmt
 | 
				
			||||||
    rev: 0.12.1
 | 
					    rev: 1.2.0
 | 
				
			||||||
    hooks:
 | 
					    hooks:
 | 
				
			||||||
      - id: pyproject-fmt
 | 
					      - id: pyproject-fmt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - repo: https://github.com/abravalheri/validate-pyproject
 | 
					  - repo: https://github.com/abravalheri/validate-pyproject
 | 
				
			||||||
    rev: v0.13
 | 
					    rev: v0.14
 | 
				
			||||||
    hooks:
 | 
					    hooks:
 | 
				
			||||||
      - id: validate-pyproject
 | 
					      - id: validate-pyproject
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										135
									
								
								.travis.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								.travis.yml
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,135 @@
 | 
				
			||||||
 | 
					if: tag IS present
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					env:
 | 
				
			||||||
 | 
					  global:
 | 
				
			||||||
 | 
					      - CONFIG_PATH=wheels/config.sh
 | 
				
			||||||
 | 
					      - REPO_DIR=.
 | 
				
			||||||
 | 
					      - PLAT=aarch64
 | 
				
			||||||
 | 
					      - TEST_DEPENDS=pytest-timeout
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					language: python
 | 
				
			||||||
 | 
					# Default Python version is usually 3.6
 | 
				
			||||||
 | 
					python: "3.11"
 | 
				
			||||||
 | 
					dist: focal
 | 
				
			||||||
 | 
					services: docker
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					jobs:
 | 
				
			||||||
 | 
					  include:
 | 
				
			||||||
 | 
					    - name: "3.8 Focal manylinux2014 aarch64"
 | 
				
			||||||
 | 
					      os: linux
 | 
				
			||||||
 | 
					      arch: arm64
 | 
				
			||||||
 | 
					      env:
 | 
				
			||||||
 | 
					        - MB_ML_VER=2014
 | 
				
			||||||
 | 
					        - MB_PYTHON_VERSION=3.8
 | 
				
			||||||
 | 
					    - name: "3.8 Focal manylinux_2_28 aarch64"
 | 
				
			||||||
 | 
					      os: linux
 | 
				
			||||||
 | 
					      arch: arm64
 | 
				
			||||||
 | 
					      env:
 | 
				
			||||||
 | 
					        - MB_ML_VER="_2_28"
 | 
				
			||||||
 | 
					        - MB_PYTHON_VERSION=3.8
 | 
				
			||||||
 | 
					    - name: "3.8 musllinux_1_1 aarch64"
 | 
				
			||||||
 | 
					      os: linux
 | 
				
			||||||
 | 
					      arch: arm64
 | 
				
			||||||
 | 
					      env:
 | 
				
			||||||
 | 
					        - MB_ML_VER="_1_1"
 | 
				
			||||||
 | 
					        - MB_ML_LIBC="musllinux"
 | 
				
			||||||
 | 
					        - MB_PYTHON_VERSION=3.8
 | 
				
			||||||
 | 
					    - name: "3.9 Focal manylinux2014 aarch64"
 | 
				
			||||||
 | 
					      os: linux
 | 
				
			||||||
 | 
					      arch: arm64
 | 
				
			||||||
 | 
					      env:
 | 
				
			||||||
 | 
					        - MB_ML_VER=2014
 | 
				
			||||||
 | 
					        - MB_PYTHON_VERSION=3.9
 | 
				
			||||||
 | 
					    - name: "3.9 Focal manylinux_2_28 aarch64"
 | 
				
			||||||
 | 
					      os: linux
 | 
				
			||||||
 | 
					      arch: arm64
 | 
				
			||||||
 | 
					      env:
 | 
				
			||||||
 | 
					        - MB_ML_VER="_2_28"
 | 
				
			||||||
 | 
					        - MB_PYTHON_VERSION=3.9
 | 
				
			||||||
 | 
					    - name: "3.9 musllinux_1_1 aarch64"
 | 
				
			||||||
 | 
					      os: linux
 | 
				
			||||||
 | 
					      arch: arm64
 | 
				
			||||||
 | 
					      env:
 | 
				
			||||||
 | 
					        - MB_ML_VER="_1_1"
 | 
				
			||||||
 | 
					        - MB_ML_LIBC="musllinux"
 | 
				
			||||||
 | 
					        - MB_PYTHON_VERSION=3.9
 | 
				
			||||||
 | 
					    - name: "3.10 Focal manylinux2014 aarch64"
 | 
				
			||||||
 | 
					      os: linux
 | 
				
			||||||
 | 
					      arch: arm64
 | 
				
			||||||
 | 
					      env:
 | 
				
			||||||
 | 
					        - MB_ML_VER=2014
 | 
				
			||||||
 | 
					        - MB_PYTHON_VERSION=3.10
 | 
				
			||||||
 | 
					    - name: "3.10 Focal manylinux_2_28 aarch64"
 | 
				
			||||||
 | 
					      os: linux
 | 
				
			||||||
 | 
					      arch: arm64
 | 
				
			||||||
 | 
					      env:
 | 
				
			||||||
 | 
					        - MB_ML_VER="_2_28"
 | 
				
			||||||
 | 
					        - MB_PYTHON_VERSION=3.10
 | 
				
			||||||
 | 
					    - name: "3.10 musllinux_1_1 aarch64"
 | 
				
			||||||
 | 
					      os: linux
 | 
				
			||||||
 | 
					      arch: arm64
 | 
				
			||||||
 | 
					      env:
 | 
				
			||||||
 | 
					        - MB_ML_VER="_1_1"
 | 
				
			||||||
 | 
					        - MB_ML_LIBC="musllinux"
 | 
				
			||||||
 | 
					        - MB_PYTHON_VERSION=3.10
 | 
				
			||||||
 | 
					    - name: "3.11 Focal manylinux_2_28 aarch64"
 | 
				
			||||||
 | 
					      os: linux
 | 
				
			||||||
 | 
					      arch: arm64
 | 
				
			||||||
 | 
					      env:
 | 
				
			||||||
 | 
					        - MB_ML_VER=2014
 | 
				
			||||||
 | 
					        - MB_PYTHON_VERSION=3.11
 | 
				
			||||||
 | 
					    - name: "3.11 Focal manylinux_2_28 aarch64"
 | 
				
			||||||
 | 
					      os: linux
 | 
				
			||||||
 | 
					      arch: arm64
 | 
				
			||||||
 | 
					      env:
 | 
				
			||||||
 | 
					        - MB_ML_VER="_2_28"
 | 
				
			||||||
 | 
					        - MB_PYTHON_VERSION=3.11
 | 
				
			||||||
 | 
					    - name: "3.11 musllinux_1_1 aarch64"
 | 
				
			||||||
 | 
					      os: linux
 | 
				
			||||||
 | 
					      arch: arm64
 | 
				
			||||||
 | 
					      env:
 | 
				
			||||||
 | 
					        - MB_ML_VER="_1_1"
 | 
				
			||||||
 | 
					        - MB_ML_LIBC="musllinux"
 | 
				
			||||||
 | 
					        - MB_PYTHON_VERSION=3.11
 | 
				
			||||||
 | 
					    - name: "3.12 Focal manylinux_2_28 aarch64"
 | 
				
			||||||
 | 
					      os: linux
 | 
				
			||||||
 | 
					      arch: arm64
 | 
				
			||||||
 | 
					      env:
 | 
				
			||||||
 | 
					        - MB_ML_VER=2014
 | 
				
			||||||
 | 
					        - MB_PYTHON_VERSION=3.12
 | 
				
			||||||
 | 
					    - name: "3.12 Focal manylinux_2_28 aarch64"
 | 
				
			||||||
 | 
					      os: linux
 | 
				
			||||||
 | 
					      arch: arm64
 | 
				
			||||||
 | 
					      env:
 | 
				
			||||||
 | 
					        - MB_ML_VER="_2_28"
 | 
				
			||||||
 | 
					        - MB_PYTHON_VERSION=3.12
 | 
				
			||||||
 | 
					    - name: "3.12 musllinux_1_1 aarch64"
 | 
				
			||||||
 | 
					      os: linux
 | 
				
			||||||
 | 
					      arch: arm64
 | 
				
			||||||
 | 
					      env:
 | 
				
			||||||
 | 
					        - MB_ML_VER="_1_1"
 | 
				
			||||||
 | 
					        - MB_ML_LIBC="musllinux"
 | 
				
			||||||
 | 
					        - MB_PYTHON_VERSION=3.12
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					before_install:
 | 
				
			||||||
 | 
					    - source wheels/multibuild/common_utils.sh
 | 
				
			||||||
 | 
					    - source wheels/multibuild/travis_steps.sh
 | 
				
			||||||
 | 
					    - before_install
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					install:
 | 
				
			||||||
 | 
					    - build_multilinux aarch64 build_wheel
 | 
				
			||||||
 | 
					    - ls -l "${TRAVIS_BUILD_DIR}/${WHEEL_SDIR}/"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					script:
 | 
				
			||||||
 | 
					    - install_run
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Upload wheels to GitHub Releases
 | 
				
			||||||
 | 
					deploy:
 | 
				
			||||||
 | 
					  provider: releases
 | 
				
			||||||
 | 
					  api_key: $GITHUB_RELEASE_TOKEN
 | 
				
			||||||
 | 
					  file_glob: true
 | 
				
			||||||
 | 
					  file: "${TRAVIS_BUILD_DIR}/${WHEEL_SDIR}/*.whl"
 | 
				
			||||||
 | 
					  on:
 | 
				
			||||||
 | 
					    repo: python-pillow/Pillow
 | 
				
			||||||
 | 
					    tags: true
 | 
				
			||||||
 | 
					  skip_cleanup: true
 | 
				
			||||||
							
								
								
									
										112
									
								
								CHANGES.rst
									
									
									
									
									
								
							
							
						
						
									
										112
									
								
								CHANGES.rst
									
									
									
									
									
								
							| 
						 | 
					@ -5,9 +5,90 @@ Changelog (Pillow)
 | 
				
			||||||
10.1.0 (unreleased)
 | 
					10.1.0 (unreleased)
 | 
				
			||||||
-------------------
 | 
					-------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Allow saving I;16B images as PNG #7302
 | 
				
			||||||
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Corrected drawing I;16 points and writing I;16 text #7257
 | 
				
			||||||
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Set blue channel to 128 for BC5S #7413
 | 
				
			||||||
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Increase flexibility when reading IPTC fields #7319
 | 
				
			||||||
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Set C palette to be empty by default #7289
 | 
				
			||||||
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Added gs_binary to control Ghostscript use on all platforms #7392
 | 
				
			||||||
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Read bounding box information from the trailer of EPS files if specified #7382
 | 
				
			||||||
 | 
					  [nopperl, radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Added reading 8-bit color DDS images #7426
 | 
				
			||||||
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Added has_transparency_data #7420
 | 
				
			||||||
 | 
					  [radarhere, hugovk]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Fixed bug when reading BC5S DDS images #7401
 | 
				
			||||||
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Prevent TIFF orientation from being applied more than once #7383
 | 
				
			||||||
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Use previous pixel alpha for QOI_OP_RGB #7357
 | 
				
			||||||
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Added BC5U reading #7358
 | 
				
			||||||
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Allow getpixel() to accept a list #7355
 | 
				
			||||||
 | 
					  [radarhere, homm]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Allow GaussianBlur and BoxBlur to accept a sequence of x and y radii #7336
 | 
				
			||||||
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Expand JPEG buffer size when saving optimized or progressive #7345
 | 
				
			||||||
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Added session type check for Linux in ImageGrab.grabclipboard() #7332
 | 
				
			||||||
 | 
					  [TheNooB2706, radarhere, hugovk]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Allow "loop=None" when saving GIF images #7329
 | 
				
			||||||
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Fixed transparency when saving P mode images to PDF #7323
 | 
				
			||||||
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Added saving LA images as PDFs #7299
 | 
				
			||||||
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Set SMaskInData to 1 for PDFs with alpha #7316, #7317
 | 
				
			||||||
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Changed Image mode property to be read-only by default #7307
 | 
				
			||||||
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Silence exceptions in _repr_jpeg_ and _repr_png_ #7266
 | 
				
			||||||
 | 
					  [mtreinish, radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Do not use transparency when saving GIF if it has been removed when normalizing mode #7284
 | 
				
			||||||
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Fix missing symbols when libtiff depends on libjpeg #7270
 | 
					- Fix missing symbols when libtiff depends on libjpeg #7270
 | 
				
			||||||
  [heitbaum]
 | 
					  [heitbaum]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					10.0.1 (2023-09-15)
 | 
				
			||||||
 | 
					-------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Updated libwebp to 1.3.2 #7395
 | 
				
			||||||
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Updated zlib to 1.3 #7344
 | 
				
			||||||
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
10.0.0 (2023-07-01)
 | 
					10.0.0 (2023-07-01)
 | 
				
			||||||
-------------------
 | 
					-------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5735,8 +5816,8 @@ http://svn.effbot.org/public/pil/
 | 
				
			||||||
  a polyline, independent of line angle.
 | 
					  a polyline, independent of line angle.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Fixed bearing calculation and clipping in the ImageFont truetype
 | 
					- Fixed bearing calculation and clipping in the ImageFont truetype
 | 
				
			||||||
  renderer; this could lead to clipped text, or crashes in the low-
 | 
					  renderer; this could lead to clipped text, or crashes in the low-level
 | 
				
			||||||
  level _imagingft module.  (based on input from Adam Twardoch and
 | 
					  _imagingft module.  (based on input from Adam Twardoch and
 | 
				
			||||||
  others).
 | 
					  others).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Added ImageQt wrapper module, for converting PIL Image objects to
 | 
					- Added ImageQt wrapper module, for converting PIL Image objects to
 | 
				
			||||||
| 
						 | 
					@ -5817,8 +5898,7 @@ http://svn.effbot.org/public/pil/
 | 
				
			||||||
1.1.5c2 and 1.1.5 final
 | 
					1.1.5c2 and 1.1.5 final
 | 
				
			||||||
-----------------------
 | 
					-----------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Added experimental PERSPECTIVE transform method (from Jeff Breiden-
 | 
					- Added experimental PERSPECTIVE transform method (from Jeff Breidenbach).
 | 
				
			||||||
  bach).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
1.1.5c1
 | 
					1.1.5c1
 | 
				
			||||||
-------
 | 
					-------
 | 
				
			||||||
| 
						 | 
					@ -5890,8 +5970,8 @@ http://svn.effbot.org/public/pil/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Fixed BILINEAR/BICUBIC/ANTIALIAS filtering for mode "LA".
 | 
					- Fixed BILINEAR/BICUBIC/ANTIALIAS filtering for mode "LA".
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Added "getcolors()" method.  This is similar to the existing histo-
 | 
					- Added "getcolors()" method.  This is similar to the existing histogram
 | 
				
			||||||
  gram method, but looks at color values instead of individual layers,
 | 
					  method, but looks at color values instead of individual layers,
 | 
				
			||||||
  and returns an unsorted list of (count, color) tuples.
 | 
					  and returns an unsorted list of (count, color) tuples.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  By default, the method returns None if finds more than 256 colors.
 | 
					  By default, the method returns None if finds more than 256 colors.
 | 
				
			||||||
| 
						 | 
					@ -6107,8 +6187,8 @@ http://svn.effbot.org/public/pil/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Added limited support for "bitfield compression" in BMP files
 | 
					- Added limited support for "bitfield compression" in BMP files
 | 
				
			||||||
  and DIB buffers, for 15-bit, 16-bit, and 32-bit images.  This
 | 
					  and DIB buffers, for 15-bit, 16-bit, and 32-bit images.  This
 | 
				
			||||||
  also fixes a problem with ImageGrab module when copying screen-
 | 
					  also fixes a problem with ImageGrab module when copying screendumps
 | 
				
			||||||
  dumps from the clipboard on 15/16/32-bit displays.
 | 
					  from the clipboard on 15/16/32-bit displays.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Added experimental WAL (Quake 2 textures) loader.  To use this
 | 
					- Added experimental WAL (Quake 2 textures) loader.  To use this
 | 
				
			||||||
  loader, import WalImageFile and call the "open" method in that
 | 
					  loader, import WalImageFile and call the "open" method in that
 | 
				
			||||||
| 
						 | 
					@ -6219,8 +6299,8 @@ http://svn.effbot.org/public/pil/
 | 
				
			||||||
1.1.3 final
 | 
					1.1.3 final
 | 
				
			||||||
-----------
 | 
					-----------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Made setup.py look for old versions of zlib.  For some back-
 | 
					- Made setup.py look for old versions of zlib.  For some background,
 | 
				
			||||||
  ground, see: https://zlib.net/advisory-2002-03-11.txt
 | 
					  see: https://zlib.net/advisory-2002-03-11.txt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
1.1.3c2
 | 
					1.1.3c2
 | 
				
			||||||
-------
 | 
					-------
 | 
				
			||||||
| 
						 | 
					@ -6411,8 +6491,8 @@ http://svn.effbot.org/public/pil/
 | 
				
			||||||
  supports all major PIL image modes (including F and I).
 | 
					  supports all major PIL image modes (including F and I).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- The ImageFile module now includes a Parser class, which can
 | 
					- The ImageFile module now includes a Parser class, which can
 | 
				
			||||||
  be used to incrementally decode an image file (while down-
 | 
					  be used to incrementally decode an image file (while downloading
 | 
				
			||||||
  loading it from the net, for example).  See the handbook for
 | 
					  it from the net, for example).  See the handbook for
 | 
				
			||||||
  details.
 | 
					  details.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- "show" now converts non-standard modes to "L" or "RGB" (as
 | 
					- "show" now converts non-standard modes to "L" or "RGB" (as
 | 
				
			||||||
| 
						 | 
					@ -6550,8 +6630,8 @@ http://svn.effbot.org/public/pil/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- The Image "transform" method now supports Image.QUAD transforms.
 | 
					- The Image "transform" method now supports Image.QUAD transforms.
 | 
				
			||||||
  The data argument is an 8-tuple giving the upper left, lower
 | 
					  The data argument is an 8-tuple giving the upper left, lower
 | 
				
			||||||
  left, lower right, and upper right corner of the source quadri-
 | 
					  left, lower right, and upper right corner of the source quadrilateral.
 | 
				
			||||||
  lateral.  Also added Image.MESH transform which takes a list
 | 
					  Also added Image.MESH transform which takes a list
 | 
				
			||||||
  of quadrilaterals.
 | 
					  of quadrilaterals.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- The Image "resize", "rotate", and "transform" methods now support
 | 
					- The Image "resize", "rotate", and "transform" methods now support
 | 
				
			||||||
| 
						 | 
					@ -6776,8 +6856,8 @@ The test suite includes 400 individual tests.
 | 
				
			||||||
  neither "short", "int" nor "long" are 32-bit wide.
 | 
					  neither "short", "int" nor "long" are 32-bit wide.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Added file= and data= keyword arguments to PhotoImage and BitmapImage.
 | 
					- Added file= and data= keyword arguments to PhotoImage and BitmapImage.
 | 
				
			||||||
  This allows you to use them as drop-in replacements for the corre-
 | 
					  This allows you to use them as drop-in replacements for the corresponding
 | 
				
			||||||
  sponding Tkinter classes.
 | 
					  Tkinter classes.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Removed bogus references to the crack coder (ImagingCrack).
 | 
					- Removed bogus references to the crack coder (ImagingCrack).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -29,3 +29,4 @@ global-exclude .git*
 | 
				
			||||||
global-exclude *.pyc
 | 
					global-exclude *.pyc
 | 
				
			||||||
global-exclude *.so
 | 
					global-exclude *.so
 | 
				
			||||||
prune .ci
 | 
					prune .ci
 | 
				
			||||||
 | 
					prune wheels
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										10
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								README.md
									
									
									
									
									
								
							| 
						 | 
					@ -45,12 +45,12 @@ As of 2019, Pillow development is
 | 
				
			||||||
            <a href="https://ci.appveyor.com/project/python-pillow/Pillow"><img
 | 
					            <a href="https://ci.appveyor.com/project/python-pillow/Pillow"><img
 | 
				
			||||||
                alt="AppVeyor CI build status (Windows)"
 | 
					                alt="AppVeyor CI build status (Windows)"
 | 
				
			||||||
                src="https://img.shields.io/appveyor/build/python-pillow/Pillow/main.svg?label=Windows%20build"></a>
 | 
					                src="https://img.shields.io/appveyor/build/python-pillow/Pillow/main.svg?label=Windows%20build"></a>
 | 
				
			||||||
            <a href="https://github.com/python-pillow/pillow-wheels/actions"><img
 | 
					            <a href="https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml"><img
 | 
				
			||||||
                alt="GitHub Actions wheels build status (Wheels)"
 | 
					                alt="GitHub Actions build status (Wheels)"
 | 
				
			||||||
                src="https://github.com/python-pillow/pillow-wheels/workflows/Wheels/badge.svg"></a>
 | 
					                src="https://github.com/python-pillow/Pillow/workflows/Wheels/badge.svg"></a>
 | 
				
			||||||
            <a href="https://app.travis-ci.com/github/python-pillow/pillow-wheels"><img
 | 
					            <a href="https://app.travis-ci.com/github/python-pillow/Pillow"><img
 | 
				
			||||||
                alt="Travis CI wheels build status (aarch64)"
 | 
					                alt="Travis CI wheels build status (aarch64)"
 | 
				
			||||||
                src="https://img.shields.io/travis/com/python-pillow/pillow-wheels/main.svg?label=aarch64%20wheels"></a>
 | 
					                src="https://img.shields.io/travis/com/python-pillow/Pillow/main.svg?label=aarch64%20wheels"></a>
 | 
				
			||||||
            <a href="https://app.codecov.io/gh/python-pillow/Pillow"><img
 | 
					            <a href="https://app.codecov.io/gh/python-pillow/Pillow"><img
 | 
				
			||||||
                alt="Code coverage"
 | 
					                alt="Code coverage"
 | 
				
			||||||
                src="https://codecov.io/gh/python-pillow/Pillow/branch/main/graph/badge.svg"></a>
 | 
					                src="https://codecov.io/gh/python-pillow/Pillow/branch/main/graph/badge.svg"></a>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										17
									
								
								RELEASING.md
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								RELEASING.md
									
									
									
									
									
								
							| 
						 | 
					@ -10,7 +10,7 @@ Released quarterly on January 2nd, April 1st, July 1st and October 15th.
 | 
				
			||||||
* [ ] Open a release ticket e.g. https://github.com/python-pillow/Pillow/issues/3154
 | 
					* [ ] Open a release ticket e.g. https://github.com/python-pillow/Pillow/issues/3154
 | 
				
			||||||
* [ ] Develop and prepare release in `main` branch.
 | 
					* [ ] Develop and prepare release in `main` branch.
 | 
				
			||||||
* [ ] Check [GitHub Actions](https://github.com/python-pillow/Pillow/actions) and [AppVeyor](https://ci.appveyor.com/project/python-pillow/Pillow) to confirm passing tests in `main` branch.
 | 
					* [ ] Check [GitHub Actions](https://github.com/python-pillow/Pillow/actions) and [AppVeyor](https://ci.appveyor.com/project/python-pillow/Pillow) to confirm passing tests in `main` branch.
 | 
				
			||||||
* [ ] Check that all of the wheel builds [Pillow Wheel Builder](https://github.com/python-pillow/pillow-wheels) pass the tests in Travis CI and GitHub Actions.
 | 
					* [ ] Check that all of the wheel builds pass the tests in the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml) and [Travis CI](https://app.travis-ci.com/github/python-pillow/pillow) jobs by manually triggering them.
 | 
				
			||||||
* [ ] In compliance with [PEP 440](https://peps.python.org/pep-0440/), update version identifier in `src/PIL/_version.py`
 | 
					* [ ] In compliance with [PEP 440](https://peps.python.org/pep-0440/), update version identifier in `src/PIL/_version.py`
 | 
				
			||||||
* [ ] Update `CHANGES.rst`.
 | 
					* [ ] Update `CHANGES.rst`.
 | 
				
			||||||
* [ ] Run pre-release check via `make release-test` in a freshly cloned repo.
 | 
					* [ ] Run pre-release check via `make release-test` in a freshly cloned repo.
 | 
				
			||||||
| 
						 | 
					@ -99,17 +99,14 @@ Released as needed privately to individual vendors for critical security-related
 | 
				
			||||||
## Binary Distributions
 | 
					## Binary Distributions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### macOS and Linux
 | 
					### macOS and Linux
 | 
				
			||||||
* [ ] Use the [Pillow Wheel Builder](https://github.com/python-pillow/pillow-wheels):
 | 
					* [ ] Download wheels from the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml)
 | 
				
			||||||
 | 
					  and copy into `dist/`. For example using [GitHub CLI](https://github.com/cli/cli):
 | 
				
			||||||
  ```bash
 | 
					  ```bash
 | 
				
			||||||
  git clone https://github.com/python-pillow/pillow-wheels
 | 
					  gh run download --dir dist
 | 
				
			||||||
  cd pillow-wheels
 | 
					  # select dist-x.y.z
 | 
				
			||||||
  ./update-pillow-tag.sh [[release tag]]
 | 
					 | 
				
			||||||
  ```
 | 
					 | 
				
			||||||
* [ ] Download wheels from the [Pillow Wheel Builder release](https://github.com/python-pillow/pillow-wheels/releases)
 | 
					 | 
				
			||||||
  and copy into `dist/`. For example using [GitHub CLI](https://github.com/cli/cli) from the main repo:
 | 
					 | 
				
			||||||
  ```bash
 | 
					 | 
				
			||||||
  gh release download --dir dist --pattern "*.whl" --repo python-pillow/pillow-wheels
 | 
					 | 
				
			||||||
  ```
 | 
					  ```
 | 
				
			||||||
 | 
					* [ ] Download the Linux aarch64 wheels created by Travis CI from [GitHub releases](https://github.com/python-pillow/Pillow/releases)
 | 
				
			||||||
 | 
					  and copy into `dist`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Windows
 | 
					### Windows
 | 
				
			||||||
* [ ] Download the artifacts from the [GitHub Actions "Test Windows" workflow](https://github.com/python-pillow/Pillow/actions/workflows/test-windows.yml)
 | 
					* [ ] Download the artifacts from the [GitHub Actions "Test Windows" workflow](https://github.com/python-pillow/Pillow/actions/workflows/test-windows.yml)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										0
									
								
								Tests/check_j2k_leaks.py
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						
									
										0
									
								
								Tests/check_j2k_leaks.py
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							| 
						 | 
					@ -91,7 +91,7 @@ def assert_image_equal(a, b, msg=None):
 | 
				
			||||||
        if HAS_UPLOADER:
 | 
					        if HAS_UPLOADER:
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
                url = test_image_results.upload(a, b)
 | 
					                url = test_image_results.upload(a, b)
 | 
				
			||||||
                logger.error(f"Url for test images: {url}")
 | 
					                logger.error("URL for test images: %s", url)
 | 
				
			||||||
            except Exception:
 | 
					            except Exception:
 | 
				
			||||||
                pass
 | 
					                pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -126,7 +126,7 @@ def assert_image_similar(a, b, epsilon, msg=None):
 | 
				
			||||||
        if HAS_UPLOADER:
 | 
					        if HAS_UPLOADER:
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
                url = test_image_results.upload(a, b)
 | 
					                url = test_image_results.upload(a, b)
 | 
				
			||||||
                logger.error(f"Url for test images: {url}")
 | 
					                logger.exception("URL for test images: %s", url)
 | 
				
			||||||
            except Exception:
 | 
					            except Exception:
 | 
				
			||||||
                pass
 | 
					                pass
 | 
				
			||||||
        raise e
 | 
					        raise e
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -22,4 +22,3 @@ and that the name of ICC shall not be used in advertising or publicity
 | 
				
			||||||
pertaining to distribution of the software without specific, written
 | 
					pertaining to distribution of the software without specific, written
 | 
				
			||||||
prior permission. ICC makes no representations about the suitability
 | 
					prior permission. ICC makes no representations about the suitability
 | 
				
			||||||
of this software for any purpose.
 | 
					of this software for any purpose.
 | 
				
			||||||
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 95 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/bc5u.dds
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Tests/images/bc5u.dds
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 181 B After Width: | Height: | Size: 180 B  | 
							
								
								
									
										0
									
								
								Tests/images/negative_size.ppm
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						
									
										0
									
								
								Tests/images/negative_size.ppm
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
								
								
									
										
											BIN
										
									
								
								Tests/images/palette.dds
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Tests/images/palette.dds
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								Tests/images/zero_bb_eof_before_boundingbox.eps
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Tests/images/zero_bb_eof_before_boundingbox.eps
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								Tests/images/zero_bb_trailer.eps
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Tests/images/zero_bb_trailer.eps
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
						 | 
					@ -6,6 +6,7 @@ import packaging
 | 
				
			||||||
import pytest
 | 
					import pytest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from PIL import Image, features
 | 
					from PIL import Image, features
 | 
				
			||||||
 | 
					from Tests.helper import skip_unless_feature
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if sys.platform.startswith("win32"):
 | 
					if sys.platform.startswith("win32"):
 | 
				
			||||||
    pytest.skip("Fuzzer is linux only", allow_module_level=True)
 | 
					    pytest.skip("Fuzzer is linux only", allow_module_level=True)
 | 
				
			||||||
| 
						 | 
					@ -48,6 +49,7 @@ def test_fuzz_images(path):
 | 
				
			||||||
        fuzzers.disable_decompressionbomb_error()
 | 
					        fuzzers.disable_decompressionbomb_error()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@skip_unless_feature("freetype2")
 | 
				
			||||||
@pytest.mark.parametrize(
 | 
					@pytest.mark.parametrize(
 | 
				
			||||||
    "path", subprocess.check_output("find Tests/fonts -type f", shell=True).split(b"\n")
 | 
					    "path", subprocess.check_output("find Tests/fonts -type f", shell=True).split(b"\n")
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -22,7 +22,7 @@ def test_imageops_box_blur():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def box_blur(image, radius=1, n=1):
 | 
					def box_blur(image, radius=1, n=1):
 | 
				
			||||||
    return image._new(image.im.box_blur(radius, n))
 | 
					    return image._new(image.im.box_blur((radius, radius), n))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def assert_image(im, data, delta=0):
 | 
					def assert_image(im, data, delta=0):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -16,6 +16,7 @@ TEST_FILE_DX10_BC5_TYPELESS = "Tests/images/bc5_typeless.dds"
 | 
				
			||||||
TEST_FILE_DX10_BC5_UNORM = "Tests/images/bc5_unorm.dds"
 | 
					TEST_FILE_DX10_BC5_UNORM = "Tests/images/bc5_unorm.dds"
 | 
				
			||||||
TEST_FILE_DX10_BC5_SNORM = "Tests/images/bc5_snorm.dds"
 | 
					TEST_FILE_DX10_BC5_SNORM = "Tests/images/bc5_snorm.dds"
 | 
				
			||||||
TEST_FILE_BC5S = "Tests/images/bc5s.dds"
 | 
					TEST_FILE_BC5S = "Tests/images/bc5s.dds"
 | 
				
			||||||
 | 
					TEST_FILE_BC5U = "Tests/images/bc5u.dds"
 | 
				
			||||||
TEST_FILE_BC6H = "Tests/images/bc6h.dds"
 | 
					TEST_FILE_BC6H = "Tests/images/bc6h.dds"
 | 
				
			||||||
TEST_FILE_BC6HS = "Tests/images/bc6h_sf.dds"
 | 
					TEST_FILE_BC6HS = "Tests/images/bc6h_sf.dds"
 | 
				
			||||||
TEST_FILE_DX10_BC7 = "Tests/images/bc7-argb-8bpp_MipMaps-1.dds"
 | 
					TEST_FILE_DX10_BC7 = "Tests/images/bc7-argb-8bpp_MipMaps-1.dds"
 | 
				
			||||||
| 
						 | 
					@ -81,10 +82,18 @@ def test_sanity_ati1():
 | 
				
			||||||
        assert_image_equal_tofile(im, TEST_FILE_ATI1.replace(".dds", ".png"))
 | 
					        assert_image_equal_tofile(im, TEST_FILE_ATI1.replace(".dds", ".png"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_sanity_ati2():
 | 
					@pytest.mark.parametrize(
 | 
				
			||||||
    """Check ATI2 images can be opened"""
 | 
					    "image_path",
 | 
				
			||||||
 | 
					    (
 | 
				
			||||||
 | 
					        TEST_FILE_ATI2,
 | 
				
			||||||
 | 
					        # hexeditted to use BC5U FourCC
 | 
				
			||||||
 | 
					        TEST_FILE_BC5U,
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					def test_sanity_ati2_bc5u(image_path):
 | 
				
			||||||
 | 
					    """Check ATI2 and BC5U images can be opened"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open(TEST_FILE_ATI2) as im:
 | 
					    with Image.open(image_path) as im:
 | 
				
			||||||
        im.load()
 | 
					        im.load()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        assert im.format == "DDS"
 | 
					        assert im.format == "DDS"
 | 
				
			||||||
| 
						 | 
					@ -289,6 +298,11 @@ def test_dxt5_colorblock_alpha_issue_4142():
 | 
				
			||||||
        assert px[2] != 0
 | 
					        assert px[2] != 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_palette():
 | 
				
			||||||
 | 
					    with Image.open("Tests/images/palette.dds") as im:
 | 
				
			||||||
 | 
					        assert_image_equal_tofile(im, "Tests/images/transparent.gif")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_unimplemented_pixel_format():
 | 
					def test_unimplemented_pixel_format():
 | 
				
			||||||
    with pytest.raises(NotImplementedError):
 | 
					    with pytest.raises(NotImplementedError):
 | 
				
			||||||
        with Image.open("Tests/images/unimplemented_pixel_format.dds"):
 | 
					        with Image.open("Tests/images/unimplemented_pixel_format.dds"):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,6 +8,7 @@ from .helper import (
 | 
				
			||||||
    assert_image_similar,
 | 
					    assert_image_similar,
 | 
				
			||||||
    assert_image_similar_tofile,
 | 
					    assert_image_similar_tofile,
 | 
				
			||||||
    hopper,
 | 
					    hopper,
 | 
				
			||||||
 | 
					    is_win32,
 | 
				
			||||||
    mark_if_feature_version,
 | 
					    mark_if_feature_version,
 | 
				
			||||||
    skip_unless_feature,
 | 
					    skip_unless_feature,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
| 
						 | 
					@ -98,6 +99,20 @@ def test_load():
 | 
				
			||||||
        assert im.load()[0, 0] == (255, 255, 255)
 | 
					        assert im.load()[0, 0] == (255, 255, 255)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_binary():
 | 
				
			||||||
 | 
					    if HAS_GHOSTSCRIPT:
 | 
				
			||||||
 | 
					        assert EpsImagePlugin.gs_binary is not None
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        assert EpsImagePlugin.gs_binary is False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if not is_win32():
 | 
				
			||||||
 | 
					        assert EpsImagePlugin.gs_windows_binary is None
 | 
				
			||||||
 | 
					    elif not HAS_GHOSTSCRIPT:
 | 
				
			||||||
 | 
					        assert EpsImagePlugin.gs_windows_binary is False
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        assert EpsImagePlugin.gs_windows_binary is not None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_invalid_file():
 | 
					def test_invalid_file():
 | 
				
			||||||
    invalid_file = "Tests/images/flower.jpg"
 | 
					    invalid_file = "Tests/images/flower.jpg"
 | 
				
			||||||
    with pytest.raises(SyntaxError):
 | 
					    with pytest.raises(SyntaxError):
 | 
				
			||||||
| 
						 | 
					@ -404,3 +419,18 @@ def test_timeout(test_file):
 | 
				
			||||||
        with pytest.raises(Image.UnidentifiedImageError):
 | 
					        with pytest.raises(Image.UnidentifiedImageError):
 | 
				
			||||||
            with Image.open(f):
 | 
					            with Image.open(f):
 | 
				
			||||||
                pass
 | 
					                pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_bounding_box_in_trailer():
 | 
				
			||||||
 | 
					    # Check bounding boxes are parsed in the same way
 | 
				
			||||||
 | 
					    # when specified in the header and the trailer
 | 
				
			||||||
 | 
					    with Image.open("Tests/images/zero_bb_trailer.eps") as trailer_image, Image.open(
 | 
				
			||||||
 | 
					        FILE1
 | 
				
			||||||
 | 
					    ) as header_image:
 | 
				
			||||||
 | 
					        assert trailer_image.size == header_image.size
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_eof_before_bounding_box():
 | 
				
			||||||
 | 
					    with pytest.raises(OSError):
 | 
				
			||||||
 | 
					        with Image.open("Tests/images/zero_bb_eof_before_boundingbox.eps"):
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -205,14 +205,14 @@ def test_optimize_full_l():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_optimize_if_palette_can_be_reduced_by_half():
 | 
					def test_optimize_if_palette_can_be_reduced_by_half():
 | 
				
			||||||
    with Image.open("Tests/images/test.colors.gif") as im:
 | 
					    im = Image.new("P", (8, 1))
 | 
				
			||||||
        # Reduce dimensions because original is too big for _get_optimize()
 | 
					    im.palette = ImagePalette.raw("RGB", bytes((0, 0, 0) * 150))
 | 
				
			||||||
        im = im.resize((591, 443))
 | 
					    for i in range(8):
 | 
				
			||||||
    im_rgb = im.convert("RGB")
 | 
					        im.putpixel((i, 0), (i + 1, 0, 0))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    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.save(out, "GIF", optimize=optimize)
 | 
				
			||||||
        with Image.open(out) as reloaded:
 | 
					        with Image.open(out) as reloaded:
 | 
				
			||||||
            assert len(reloaded.palette.palette) // 3 == colors
 | 
					            assert len(reloaded.palette.palette) // 3 == colors
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -875,6 +875,14 @@ def test_identical_frames_to_single_frame(duration, tmp_path):
 | 
				
			||||||
        assert reread.info["duration"] == 8500
 | 
					        assert reread.info["duration"] == 8500
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_loop_none(tmp_path):
 | 
				
			||||||
 | 
					    out = str(tmp_path / "temp.gif")
 | 
				
			||||||
 | 
					    im = Image.new("L", (100, 100), "#000")
 | 
				
			||||||
 | 
					    im.save(out, loop=None)
 | 
				
			||||||
 | 
					    with Image.open(out) as reread:
 | 
				
			||||||
 | 
					        assert "loop" not in reread.info
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_number_of_loops(tmp_path):
 | 
					def test_number_of_loops(tmp_path):
 | 
				
			||||||
    number_of_loops = 2
 | 
					    number_of_loops = 2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1086,6 +1094,21 @@ def test_transparent_optimize(tmp_path):
 | 
				
			||||||
        assert reloaded.info["transparency"] == reloaded.getpixel((252, 0))
 | 
					        assert reloaded.info["transparency"] == reloaded.getpixel((252, 0))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_removed_transparency(tmp_path):
 | 
				
			||||||
 | 
					    out = str(tmp_path / "temp.gif")
 | 
				
			||||||
 | 
					    im = Image.new("RGB", (256, 1))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for x in range(256):
 | 
				
			||||||
 | 
					        im.putpixel((x, 0), (x, 0, 0))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    im.info["transparency"] = (255, 255, 255)
 | 
				
			||||||
 | 
					    with pytest.warns(UserWarning):
 | 
				
			||||||
 | 
					        im.save(out)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    with Image.open(out) as reloaded:
 | 
				
			||||||
 | 
					        assert "transparency" not in reloaded.info
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_rgb_transparency(tmp_path):
 | 
					def test_rgb_transparency(tmp_path):
 | 
				
			||||||
    out = str(tmp_path / "temp.gif")
 | 
					    out = str(tmp_path / "temp.gif")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1157,18 +1180,17 @@ def test_palette_save_L(tmp_path):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_palette_save_P(tmp_path):
 | 
					def test_palette_save_P(tmp_path):
 | 
				
			||||||
    # Pass in a different palette, then construct what the image would look like.
 | 
					    im = Image.new("P", (1, 2))
 | 
				
			||||||
    # Forcing a non-straight grayscale palette.
 | 
					    im.putpixel((0, 1), 1)
 | 
				
			||||||
 | 
					 | 
				
			||||||
    im = hopper("P")
 | 
					 | 
				
			||||||
    palette = bytes(255 - i // 3 for i in range(768))
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    out = str(tmp_path / "temp.gif")
 | 
					    out = str(tmp_path / "temp.gif")
 | 
				
			||||||
    im.save(out, palette=palette)
 | 
					    im.save(out, palette=bytes((1, 2, 3, 4, 5, 6)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Image.open(out) as reloaded:
 | 
					    with Image.open(out) as reloaded:
 | 
				
			||||||
        im.putpalette(palette)
 | 
					        reloaded_rgb = reloaded.convert("RGB")
 | 
				
			||||||
        assert_image_equal(reloaded, im)
 | 
					
 | 
				
			||||||
 | 
					        assert reloaded_rgb.getpixel((0, 0)) == (1, 2, 3)
 | 
				
			||||||
 | 
					        assert reloaded_rgb.getpixel((0, 1)) == (4, 5, 6)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_palette_save_duplicate_entries(tmp_path):
 | 
					def test_palette_save_duplicate_entries(tmp_path):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
import sys
 | 
					import sys
 | 
				
			||||||
from io import StringIO
 | 
					from io import BytesIO, StringIO
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from PIL import Image, IptcImagePlugin
 | 
					from PIL import Image, IptcImagePlugin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -30,6 +30,36 @@ def test_getiptcinfo_jpg_found():
 | 
				
			||||||
    assert iptc[(2, 101)] == b"Hungary"
 | 
					    assert iptc[(2, 101)] == b"Hungary"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_getiptcinfo_fotostation():
 | 
				
			||||||
 | 
					    # Arrange
 | 
				
			||||||
 | 
					    with open(TEST_FILE, "rb") as fp:
 | 
				
			||||||
 | 
					        data = bytearray(fp.read())
 | 
				
			||||||
 | 
					    data[86] = 240
 | 
				
			||||||
 | 
					    f = BytesIO(data)
 | 
				
			||||||
 | 
					    with Image.open(f) as im:
 | 
				
			||||||
 | 
					        # Act
 | 
				
			||||||
 | 
					        iptc = IptcImagePlugin.getiptcinfo(im)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Assert
 | 
				
			||||||
 | 
					    for tag in iptc.keys():
 | 
				
			||||||
 | 
					        if tag[0] == 240:
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					    assert False, "FotoStation tag not found"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_getiptcinfo_zero_padding():
 | 
				
			||||||
 | 
					    # Arrange
 | 
				
			||||||
 | 
					    with Image.open(TEST_FILE) as im:
 | 
				
			||||||
 | 
					        im.info["photoshop"][0x0404] += b"\x00\x00\x00"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Act
 | 
				
			||||||
 | 
					        iptc = IptcImagePlugin.getiptcinfo(im)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Assert
 | 
				
			||||||
 | 
					    assert isinstance(iptc, dict)
 | 
				
			||||||
 | 
					    assert len(iptc) == 3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
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:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -214,13 +214,20 @@ class TestFileJpeg:
 | 
				
			||||||
            # Should not raise OSError for image with icc larger than image size.
 | 
					            # Should not raise OSError for image with icc larger than image size.
 | 
				
			||||||
            im.save(
 | 
					            im.save(
 | 
				
			||||||
                f,
 | 
					                f,
 | 
				
			||||||
                format="JPEG",
 | 
					 | 
				
			||||||
                progressive=True,
 | 
					                progressive=True,
 | 
				
			||||||
                quality=95,
 | 
					                quality=95,
 | 
				
			||||||
                icc_profile=icc_profile,
 | 
					                icc_profile=icc_profile,
 | 
				
			||||||
                optimize=True,
 | 
					                optimize=True,
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        with Image.open("Tests/images/flower2.jpg") as im:
 | 
				
			||||||
 | 
					            f = str(tmp_path / "temp2.jpg")
 | 
				
			||||||
 | 
					            im.save(f, progressive=True, quality=94, icc_profile=b" " * 53955)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        with Image.open("Tests/images/flower2.jpg") as im:
 | 
				
			||||||
 | 
					            f = str(tmp_path / "temp3.jpg")
 | 
				
			||||||
 | 
					            im.save(f, progressive=True, quality=94, exif=b" " * 43668)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_optimize(self):
 | 
					    def test_optimize(self):
 | 
				
			||||||
        im1 = self.roundtrip(hopper())
 | 
					        im1 = self.roundtrip(hopper())
 | 
				
			||||||
        im2 = self.roundtrip(hopper(), optimize=0)
 | 
					        im2 = self.roundtrip(hopper(), optimize=0)
 | 
				
			||||||
| 
						 | 
					@ -945,11 +952,10 @@ class TestFileJpeg:
 | 
				
			||||||
            assert repr_jpeg.format == "JPEG"
 | 
					            assert repr_jpeg.format == "JPEG"
 | 
				
			||||||
            assert_image_similar(im, repr_jpeg, 17)
 | 
					            assert_image_similar(im, repr_jpeg, 17)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_repr_jpeg_error(self):
 | 
					    def test_repr_jpeg_error_returns_none(self):
 | 
				
			||||||
        im = hopper("F")
 | 
					        im = hopper("F")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        with pytest.raises(ValueError):
 | 
					        assert im._repr_jpeg_() is None
 | 
				
			||||||
            im._repr_jpeg_()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@pytest.mark.skipif(not is_win32(), reason="Windows only")
 | 
					@pytest.mark.skipif(not is_win32(), reason="Windows only")
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -274,17 +274,15 @@ def test_sgnd(tmp_path):
 | 
				
			||||||
        assert reloaded_signed.getpixel((0, 0)) == 128
 | 
					        assert reloaded_signed.getpixel((0, 0)) == 128
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_rgba():
 | 
					@pytest.mark.parametrize("ext", (".j2k", ".jp2"))
 | 
				
			||||||
 | 
					def test_rgba(ext):
 | 
				
			||||||
    # Arrange
 | 
					    # Arrange
 | 
				
			||||||
    with Image.open("Tests/images/rgb_trns_ycbc.j2k") as j2k:
 | 
					    with Image.open("Tests/images/rgb_trns_ycbc" + ext) as im:
 | 
				
			||||||
        with Image.open("Tests/images/rgb_trns_ycbc.jp2") as jp2:
 | 
					        # Act
 | 
				
			||||||
            # Act
 | 
					        im.load()
 | 
				
			||||||
            j2k.load()
 | 
					 | 
				
			||||||
            jp2.load()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # Assert
 | 
					        # Assert
 | 
				
			||||||
            assert j2k.mode == "RGBA"
 | 
					        assert im.mode == "RGBA"
 | 
				
			||||||
            assert jp2.mode == "RGBA"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@pytest.mark.parametrize("ext", (".j2k", ".jp2"))
 | 
					@pytest.mark.parametrize("ext", (".j2k", ".jp2"))
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,7 +8,7 @@ from collections import namedtuple
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import pytest
 | 
					import pytest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from PIL import Image, ImageFilter, TiffImagePlugin, TiffTags, features
 | 
					from PIL import Image, ImageFilter, ImageOps, TiffImagePlugin, TiffTags, features
 | 
				
			||||||
from PIL.TiffImagePlugin import SAMPLEFORMAT, STRIPOFFSETS, SUBIFD
 | 
					from PIL.TiffImagePlugin import SAMPLEFORMAT, STRIPOFFSETS, SUBIFD
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .helper import (
 | 
					from .helper import (
 | 
				
			||||||
| 
						 | 
					@ -1035,7 +1035,18 @@ class TestFileLibTiff(LibTiffTestCase):
 | 
				
			||||||
        with Image.open("Tests/images/g4_orientation_1.tif") as base_im:
 | 
					        with Image.open("Tests/images/g4_orientation_1.tif") as base_im:
 | 
				
			||||||
            for i in range(2, 9):
 | 
					            for i in range(2, 9):
 | 
				
			||||||
                with Image.open("Tests/images/g4_orientation_" + str(i) + ".tif") as im:
 | 
					                with Image.open("Tests/images/g4_orientation_" + str(i) + ".tif") as im:
 | 
				
			||||||
 | 
					                    assert 274 in im.tag_v2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    im.load()
 | 
					                    im.load()
 | 
				
			||||||
 | 
					                    assert 274 not in im.tag_v2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    assert_image_similar(base_im, im, 0.7)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_exif_transpose(self):
 | 
				
			||||||
 | 
					        with Image.open("Tests/images/g4_orientation_1.tif") as base_im:
 | 
				
			||||||
 | 
					            for i in range(2, 9):
 | 
				
			||||||
 | 
					                with Image.open("Tests/images/g4_orientation_" + str(i) + ".tif") as im:
 | 
				
			||||||
 | 
					                    im = ImageOps.exif_transpose(im)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    assert_image_similar(base_im, im, 0.7)
 | 
					                    assert_image_similar(base_im, im, 0.7)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -43,8 +43,25 @@ def test_save(tmp_path, mode):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@skip_unless_feature("jpg_2000")
 | 
					@skip_unless_feature("jpg_2000")
 | 
				
			||||||
def test_save_rgba(tmp_path):
 | 
					@pytest.mark.parametrize("mode", ("LA", "RGBA"))
 | 
				
			||||||
    helper_save_as_pdf(tmp_path, "RGBA")
 | 
					def test_save_alpha(tmp_path, mode):
 | 
				
			||||||
 | 
					    helper_save_as_pdf(tmp_path, mode)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_p_alpha(tmp_path):
 | 
				
			||||||
 | 
					    # Arrange
 | 
				
			||||||
 | 
					    outfile = str(tmp_path / "temp.pdf")
 | 
				
			||||||
 | 
					    with Image.open("Tests/images/pil123p.png") as im:
 | 
				
			||||||
 | 
					        assert im.mode == "P"
 | 
				
			||||||
 | 
					        assert isinstance(im.info["transparency"], bytes)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Act
 | 
				
			||||||
 | 
					        im.save(outfile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Assert
 | 
				
			||||||
 | 
					    with open(outfile, "rb") as fp:
 | 
				
			||||||
 | 
					        contents = fp.read()
 | 
				
			||||||
 | 
					    assert b"\n/SMask " in contents
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_monochrome(tmp_path):
 | 
					def test_monochrome(tmp_path):
 | 
				
			||||||
| 
						 | 
					@ -57,8 +74,8 @@ def test_monochrome(tmp_path):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_unsupported_mode(tmp_path):
 | 
					def test_unsupported_mode(tmp_path):
 | 
				
			||||||
    im = hopper("LA")
 | 
					    im = hopper("PA")
 | 
				
			||||||
    outfile = str(tmp_path / "temp_LA.pdf")
 | 
					    outfile = str(tmp_path / "temp_PA.pdf")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with pytest.raises(ValueError):
 | 
					    with pytest.raises(ValueError):
 | 
				
			||||||
        im.save(outfile)
 | 
					        im.save(outfile)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -79,7 +79,7 @@ class TestFilePng:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    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+){1,3}$", features.version_codec("zlib"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        test_file = str(tmp_path / "temp.png")
 | 
					        test_file = str(tmp_path / "temp.png")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -92,11 +92,11 @@ class TestFilePng:
 | 
				
			||||||
            assert im.format == "PNG"
 | 
					            assert im.format == "PNG"
 | 
				
			||||||
            assert im.get_format_mimetype() == "image/png"
 | 
					            assert im.get_format_mimetype() == "image/png"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for mode in ["1", "L", "P", "RGB", "I", "I;16"]:
 | 
					        for mode in ["1", "L", "P", "RGB", "I", "I;16", "I;16B"]:
 | 
				
			||||||
            im = hopper(mode)
 | 
					            im = hopper(mode)
 | 
				
			||||||
            im.save(test_file)
 | 
					            im.save(test_file)
 | 
				
			||||||
            with Image.open(test_file) as reloaded:
 | 
					            with Image.open(test_file) as reloaded:
 | 
				
			||||||
                if mode == "I;16":
 | 
					                if mode in ("I;16", "I;16B"):
 | 
				
			||||||
                    reloaded = reloaded.convert(mode)
 | 
					                    reloaded = reloaded.convert(mode)
 | 
				
			||||||
                assert_image_equal(reloaded, im)
 | 
					                assert_image_equal(reloaded, im)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -532,11 +532,10 @@ class TestFilePng:
 | 
				
			||||||
            assert repr_png.format == "PNG"
 | 
					            assert repr_png.format == "PNG"
 | 
				
			||||||
            assert_image_equal(im, repr_png)
 | 
					            assert_image_equal(im, repr_png)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_repr_png_error(self):
 | 
					    def test_repr_png_error_returns_none(self):
 | 
				
			||||||
        im = hopper("F")
 | 
					        im = hopper("F")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        with pytest.raises(ValueError):
 | 
					        assert im._repr_png_() is None
 | 
				
			||||||
            im._repr_png_()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_chunk_order(self, tmp_path):
 | 
					    def test_chunk_order(self, tmp_path):
 | 
				
			||||||
        with Image.open("Tests/images/icc_profile.png") as im:
 | 
					        with Image.open("Tests/images/icc_profile.png") as im:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,7 +2,7 @@ import pytest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from PIL import Image, QoiImagePlugin
 | 
					from PIL import Image, QoiImagePlugin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .helper import assert_image_equal_tofile, assert_image_similar_tofile
 | 
					from .helper import assert_image_equal_tofile
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_sanity():
 | 
					def test_sanity():
 | 
				
			||||||
| 
						 | 
					@ -18,7 +18,7 @@ def test_sanity():
 | 
				
			||||||
        assert im.size == (162, 150)
 | 
					        assert im.size == (162, 150)
 | 
				
			||||||
        assert im.format == "QOI"
 | 
					        assert im.format == "QOI"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        assert_image_similar_tofile(im, "Tests/images/pil123rgba.png", 0.03)
 | 
					        assert_image_equal_tofile(im, "Tests/images/pil123rgba.png")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_invalid_file():
 | 
					def test_invalid_file():
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -235,3 +235,13 @@ class TestFileWebp:
 | 
				
			||||||
        with Image.open(out_webp) as reloaded:
 | 
					        with Image.open(out_webp) as reloaded:
 | 
				
			||||||
            reloaded.load()
 | 
					            reloaded.load()
 | 
				
			||||||
            assert reloaded.info["duration"] == 1000
 | 
					            assert reloaded.info["duration"] == 1000
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_roundtrip_rgba_palette(self, tmp_path):
 | 
				
			||||||
 | 
					        temp_file = str(tmp_path / "temp.webp")
 | 
				
			||||||
 | 
					        im = Image.new("RGBA", (1, 1)).convert("P")
 | 
				
			||||||
 | 
					        assert im.mode == "P"
 | 
				
			||||||
 | 
					        assert im.palette.mode == "RGBA"
 | 
				
			||||||
 | 
					        im.save(temp_file)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        with Image.open(temp_file) as im:
 | 
				
			||||||
 | 
					            assert im.getpixel((0, 0)) == (0, 0, 0, 0)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -135,6 +135,12 @@ class TestImage:
 | 
				
			||||||
        with pytest.raises(AttributeError):
 | 
					        with pytest.raises(AttributeError):
 | 
				
			||||||
            im.size = (3, 4)
 | 
					            im.size = (3, 4)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_set_mode(self):
 | 
				
			||||||
 | 
					        im = Image.new("RGB", (1, 1))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        with pytest.raises(AttributeError):
 | 
				
			||||||
 | 
					            im.mode = "P"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_invalid_image(self):
 | 
					    def test_invalid_image(self):
 | 
				
			||||||
        im = io.BytesIO(b"")
 | 
					        im = io.BytesIO(b"")
 | 
				
			||||||
        with pytest.raises(UnidentifiedImageError):
 | 
					        with pytest.raises(UnidentifiedImageError):
 | 
				
			||||||
| 
						 | 
					@ -632,8 +638,8 @@ class TestImage:
 | 
				
			||||||
                im.remap_palette(None)
 | 
					                im.remap_palette(None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_remap_palette_transparency(self):
 | 
					    def test_remap_palette_transparency(self):
 | 
				
			||||||
        im = Image.new("P", (1, 2))
 | 
					        im = Image.new("P", (1, 2), (0, 0, 0))
 | 
				
			||||||
        im.putpixel((0, 1), 1)
 | 
					        im.putpixel((0, 1), (255, 0, 0))
 | 
				
			||||||
        im.info["transparency"] = 0
 | 
					        im.info["transparency"] = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        im_remapped = im.remap_palette([1, 0])
 | 
					        im_remapped = im.remap_palette([1, 0])
 | 
				
			||||||
| 
						 | 
					@ -655,15 +661,15 @@ class TestImage:
 | 
				
			||||||
        blank_p.palette = None
 | 
					        blank_p.palette = None
 | 
				
			||||||
        blank_pa.palette = None
 | 
					        blank_pa.palette = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        def _make_new(base_image, im, palette_result=None):
 | 
					        def _make_new(base_image, image, palette_result=None):
 | 
				
			||||||
            new_im = base_image._new(im)
 | 
					            new_image = base_image._new(image.im)
 | 
				
			||||||
            assert new_im.mode == im.mode
 | 
					            assert new_image.mode == image.mode
 | 
				
			||||||
            assert new_im.size == im.size
 | 
					            assert new_image.size == image.size
 | 
				
			||||||
            assert new_im.info == base_image.info
 | 
					            assert new_image.info == base_image.info
 | 
				
			||||||
            if palette_result is not None:
 | 
					            if palette_result is not None:
 | 
				
			||||||
                assert new_im.palette.tobytes() == palette_result.tobytes()
 | 
					                assert new_image.palette.tobytes() == palette_result.tobytes()
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                assert new_im.palette is None
 | 
					                assert new_image.palette is None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        _make_new(im, im_p, ImagePalette.ImagePalette(list(range(256)) * 3))
 | 
					        _make_new(im, im_p, ImagePalette.ImagePalette(list(range(256)) * 3))
 | 
				
			||||||
        _make_new(im_p, im, None)
 | 
					        _make_new(im_p, im, None)
 | 
				
			||||||
| 
						 | 
					@ -900,6 +906,31 @@ class TestImage:
 | 
				
			||||||
        im = Image.new("RGB", size)
 | 
					        im = Image.new("RGB", size)
 | 
				
			||||||
        assert im.tobytes() == b""
 | 
					        assert im.tobytes() == b""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_has_transparency_data(self):
 | 
				
			||||||
 | 
					        for mode in ("1", "L", "P", "RGB"):
 | 
				
			||||||
 | 
					            im = Image.new(mode, (1, 1))
 | 
				
			||||||
 | 
					            assert not im.has_transparency_data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for mode in ("LA", "La", "PA", "RGBA", "RGBa"):
 | 
				
			||||||
 | 
					            im = Image.new(mode, (1, 1))
 | 
				
			||||||
 | 
					            assert im.has_transparency_data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # P mode with "transparency" info
 | 
				
			||||||
 | 
					        with Image.open("Tests/images/first_frame_transparency.gif") as im:
 | 
				
			||||||
 | 
					            assert "transparency" in im.info
 | 
				
			||||||
 | 
					            assert im.has_transparency_data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # RGB mode with "transparency" info
 | 
				
			||||||
 | 
					        with Image.open("Tests/images/rgb_trns.png") as im:
 | 
				
			||||||
 | 
					            assert "transparency" in im.info
 | 
				
			||||||
 | 
					            assert im.has_transparency_data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # P mode with RGBA palette
 | 
				
			||||||
 | 
					        im = Image.new("RGBA", (1, 1)).convert("P")
 | 
				
			||||||
 | 
					        assert im.mode == "P"
 | 
				
			||||||
 | 
					        assert im.palette.mode == "RGBA"
 | 
				
			||||||
 | 
					        assert im.has_transparency_data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_apply_transparency(self):
 | 
					    def test_apply_transparency(self):
 | 
				
			||||||
        im = Image.new("P", (1, 1))
 | 
					        im = Image.new("P", (1, 1))
 | 
				
			||||||
        im.putpalette((0, 0, 0, 1, 1, 1))
 | 
					        im.putpalette((0, 0, 0, 1, 1, 1))
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -213,6 +213,10 @@ class TestImageGetPixel(AccessTest):
 | 
				
			||||||
    def test_basic(self, mode):
 | 
					    def test_basic(self, mode):
 | 
				
			||||||
        self.check(mode)
 | 
					        self.check(mode)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_list(self):
 | 
				
			||||||
 | 
					        im = hopper()
 | 
				
			||||||
 | 
					        assert im.getpixel([0, 0]) == (20, 20, 70)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @pytest.mark.parametrize("mode", ("I;16", "I;16B"))
 | 
					    @pytest.mark.parametrize("mode", ("I;16", "I;16B"))
 | 
				
			||||||
    @pytest.mark.parametrize(
 | 
					    @pytest.mark.parametrize(
 | 
				
			||||||
        "expected_color", (2**15 - 1, 2**15, 2**15 + 1, 2**16 - 1)
 | 
					        "expected_color", (2**15 - 1, 2**15, 2**15 + 1, 2**16 - 1)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -117,11 +117,11 @@ def test_trns_p(tmp_path):
 | 
				
			||||||
    f = str(tmp_path / "temp.png")
 | 
					    f = str(tmp_path / "temp.png")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    im_l = im.convert("L")
 | 
					    im_l = im.convert("L")
 | 
				
			||||||
    assert im_l.info["transparency"] == 1  # undone
 | 
					    assert im_l.info["transparency"] == 0
 | 
				
			||||||
    im_l.save(f)
 | 
					    im_l.save(f)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    im_rgb = im.convert("RGB")
 | 
					    im_rgb = im.convert("RGB")
 | 
				
			||||||
    assert im_rgb.info["transparency"] == (0, 1, 2)  # undone
 | 
					    assert im_rgb.info["transparency"] == (0, 0, 0)
 | 
				
			||||||
    im_rgb.save(f)
 | 
					    im_rgb.save(f)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -23,9 +23,12 @@ from .helper import assert_image_equal, hopper
 | 
				
			||||||
        ImageFilter.MinFilter,
 | 
					        ImageFilter.MinFilter,
 | 
				
			||||||
        ImageFilter.ModeFilter,
 | 
					        ImageFilter.ModeFilter,
 | 
				
			||||||
        ImageFilter.GaussianBlur,
 | 
					        ImageFilter.GaussianBlur,
 | 
				
			||||||
 | 
					        ImageFilter.GaussianBlur(0),
 | 
				
			||||||
        ImageFilter.GaussianBlur(5),
 | 
					        ImageFilter.GaussianBlur(5),
 | 
				
			||||||
 | 
					        ImageFilter.GaussianBlur((2, 5)),
 | 
				
			||||||
        ImageFilter.BoxBlur(0),
 | 
					        ImageFilter.BoxBlur(0),
 | 
				
			||||||
        ImageFilter.BoxBlur(5),
 | 
					        ImageFilter.BoxBlur(5),
 | 
				
			||||||
 | 
					        ImageFilter.BoxBlur((2, 5)),
 | 
				
			||||||
        ImageFilter.UnsharpMask,
 | 
					        ImageFilter.UnsharpMask,
 | 
				
			||||||
        ImageFilter.UnsharpMask(10),
 | 
					        ImageFilter.UnsharpMask(10),
 | 
				
			||||||
    ),
 | 
					    ),
 | 
				
			||||||
| 
						 | 
					@ -185,12 +188,21 @@ def test_consistency_5x5(mode):
 | 
				
			||||||
            assert_image_equal(source.filter(kernel), reference)
 | 
					            assert_image_equal(source.filter(kernel), reference)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_invalid_box_blur_filter():
 | 
					@pytest.mark.parametrize(
 | 
				
			||||||
 | 
					    "radius",
 | 
				
			||||||
 | 
					    (
 | 
				
			||||||
 | 
					        -2,
 | 
				
			||||||
 | 
					        (-2, -2),
 | 
				
			||||||
 | 
					        (-2, 2),
 | 
				
			||||||
 | 
					        (2, -2),
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					def test_invalid_box_blur_filter(radius):
 | 
				
			||||||
    with pytest.raises(ValueError):
 | 
					    with pytest.raises(ValueError):
 | 
				
			||||||
        ImageFilter.BoxBlur(-2)
 | 
					        ImageFilter.BoxBlur(radius)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    im = hopper()
 | 
					    im = hopper()
 | 
				
			||||||
    box_blur_filter = ImageFilter.BoxBlur(2)
 | 
					    box_blur_filter = ImageFilter.BoxBlur(2)
 | 
				
			||||||
    box_blur_filter.radius = -2
 | 
					    box_blur_filter.radius = radius
 | 
				
			||||||
    with pytest.raises(ValueError):
 | 
					    with pytest.raises(ValueError):
 | 
				
			||||||
        im.filter(box_blur_filter)
 | 
					        im.filter(box_blur_filter)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -84,3 +84,14 @@ def test_rgba_palette(mode, palette):
 | 
				
			||||||
    im.putpalette(palette, mode)
 | 
					    im.putpalette(palette, mode)
 | 
				
			||||||
    assert im.getpalette() == [1, 2, 3]
 | 
					    assert im.getpalette() == [1, 2, 3]
 | 
				
			||||||
    assert im.palette.colors == {(1, 2, 3, 4): 0}
 | 
					    assert im.palette.colors == {(1, 2, 3, 4): 0}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_empty_palette():
 | 
				
			||||||
 | 
					    im = Image.new("P", (1, 1))
 | 
				
			||||||
 | 
					    assert im.getpalette() == []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_undefined_palette_index():
 | 
				
			||||||
 | 
					    im = Image.new("P", (1, 1), 3)
 | 
				
			||||||
 | 
					    im.putpalette((1, 2, 3))
 | 
				
			||||||
 | 
					    assert im.convert("RGB").getpixel((0, 0)) == (0, 0, 0)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -586,6 +586,18 @@ def test_point(points):
 | 
				
			||||||
    assert_image_equal_tofile(im, "Tests/images/imagedraw_point.png")
 | 
					    assert_image_equal_tofile(im, "Tests/images/imagedraw_point.png")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_point_I16():
 | 
				
			||||||
 | 
					    # Arrange
 | 
				
			||||||
 | 
					    im = Image.new("I;16", (1, 1))
 | 
				
			||||||
 | 
					    draw = ImageDraw.Draw(im)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Act
 | 
				
			||||||
 | 
					    draw.point((0, 0), fill=0x1234)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Assert
 | 
				
			||||||
 | 
					    assert im.getpixel((0, 0)) == 0x1234
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@pytest.mark.parametrize("points", POINTS)
 | 
					@pytest.mark.parametrize("points", POINTS)
 | 
				
			||||||
def test_polygon(points):
 | 
					def test_polygon(points):
 | 
				
			||||||
    # Arrange
 | 
					    # Arrange
 | 
				
			||||||
| 
						 | 
					@ -732,7 +744,7 @@ def test_rectangle_I16(bbox):
 | 
				
			||||||
    draw = ImageDraw.Draw(im)
 | 
					    draw = ImageDraw.Draw(im)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Act
 | 
					    # Act
 | 
				
			||||||
    draw.rectangle(bbox, fill="black", outline="green")
 | 
					    draw.rectangle(bbox, outline=0xFFFF)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Assert
 | 
					    # Assert
 | 
				
			||||||
    assert_image_equal_tofile(im.convert("I"), "Tests/images/imagedraw_rectangle_I.png")
 | 
					    assert_image_equal_tofile(im.convert("I"), "Tests/images/imagedraw_rectangle_I.png")
 | 
				
			||||||
| 
						 | 
					@ -1326,6 +1338,7 @@ def test_stroke_multiline():
 | 
				
			||||||
    assert_image_similar_tofile(im, "Tests/images/imagedraw_stroke_multiline.png", 3.3)
 | 
					    assert_image_similar_tofile(im, "Tests/images/imagedraw_stroke_multiline.png", 3.3)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@skip_unless_feature("freetype2")
 | 
				
			||||||
def test_setting_default_font():
 | 
					def test_setting_default_font():
 | 
				
			||||||
    # Arrange
 | 
					    # Arrange
 | 
				
			||||||
    im = Image.new("RGB", (100, 250))
 | 
					    im = Image.new("RGB", (100, 250))
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -136,7 +136,7 @@ class TestImageFile:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        class DummyImageFile(ImageFile.ImageFile):
 | 
					        class DummyImageFile(ImageFile.ImageFile):
 | 
				
			||||||
            def _open(self):
 | 
					            def _open(self):
 | 
				
			||||||
                self.mode = "RGB"
 | 
					                self._mode = "RGB"
 | 
				
			||||||
                self._size = (1, 1)
 | 
					                self._size = (1, 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        im = DummyImageFile(buf)
 | 
					        im = DummyImageFile(buf)
 | 
				
			||||||
| 
						 | 
					@ -217,7 +217,7 @@ xoff, yoff, xsize, ysize = 10, 20, 100, 100
 | 
				
			||||||
class MockImageFile(ImageFile.ImageFile):
 | 
					class MockImageFile(ImageFile.ImageFile):
 | 
				
			||||||
    def _open(self):
 | 
					    def _open(self):
 | 
				
			||||||
        self.rawmode = "RGBA"
 | 
					        self.rawmode = "RGBA"
 | 
				
			||||||
        self.mode = "RGBA"
 | 
					        self._mode = "RGBA"
 | 
				
			||||||
        self._size = (200, 200)
 | 
					        self._size = (200, 200)
 | 
				
			||||||
        self.tile = [("MOCK", (xoff, yoff, xoff + xsize, yoff + ysize), 32, None)]
 | 
					        self.tile = [("MOCK", (xoff, yoff, xoff + xsize, yoff + ysize), 32, None)]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -141,7 +141,9 @@ def test_I16(font):
 | 
				
			||||||
    draw = ImageDraw.Draw(im)
 | 
					    draw = ImageDraw.Draw(im)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    txt = "Hello World!"
 | 
					    txt = "Hello World!"
 | 
				
			||||||
    draw.text((10, 10), txt, font=font)
 | 
					    draw.text((10, 10), txt, fill=0xFFFE, font=font)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    assert im.getpixel((12, 14)) == 0xFFFE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    target = "Tests/images/transparent_background_text_L.png"
 | 
					    target = "Tests/images/transparent_background_text_L.png"
 | 
				
			||||||
    assert_image_similar_tofile(im.convert("L"), target, 0.01)
 | 
					    assert_image_similar_tofile(im.convert("L"), target, 0.01)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -75,13 +75,13 @@ def test_pickle_la_mode_with_palette(tmp_path):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Act / Assert
 | 
					    # Act / Assert
 | 
				
			||||||
    for protocol in range(0, pickle.HIGHEST_PROTOCOL + 1):
 | 
					    for protocol in range(0, pickle.HIGHEST_PROTOCOL + 1):
 | 
				
			||||||
        im.mode = "LA"
 | 
					        im._mode = "LA"
 | 
				
			||||||
        with open(filename, "wb") as f:
 | 
					        with open(filename, "wb") as f:
 | 
				
			||||||
            pickle.dump(im, f, protocol)
 | 
					            pickle.dump(im, f, protocol)
 | 
				
			||||||
        with open(filename, "rb") as f:
 | 
					        with open(filename, "rb") as f:
 | 
				
			||||||
            loaded_im = pickle.load(f)
 | 
					            loaded_im = pickle.load(f)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        im.mode = "PA"
 | 
					        im._mode = "PA"
 | 
				
			||||||
        assert im == loaded_im
 | 
					        assert im == loaded_im
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -112,6 +112,7 @@ def helper_assert_pickled_font_images(font1, font2):
 | 
				
			||||||
    assert_image_equal(im1, im2)
 | 
					    assert_image_equal(im1, im2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@skip_unless_feature("freetype2")
 | 
				
			||||||
@pytest.mark.parametrize("protocol", list(range(0, pickle.HIGHEST_PROTOCOL + 1)))
 | 
					@pytest.mark.parametrize("protocol", list(range(0, pickle.HIGHEST_PROTOCOL + 1)))
 | 
				
			||||||
def test_pickle_font_string(protocol):
 | 
					def test_pickle_font_string(protocol):
 | 
				
			||||||
    # Arrange
 | 
					    # Arrange
 | 
				
			||||||
| 
						 | 
					@ -125,6 +126,7 @@ def test_pickle_font_string(protocol):
 | 
				
			||||||
    helper_assert_pickled_font_images(font, unpickled_font)
 | 
					    helper_assert_pickled_font_images(font, unpickled_font)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@skip_unless_feature("freetype2")
 | 
				
			||||||
@pytest.mark.parametrize("protocol", list(range(0, pickle.HIGHEST_PROTOCOL + 1)))
 | 
					@pytest.mark.parametrize("protocol", list(range(0, pickle.HIGHEST_PROTOCOL + 1)))
 | 
				
			||||||
def test_pickle_font_file(tmp_path, protocol):
 | 
					def test_pickle_font_file(tmp_path, protocol):
 | 
				
			||||||
    # Arrange
 | 
					    # Arrange
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										0
									
								
								_custom_build/backend.py
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						
									
										0
									
								
								_custom_build/backend.py
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
#!/bin/bash
 | 
					#!/bin/bash
 | 
				
			||||||
# install libimagequant
 | 
					# install libimagequant
 | 
				
			||||||
 | 
					
 | 
				
			||||||
archive=libimagequant-4.2.0
 | 
					archive=libimagequant-4.2.1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
./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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,4 +11,3 @@ pushd $archive
 | 
				
			||||||
meson build --prefix=/usr && sudo ninja -C build install
 | 
					meson build --prefix=/usr && sudo ninja -C build install
 | 
				
			||||||
 | 
					
 | 
				
			||||||
popd
 | 
					popd
 | 
				
			||||||
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,4 +15,3 @@ make && sudo make install
 | 
				
			||||||
cd ..
 | 
					cd ..
 | 
				
			||||||
 | 
					
 | 
				
			||||||
popd
 | 
					popd
 | 
				
			||||||
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
#!/bin/bash
 | 
					#!/bin/bash
 | 
				
			||||||
# install webp
 | 
					# install webp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
archive=libwebp-1.3.1
 | 
					archive=libwebp-1.3.2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
./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,4 +2,3 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pkg install -y python ndk-sysroot clang make \
 | 
					pkg install -y python ndk-sysroot clang make \
 | 
				
			||||||
    libjpeg-turbo
 | 
					    libjpeg-turbo
 | 
				
			||||||
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,7 +12,7 @@ The fork author's goal is to foster and support active development of PIL throug
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.. _GitHub Actions: https://github.com/python-pillow/Pillow/actions
 | 
					.. _GitHub Actions: https://github.com/python-pillow/Pillow/actions
 | 
				
			||||||
.. _AppVeyor: https://ci.appveyor.com/project/Python-pillow/pillow
 | 
					.. _AppVeyor: https://ci.appveyor.com/project/Python-pillow/pillow
 | 
				
			||||||
.. _Travis CI: https://app.travis-ci.com/github/python-pillow/pillow-wheels
 | 
					.. _Travis CI: https://app.travis-ci.com/github/python-pillow/Pillow
 | 
				
			||||||
.. _GitHub: https://github.com/python-pillow/Pillow
 | 
					.. _GitHub: https://github.com/python-pillow/Pillow
 | 
				
			||||||
.. _Python Package Index: https://pypi.org/project/Pillow/
 | 
					.. _Python Package Index: https://pypi.org/project/Pillow/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -225,7 +225,7 @@ class DdsImageFile(ImageFile.ImageFile):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        flags, height, width = struct.unpack("<3I", header.read(12))
 | 
					        flags, height, width = struct.unpack("<3I", header.read(12))
 | 
				
			||||||
        self._size = (width, height)
 | 
					        self._size = (width, height)
 | 
				
			||||||
        self.mode = "RGBA"
 | 
					        self._mode = "RGBA"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        pitch, depth, mipmaps = struct.unpack("<3I", header.read(12))
 | 
					        pitch, depth, mipmaps = struct.unpack("<3I", header.read(12))
 | 
				
			||||||
        struct.unpack("<11I", header.read(44))  # reserved
 | 
					        struct.unpack("<11I", header.read(44))  # reserved
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -63,8 +63,35 @@ DDS
 | 
				
			||||||
^^^
 | 
					^^^
 | 
				
			||||||
 | 
					
 | 
				
			||||||
DDS is a popular container texture format used in video games and natively supported
 | 
					DDS is a popular container texture format used in video games and natively supported
 | 
				
			||||||
by DirectX. Uncompressed RGB and RGBA can be read, and (since 8.3.0) written. DXT1,
 | 
					by DirectX.
 | 
				
			||||||
DXT3 (since 3.4.0) and DXT5 pixel formats can be read, only in ``RGBA`` mode.
 | 
					
 | 
				
			||||||
 | 
					DXT1 and DXT5 pixel formats can be read, only in ``RGBA`` mode.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. versionadded:: 3.4.0
 | 
				
			||||||
 | 
					   DXT3 images can be read in ``RGB`` mode and DX10 images can be read in
 | 
				
			||||||
 | 
					   ``RGB`` and ``RGBA`` mode.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. versionadded:: 6.0.0
 | 
				
			||||||
 | 
					   Uncompressed ``RGBA`` images can be read.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. versionadded:: 8.3.0
 | 
				
			||||||
 | 
					   BC5S images can be opened in ``RGB`` mode, and uncompressed ``RGB`` images
 | 
				
			||||||
 | 
					   can be read. Uncompressed data can also be saved to image files.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. versionadded:: 9.3.0
 | 
				
			||||||
 | 
					   ATI1 images can be opened in ``L`` mode and ATI2 images can be opened in
 | 
				
			||||||
 | 
					   ``RGB`` mode.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. versionadded:: 9.4.0
 | 
				
			||||||
 | 
					   Uncompressed ``L`` ("luminance") and ``LA`` images can be opened and saved.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. versionadded:: 10.1.0
 | 
				
			||||||
 | 
					   BC5U can be read in ``RGB`` mode, and 8-bit color indexed images can be read
 | 
				
			||||||
 | 
					   in ``P`` mode.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
DIB
 | 
					DIB
 | 
				
			||||||
^^^
 | 
					^^^
 | 
				
			||||||
| 
						 | 
					@ -88,8 +115,13 @@ in ``L``, ``RGB`` and ``CMYK`` modes.
 | 
				
			||||||
Loading
 | 
					Loading
 | 
				
			||||||
~~~~~~~
 | 
					~~~~~~~
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					To use Ghostscript, Pillow searches for the "gs" executable. On Windows, it
 | 
				
			||||||
 | 
					also searches for "gswin32c" and "gswin64c". To customise this behaviour,
 | 
				
			||||||
 | 
					``EpsImagePlugin.gs_binary = "gswin64"`` will set the name of the executable to
 | 
				
			||||||
 | 
					use. ``EpsImagePlugin.gs_binary = False`` will prevent Ghostscript use.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
If Ghostscript is available, you can call the :py:meth:`~PIL.Image.Image.load`
 | 
					If Ghostscript is available, you can call the :py:meth:`~PIL.Image.Image.load`
 | 
				
			||||||
method with the following parameters to affect how Ghostscript renders the EPS
 | 
					method with the following parameters to affect how Ghostscript renders the EPS.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**scale**
 | 
					**scale**
 | 
				
			||||||
    Affects the scale of the resultant rasterized image. If the EPS suggests
 | 
					    Affects the scale of the resultant rasterized image. If the EPS suggests
 | 
				
			||||||
| 
						 | 
					@ -253,7 +285,7 @@ their :py:attr:`~PIL.Image.Image.info` values.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**loop**
 | 
					**loop**
 | 
				
			||||||
    Integer number of times the GIF should loop. 0 means that it will loop
 | 
					    Integer number of times the GIF should loop. 0 means that it will loop
 | 
				
			||||||
    forever. By default, the image will not loop.
 | 
					    forever. If omitted or ``None``, the image will not loop.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**comment**
 | 
					**comment**
 | 
				
			||||||
    A comment about the image.
 | 
					    A comment about the image.
 | 
				
			||||||
| 
						 | 
					@ -861,6 +893,10 @@ PPM
 | 
				
			||||||
Pillow reads and writes PBM, PGM, PPM and PNM files containing ``1``, ``L``, ``I`` or
 | 
					Pillow reads and writes PBM, PGM, PPM and PNM files containing ``1``, ``L``, ``I`` or
 | 
				
			||||||
``RGB`` data.
 | 
					``RGB`` data.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"Raw" (P4 to P6) formats can be read, and are used when writing.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Since Pillow 9.2.0, "plain" (P1 to P3) formats can be read as well.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
SGI
 | 
					SGI
 | 
				
			||||||
^^^
 | 
					^^^
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1482,7 +1518,7 @@ files. Different encoding methods are used, depending on the image mode.
 | 
				
			||||||
  unavailable
 | 
					  unavailable
 | 
				
			||||||
* L, RGB and CMYK mode images use JPEG encoding
 | 
					* L, RGB and CMYK mode images use JPEG encoding
 | 
				
			||||||
* P mode images use HEX encoding
 | 
					* P mode images use HEX encoding
 | 
				
			||||||
* RGBA mode images use JPEG2000 encoding
 | 
					* LA and RGBA mode images use JPEG2000 encoding
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.. _pdf-saving:
 | 
					.. _pdf-saving:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -72,11 +72,11 @@ true color.
 | 
				
			||||||
            # mode setting
 | 
					            # mode setting
 | 
				
			||||||
            bits = int(header[3])
 | 
					            bits = int(header[3])
 | 
				
			||||||
            if bits == 1:
 | 
					            if bits == 1:
 | 
				
			||||||
                self.mode = "1"
 | 
					                self._mode = "1"
 | 
				
			||||||
            elif bits == 8:
 | 
					            elif bits == 8:
 | 
				
			||||||
                self.mode = "L"
 | 
					                self._mode = "L"
 | 
				
			||||||
            elif bits == 24:
 | 
					            elif bits == 24:
 | 
				
			||||||
                self.mode = "RGB"
 | 
					                self._mode = "RGB"
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                msg = "unknown number of bits"
 | 
					                msg = "unknown number of bits"
 | 
				
			||||||
                raise SyntaxError(msg)
 | 
					                raise SyntaxError(msg)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -37,12 +37,12 @@ Pillow for enterprise is available via the Tidelift Subscription. `Learn more <h
 | 
				
			||||||
   :target: https://ci.appveyor.com/project/python-pillow/Pillow
 | 
					   :target: https://ci.appveyor.com/project/python-pillow/Pillow
 | 
				
			||||||
   :alt: AppVeyor CI build status (Windows)
 | 
					   :alt: AppVeyor CI build status (Windows)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.. image:: https://github.com/python-pillow/pillow-wheels/workflows/Wheels/badge.svg
 | 
					.. image:: https://github.com/python-pillow/Pillow/workflows/Wheels/badge.svg
 | 
				
			||||||
   :target: https://github.com/python-pillow/pillow-wheels/actions
 | 
					   :target: https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml
 | 
				
			||||||
   :alt: GitHub Actions wheels build status (Wheels)
 | 
					   :alt: GitHub Actions build status (Wheels)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.. image:: https://img.shields.io/travis/com/python-pillow/pillow-wheels/main.svg?label=aarch64%20wheels
 | 
					.. image:: https://img.shields.io/travis/com/python-pillow/Pillow/main.svg?label=aarch64%20wheels
 | 
				
			||||||
   :target: https://app.travis-ci.com/github/python-pillow/pillow-wheels
 | 
					   :target: https://app.travis-ci.com/github/python-pillow/Pillow
 | 
				
			||||||
   :alt: Travis CI wheels build status (aarch64)
 | 
					   :alt: Travis CI wheels build status (aarch64)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.. image:: https://codecov.io/gh/python-pillow/Pillow/branch/main/graph/badge.svg
 | 
					.. image:: https://codecov.io/gh/python-pillow/Pillow/branch/main/graph/badge.svg
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -83,10 +83,9 @@ Install Pillow with :command:`pip`::
 | 
				
			||||||
.. tab:: Windows
 | 
					.. tab:: Windows
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    We provide Pillow binaries for Windows compiled for the matrix of
 | 
					    We provide Pillow binaries for Windows compiled for the matrix of
 | 
				
			||||||
    supported Pythons in both 32 and 64-bit versions in the wheel format.
 | 
					    supported Pythons in 64-bit versions in the wheel format. These binaries include
 | 
				
			||||||
    These binaries include support for all optional libraries except
 | 
					    support for all optional libraries except libimagequant and libxcb. Raqm support
 | 
				
			||||||
    libimagequant and libxcb. Raqm support requires
 | 
					    requires FriBiDi to be installed separately::
 | 
				
			||||||
    FriBiDi to be installed separately::
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        python3 -m pip install --upgrade pip
 | 
					        python3 -m pip install --upgrade pip
 | 
				
			||||||
        python3 -m pip install --upgrade Pillow
 | 
					        python3 -m pip install --upgrade Pillow
 | 
				
			||||||
| 
						 | 
					@ -181,7 +180,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.2**
 | 
					  * Pillow has been tested with libimagequant **2.6-4.2.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.
 | 
				
			||||||
| 
						 | 
					@ -499,11 +498,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.8, 3.9, 3.10, 3.11      | 10.0.0           |arm           |
 | 
					| macOS 14 Sonoma                  | 3.8, 3.9, 3.10, 3.11      | 10.0.1           |arm           |
 | 
				
			||||||
|                                  +---------------------------+------------------+              |
 | 
					 | 
				
			||||||
|                                  | 3.7, 3.8, 3.9, 3.10, 3.11 | 9.5.0            |              |
 | 
					 | 
				
			||||||
+----------------------------------+---------------------------+------------------+--------------+
 | 
					+----------------------------------+---------------------------+------------------+--------------+
 | 
				
			||||||
| macOS 12 Big Sur                 | 3.7, 3.8, 3.9, 3.10, 3.11 | 9.3.0            |arm           |
 | 
					| macOS 13 Ventura                 | 3.8, 3.9, 3.10, 3.11      | 10.0.1           |arm           |
 | 
				
			||||||
 | 
					|                                  +---------------------------+------------------+              |
 | 
				
			||||||
 | 
					|                                  | 3.7                       | 9.5.0            |              |
 | 
				
			||||||
 | 
					+----------------------------------+---------------------------+------------------+--------------+
 | 
				
			||||||
 | 
					| macOS 12 Monterey                | 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           |
 | 
				
			||||||
|                                  +---------------------------+------------------+--------------+
 | 
					|                                  +---------------------------+------------------+--------------+
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,8 @@
 | 
				
			||||||
Python,3.11,3.10,3.9,3.8,3.7,3.6,3.5
 | 
					Python,3.12,3.11,3.10,3.9,3.8,3.7,3.6,3.5
 | 
				
			||||||
Pillow >= 10,Yes,Yes,Yes,Yes,,,
 | 
					Pillow >= 10.1,Yes,Yes,Yes,Yes,Yes,,,
 | 
				
			||||||
Pillow 9.3 - 9.5,Yes,Yes,Yes,Yes,Yes,,
 | 
					Pillow 10.0,,Yes,Yes,Yes,Yes,,,
 | 
				
			||||||
Pillow 9.0 - 9.2,,Yes,Yes,Yes,Yes,,
 | 
					Pillow 9.3 - 9.5,,Yes,Yes,Yes,Yes,Yes,,
 | 
				
			||||||
Pillow 8.3.2 - 8.4,,Yes,Yes,Yes,Yes,Yes,
 | 
					Pillow 9.0 - 9.2,,,Yes,Yes,Yes,Yes,,
 | 
				
			||||||
Pillow 8.0 - 8.3.1,,,Yes,Yes,Yes,Yes,
 | 
					Pillow 8.3.2 - 8.4,,,Yes,Yes,Yes,Yes,Yes,
 | 
				
			||||||
Pillow 7.0 - 7.2,,,,Yes,Yes,Yes,Yes
 | 
					Pillow 8.0 - 8.3.1,,,,Yes,Yes,Yes,Yes,
 | 
				
			||||||
 | 
					Pillow 7.0 - 7.2,,,,,Yes,Yes,Yes,Yes
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		
		
			
  | 
| 
						 | 
					@ -93,10 +93,14 @@ Generating images
 | 
				
			||||||
Registering plugins
 | 
					Registering plugins
 | 
				
			||||||
^^^^^^^^^^^^^^^^^^^
 | 
					^^^^^^^^^^^^^^^^^^^
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. autofunction:: preinit
 | 
				
			||||||
 | 
					.. autofunction:: init
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.. note::
 | 
					.. note::
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    These functions are for use by plugin authors. Application authors can
 | 
					    These functions are for use by plugin authors. They are called when a
 | 
				
			||||||
    ignore them.
 | 
					    plugin is loaded as part of :py:meth:`~preinit()` or :py:meth:`~init()`.
 | 
				
			||||||
 | 
					    Application authors can ignore them.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.. autofunction:: register_open
 | 
					.. autofunction:: register_open
 | 
				
			||||||
.. autofunction:: register_mime
 | 
					.. autofunction:: register_mime
 | 
				
			||||||
| 
						 | 
					@ -347,6 +351,8 @@ Instances of the :py:class:`Image` class have the following attributes:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    .. seealso:: :attr:`~Image.is_animated`, :func:`~Image.seek` and :func:`~Image.tell`
 | 
					    .. seealso:: :attr:`~Image.is_animated`, :func:`~Image.seek` and :func:`~Image.tell`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. autoattribute:: PIL.Image.Image.has_transparency_data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Classes
 | 
					Classes
 | 
				
			||||||
-------
 | 
					-------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -538,7 +538,7 @@ Methods
 | 
				
			||||||
                     It should be a `BCP 47 language code`_.
 | 
					                     It should be a `BCP 47 language code`_.
 | 
				
			||||||
                     Requires libraqm.
 | 
					                     Requires libraqm.
 | 
				
			||||||
    :param embedded_color: Whether to use font embedded color glyphs (COLR, CBDT, SBIX).
 | 
					    :param embedded_color: Whether to use font embedded color glyphs (COLR, CBDT, SBIX).
 | 
				
			||||||
    :return: Width for horizontal, height for vertical text.
 | 
					    :return: Either width for horizontal text, or height for vertical text.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.. py:method:: ImageDraw.textbbox(xy, text, font=None, anchor=None, spacing=4, align="left", direction=None, features=None, language=None, stroke_width=0, embedded_color=False)
 | 
					.. py:method:: ImageDraw.textbbox(xy, text, font=None, anchor=None, spacing=4, align="left", direction=None, features=None, language=None, stroke_width=0, embedded_color=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -206,4 +206,4 @@ Support reading signed 8-bit TIFF images
 | 
				
			||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 | 
					^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TIFF images with signed integer data, 8 bits per sample and a photometric
 | 
					TIFF images with signed integer data, 8 bits per sample and a photometric
 | 
				
			||||||
interpretaton of BlackIsZero can now be read.
 | 
					interpretation of BlackIsZero can now be read.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										14
									
								
								docs/releasenotes/10.0.1.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								docs/releasenotes/10.0.1.rst
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,14 @@
 | 
				
			||||||
 | 
					10.0.1
 | 
				
			||||||
 | 
					------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Security
 | 
				
			||||||
 | 
					========
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This release addresses :cve:`2023-4863`, by providing an updated install script and
 | 
				
			||||||
 | 
					updated wheels to include libwebp 1.3.2, preventing a potential heap buffer overflow
 | 
				
			||||||
 | 
					in WebP.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Updated tests to pass with latest zlib version
 | 
				
			||||||
 | 
					==============================================
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The release of zlib 1.3 caused one of the tests in the Pillow test suite to fail.
 | 
				
			||||||
							
								
								
									
										66
									
								
								docs/releasenotes/10.1.0.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								docs/releasenotes/10.1.0.rst
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,66 @@
 | 
				
			||||||
 | 
					10.1.0
 | 
				
			||||||
 | 
					------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Backwards Incompatible Changes
 | 
				
			||||||
 | 
					==============================
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Setting image mode
 | 
				
			||||||
 | 
					^^^^^^^^^^^^^^^^^^
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					If you attempt to set the mode of an image directly, e.g.
 | 
				
			||||||
 | 
					``im.mode = "RGBA"``, you will now receive an ``AttributeError``. This is
 | 
				
			||||||
 | 
					not about removing existing functionality, but instead about raising an
 | 
				
			||||||
 | 
					explicit error to prevent later consequences. The ``convert`` method is the
 | 
				
			||||||
 | 
					correct way to change an image's mode.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Deprecations
 | 
				
			||||||
 | 
					============
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TODO
 | 
				
			||||||
 | 
					^^^^
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TODO
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					API Changes
 | 
				
			||||||
 | 
					===========
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TODO
 | 
				
			||||||
 | 
					^^^^
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TODO
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					API Additions
 | 
				
			||||||
 | 
					=============
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					has_transparency_data
 | 
				
			||||||
 | 
					^^^^^^^^^^^^^^^^^^^^^
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Images now have :py:attr:`~PIL.Image.Image.has_transparency_data` to indicate
 | 
				
			||||||
 | 
					whether the image has transparency data, whether in the form of an alpha
 | 
				
			||||||
 | 
					channel, a palette with an alpha channel, or a "transparency" key in the
 | 
				
			||||||
 | 
					:py:attr:`~PIL.Image.Image.info` dictionary.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Even if this attribute is true, the image might still appear solid, if all of
 | 
				
			||||||
 | 
					the values shown within are opaque.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Security
 | 
				
			||||||
 | 
					========
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TODO
 | 
				
			||||||
 | 
					^^^^
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TODO
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Other Changes
 | 
				
			||||||
 | 
					=============
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Added support for DDS 8-bit color indexed images
 | 
				
			||||||
 | 
					^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Support has been added to read PALETTEINDEXED8 DDS files as P mode images.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Support reading signed 8-bit YCbCr TIFF images
 | 
				
			||||||
 | 
					^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TIFF images with unsigned integer data, 8 bits per sample and a photometric
 | 
				
			||||||
 | 
					interpretation of YCbCr can now be read.
 | 
				
			||||||
| 
						 | 
					@ -49,4 +49,3 @@ The external dependencies on libjpeg and zlib are now required by default.
 | 
				
			||||||
If the headers or libraries are not found, then installation will abort
 | 
					If the headers or libraries are not found, then installation will abort
 | 
				
			||||||
with an error. This behaviour can be disabled with the ``--disable-libjpeg``
 | 
					with an error. This behaviour can be disabled with the ``--disable-libjpeg``
 | 
				
			||||||
and ``--disable-zlib`` flags.
 | 
					and ``--disable-zlib`` flags.
 | 
				
			||||||
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -34,7 +34,3 @@ image size can lead to a smaller allocation than expected, leading to
 | 
				
			||||||
arbitrary writes.
 | 
					arbitrary writes.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
This issue was found by Cris Neckar at Divergent Security.
 | 
					This issue was found by Cris Neckar at Divergent Security.
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -20,5 +20,3 @@ CPython 3.6.1 to not work on installations of C-Python 3.6.0. This fix
 | 
				
			||||||
undefines PySlice_GetIndicesEx if it exists to restore compatibility
 | 
					undefines PySlice_GetIndicesEx if it exists to restore compatibility
 | 
				
			||||||
with both 3.6.0 and 3.6.1. See https://bugs.python.org/issue29943 for
 | 
					with both 3.6.0 and 3.6.1. See https://bugs.python.org/issue29943 for
 | 
				
			||||||
more details.
 | 
					more details.
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,4 +8,3 @@ Fixed Windows PyPy Build
 | 
				
			||||||
 | 
					
 | 
				
			||||||
A change in the 4.2.0 cycle broke the Windows PyPy build. This has
 | 
					A change in the 4.2.0 cycle broke the Windows PyPy build. This has
 | 
				
			||||||
been fixed, and PyPy is now part of the Windows CI matrix.
 | 
					been fixed, and PyPy is now part of the Windows CI matrix.
 | 
				
			||||||
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -175,6 +175,3 @@ Dark theme for docs
 | 
				
			||||||
^^^^^^^^^^^^^^^^^^^
 | 
					^^^^^^^^^^^^^^^^^^^
 | 
				
			||||||
 | 
					
 | 
				
			||||||
The https://pillow.readthedocs.io documentation will use a dark theme if the user has requested the system use one. Uses the ``prefers-color-scheme`` CSS media query.
 | 
					The https://pillow.readthedocs.io documentation will use a dark theme if the user has requested the system use one. Uses the ``prefers-color-scheme`` CSS media query.
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,6 +14,8 @@ expected to be backported to earlier versions.
 | 
				
			||||||
.. toctree::
 | 
					.. toctree::
 | 
				
			||||||
  :maxdepth: 2
 | 
					  :maxdepth: 2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  10.1.0
 | 
				
			||||||
 | 
					  10.0.1
 | 
				
			||||||
  10.0.0
 | 
					  10.0.0
 | 
				
			||||||
  9.5.0
 | 
					  9.5.0
 | 
				
			||||||
  9.4.0
 | 
					  9.4.0
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -16,6 +16,7 @@ classifiers =
 | 
				
			||||||
    Programming Language :: Python :: 3.9
 | 
					    Programming Language :: Python :: 3.9
 | 
				
			||||||
    Programming Language :: Python :: 3.10
 | 
					    Programming Language :: Python :: 3.10
 | 
				
			||||||
    Programming Language :: Python :: 3.11
 | 
					    Programming Language :: Python :: 3.11
 | 
				
			||||||
 | 
					    Programming Language :: Python :: 3.12
 | 
				
			||||||
    Programming Language :: Python :: Implementation :: CPython
 | 
					    Programming Language :: Python :: Implementation :: CPython
 | 
				
			||||||
    Programming Language :: Python :: Implementation :: PyPy
 | 
					    Programming Language :: Python :: Implementation :: PyPy
 | 
				
			||||||
    Topic :: Multimedia :: Graphics
 | 
					    Topic :: Multimedia :: Graphics
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										2
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								setup.py
									
									
									
									
									
								
							| 
						 | 
					@ -39,7 +39,7 @@ TIFF_ROOT = None
 | 
				
			||||||
ZLIB_ROOT = None
 | 
					ZLIB_ROOT = None
 | 
				
			||||||
FUZZING_BUILD = "LIB_FUZZING_ENGINE" in os.environ
 | 
					FUZZING_BUILD = "LIB_FUZZING_ENGINE" in os.environ
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if sys.platform == "win32" and sys.version_info >= (3, 12):
 | 
					if sys.platform == "win32" and sys.version_info >= (3, 13):
 | 
				
			||||||
    import atexit
 | 
					    import atexit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    atexit.register(
 | 
					    atexit.register(
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -68,11 +68,11 @@ def bdf_char(f):
 | 
				
			||||||
    # followed by the width in x (BBw), height in y (BBh),
 | 
					    # followed by the width in x (BBw), height in y (BBh),
 | 
				
			||||||
    # and x and y displacement (BBxoff0, BByoff0)
 | 
					    # and x and y displacement (BBxoff0, BByoff0)
 | 
				
			||||||
    # of the lower left corner from the origin of the character.
 | 
					    # of the lower left corner from the origin of the character.
 | 
				
			||||||
    width, height, x_disp, y_disp = [int(p) for p in props["BBX"].split()]
 | 
					    width, height, x_disp, y_disp = (int(p) for p in props["BBX"].split())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # The word DWIDTH
 | 
					    # The word DWIDTH
 | 
				
			||||||
    # followed by the width in x and y of the character in device pixels.
 | 
					    # followed by the width in x and y of the character in device pixels.
 | 
				
			||||||
    dwx, dwy = [int(p) for p in props["DWIDTH"].split()]
 | 
					    dwx, dwy = (int(p) for p in props["DWIDTH"].split())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    bbox = (
 | 
					    bbox = (
 | 
				
			||||||
        (dwx, dwy),
 | 
					        (dwx, dwy),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -266,7 +266,7 @@ class BlpImageFile(ImageFile.ImageFile):
 | 
				
			||||||
            msg = f"Bad BLP magic {repr(self.magic)}"
 | 
					            msg = f"Bad BLP magic {repr(self.magic)}"
 | 
				
			||||||
            raise BLPFormatError(msg)
 | 
					            raise BLPFormatError(msg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.mode = "RGBA" if self._blp_alpha_depth else "RGB"
 | 
					        self._mode = "RGBA" if self._blp_alpha_depth else "RGB"
 | 
				
			||||||
        self.tile = [(decoder, (0, 0) + self.size, 0, (self.mode, 0, 1))]
 | 
					        self.tile = [(decoder, (0, 0) + self.size, 0, (self.mode, 0, 1))]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -419,9 +419,11 @@ class BLPEncoder(ImageFile.PyEncoder):
 | 
				
			||||||
    def _write_palette(self):
 | 
					    def _write_palette(self):
 | 
				
			||||||
        data = b""
 | 
					        data = b""
 | 
				
			||||||
        palette = self.im.getpalette("RGBA", "RGBA")
 | 
					        palette = self.im.getpalette("RGBA", "RGBA")
 | 
				
			||||||
        for i in range(256):
 | 
					        for i in range(len(palette) // 4):
 | 
				
			||||||
            r, g, b, a = palette[i * 4 : (i + 1) * 4]
 | 
					            r, g, b, a = palette[i * 4 : (i + 1) * 4]
 | 
				
			||||||
            data += struct.pack("<4B", b, g, r, a)
 | 
					            data += struct.pack("<4B", b, g, r, a)
 | 
				
			||||||
 | 
					        while len(data) < 256 * 4:
 | 
				
			||||||
 | 
					            data += b"\x00" * 4
 | 
				
			||||||
        return data
 | 
					        return data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def encode(self, bufsize):
 | 
					    def encode(self, bufsize):
 | 
				
			||||||
| 
						 | 
					@ -442,7 +444,7 @@ class BLPEncoder(ImageFile.PyEncoder):
 | 
				
			||||||
        return len(data), 0, data
 | 
					        return len(data), 0, data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def _save(im, fp, filename, save_all=False):
 | 
					def _save(im, fp, filename):
 | 
				
			||||||
    if im.mode != "P":
 | 
					    if im.mode != "P":
 | 
				
			||||||
        msg = "Unsupported BLP image mode"
 | 
					        msg = "Unsupported BLP image mode"
 | 
				
			||||||
        raise ValueError(msg)
 | 
					        raise ValueError(msg)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -163,7 +163,7 @@ class BmpImageFile(ImageFile.ImageFile):
 | 
				
			||||||
            offset += 4 * file_info["colors"]
 | 
					            offset += 4 * file_info["colors"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # ---------------------- Check bit depth for unusual unsupported values
 | 
					        # ---------------------- Check bit depth for unusual unsupported values
 | 
				
			||||||
        self.mode, raw_mode = BIT2MODE.get(file_info["bits"], (None, None))
 | 
					        self._mode, raw_mode = BIT2MODE.get(file_info["bits"], (None, None))
 | 
				
			||||||
        if self.mode is None:
 | 
					        if self.mode is None:
 | 
				
			||||||
            msg = f"Unsupported BMP pixel depth ({file_info['bits']})"
 | 
					            msg = f"Unsupported BMP pixel depth ({file_info['bits']})"
 | 
				
			||||||
            raise OSError(msg)
 | 
					            raise OSError(msg)
 | 
				
			||||||
| 
						 | 
					@ -200,7 +200,7 @@ class BmpImageFile(ImageFile.ImageFile):
 | 
				
			||||||
                    and file_info["rgba_mask"] in SUPPORTED[file_info["bits"]]
 | 
					                    and file_info["rgba_mask"] in SUPPORTED[file_info["bits"]]
 | 
				
			||||||
                ):
 | 
					                ):
 | 
				
			||||||
                    raw_mode = MASK_MODES[(file_info["bits"], file_info["rgba_mask"])]
 | 
					                    raw_mode = MASK_MODES[(file_info["bits"], file_info["rgba_mask"])]
 | 
				
			||||||
                    self.mode = "RGBA" if "A" in raw_mode else self.mode
 | 
					                    self._mode = "RGBA" if "A" in raw_mode else self.mode
 | 
				
			||||||
                elif (
 | 
					                elif (
 | 
				
			||||||
                    file_info["bits"] in (24, 16)
 | 
					                    file_info["bits"] in (24, 16)
 | 
				
			||||||
                    and file_info["rgb_mask"] in SUPPORTED[file_info["bits"]]
 | 
					                    and file_info["rgb_mask"] in SUPPORTED[file_info["bits"]]
 | 
				
			||||||
| 
						 | 
					@ -214,7 +214,7 @@ class BmpImageFile(ImageFile.ImageFile):
 | 
				
			||||||
                raise OSError(msg)
 | 
					                raise OSError(msg)
 | 
				
			||||||
        elif file_info["compression"] == self.RAW:
 | 
					        elif file_info["compression"] == self.RAW:
 | 
				
			||||||
            if file_info["bits"] == 32 and header == 22:  # 32-bit .cur offset
 | 
					            if file_info["bits"] == 32 and header == 22:  # 32-bit .cur offset
 | 
				
			||||||
                raw_mode, self.mode = "BGRA", "RGBA"
 | 
					                raw_mode, self._mode = "BGRA", "RGBA"
 | 
				
			||||||
        elif file_info["compression"] in (self.RLE8, self.RLE4):
 | 
					        elif file_info["compression"] in (self.RLE8, self.RLE4):
 | 
				
			||||||
            decoder_name = "bmp_rle"
 | 
					            decoder_name = "bmp_rle"
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
| 
						 | 
					@ -245,10 +245,10 @@ class BmpImageFile(ImageFile.ImageFile):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                # ------- If all colors are grey, white or black, ditch palette
 | 
					                # ------- If all colors are grey, white or black, ditch palette
 | 
				
			||||||
                if greyscale:
 | 
					                if greyscale:
 | 
				
			||||||
                    self.mode = "1" if file_info["colors"] == 2 else "L"
 | 
					                    self._mode = "1" if file_info["colors"] == 2 else "L"
 | 
				
			||||||
                    raw_mode = self.mode
 | 
					                    raw_mode = self.mode
 | 
				
			||||||
                else:
 | 
					                else:
 | 
				
			||||||
                    self.mode = "P"
 | 
					                    self._mode = "P"
 | 
				
			||||||
                    self.palette = ImagePalette.raw(
 | 
					                    self.palette = ImagePalette.raw(
 | 
				
			||||||
                        "BGRX" if padding == 4 else "BGR", palette
 | 
					                        "BGRX" if padding == 4 else "BGR", palette
 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -46,7 +46,7 @@ class BufrStubImageFile(ImageFile.StubImageFile):
 | 
				
			||||||
        self.fp.seek(offset)
 | 
					        self.fp.seek(offset)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # make something up
 | 
					        # make something up
 | 
				
			||||||
        self.mode = "F"
 | 
					        self._mode = "F"
 | 
				
			||||||
        self._size = 1, 1
 | 
					        self._size = 1, 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        loader = self._load()
 | 
					        loader = self._load()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,7 +13,7 @@ Full text of the CC0 license:
 | 
				
			||||||
import struct
 | 
					import struct
 | 
				
			||||||
from io import BytesIO
 | 
					from io import BytesIO
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from . import Image, ImageFile
 | 
					from . import Image, ImageFile, ImagePalette
 | 
				
			||||||
from ._binary import o32le as o32
 | 
					from ._binary import o32le as o32
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Magic ("DDS ")
 | 
					# Magic ("DDS ")
 | 
				
			||||||
| 
						 | 
					@ -128,7 +128,7 @@ class DdsImageFile(ImageFile.ImageFile):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        flags, height, width = struct.unpack("<3I", header.read(12))
 | 
					        flags, height, width = struct.unpack("<3I", header.read(12))
 | 
				
			||||||
        self._size = (width, height)
 | 
					        self._size = (width, height)
 | 
				
			||||||
        self.mode = "RGBA"
 | 
					        self._mode = "RGBA"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        pitch, depth, mipmaps = struct.unpack("<3I", header.read(12))
 | 
					        pitch, depth, mipmaps = struct.unpack("<3I", header.read(12))
 | 
				
			||||||
        struct.unpack("<11I", header.read(44))  # reserved
 | 
					        struct.unpack("<11I", header.read(44))  # reserved
 | 
				
			||||||
| 
						 | 
					@ -141,9 +141,9 @@ class DdsImageFile(ImageFile.ImageFile):
 | 
				
			||||||
        if pfflags & DDPF_LUMINANCE:
 | 
					        if pfflags & DDPF_LUMINANCE:
 | 
				
			||||||
            # Texture contains uncompressed L or LA data
 | 
					            # Texture contains uncompressed L or LA data
 | 
				
			||||||
            if pfflags & DDPF_ALPHAPIXELS:
 | 
					            if pfflags & DDPF_ALPHAPIXELS:
 | 
				
			||||||
                self.mode = "LA"
 | 
					                self._mode = "LA"
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                self.mode = "L"
 | 
					                self._mode = "L"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            self.tile = [("raw", (0, 0) + self.size, 0, (self.mode, 0, 1))]
 | 
					            self.tile = [("raw", (0, 0) + self.size, 0, (self.mode, 0, 1))]
 | 
				
			||||||
        elif pfflags & DDPF_RGB:
 | 
					        elif pfflags & DDPF_RGB:
 | 
				
			||||||
| 
						 | 
					@ -153,10 +153,14 @@ class DdsImageFile(ImageFile.ImageFile):
 | 
				
			||||||
            if pfflags & DDPF_ALPHAPIXELS:
 | 
					            if pfflags & DDPF_ALPHAPIXELS:
 | 
				
			||||||
                rawmode += masks[0xFF000000]
 | 
					                rawmode += masks[0xFF000000]
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                self.mode = "RGB"
 | 
					                self._mode = "RGB"
 | 
				
			||||||
            rawmode += masks[0xFF0000] + masks[0xFF00] + masks[0xFF]
 | 
					            rawmode += masks[0xFF0000] + masks[0xFF00] + masks[0xFF]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            self.tile = [("raw", (0, 0) + self.size, 0, (rawmode[::-1], 0, 1))]
 | 
					            self.tile = [("raw", (0, 0) + self.size, 0, (rawmode[::-1], 0, 1))]
 | 
				
			||||||
 | 
					        elif pfflags & DDPF_PALETTEINDEXED8:
 | 
				
			||||||
 | 
					            self._mode = "P"
 | 
				
			||||||
 | 
					            self.palette = ImagePalette.raw("RGBA", self.fp.read(1024))
 | 
				
			||||||
 | 
					            self.tile = [("raw", (0, 0) + self.size, 0, "L")]
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            data_start = header_size + 4
 | 
					            data_start = header_size + 4
 | 
				
			||||||
            n = 0
 | 
					            n = 0
 | 
				
			||||||
| 
						 | 
					@ -172,15 +176,15 @@ class DdsImageFile(ImageFile.ImageFile):
 | 
				
			||||||
            elif fourcc == b"ATI1":
 | 
					            elif fourcc == b"ATI1":
 | 
				
			||||||
                self.pixel_format = "BC4"
 | 
					                self.pixel_format = "BC4"
 | 
				
			||||||
                n = 4
 | 
					                n = 4
 | 
				
			||||||
                self.mode = "L"
 | 
					                self._mode = "L"
 | 
				
			||||||
            elif fourcc == b"ATI2":
 | 
					            elif fourcc in (b"ATI2", b"BC5U"):
 | 
				
			||||||
                self.pixel_format = "BC5"
 | 
					                self.pixel_format = "BC5"
 | 
				
			||||||
                n = 5
 | 
					                n = 5
 | 
				
			||||||
                self.mode = "RGB"
 | 
					                self._mode = "RGB"
 | 
				
			||||||
            elif fourcc == b"BC5S":
 | 
					            elif fourcc == b"BC5S":
 | 
				
			||||||
                self.pixel_format = "BC5S"
 | 
					                self.pixel_format = "BC5S"
 | 
				
			||||||
                n = 5
 | 
					                n = 5
 | 
				
			||||||
                self.mode = "RGB"
 | 
					                self._mode = "RGB"
 | 
				
			||||||
            elif fourcc == b"DX10":
 | 
					            elif fourcc == b"DX10":
 | 
				
			||||||
                data_start += 20
 | 
					                data_start += 20
 | 
				
			||||||
                # ignoring flags which pertain to volume textures and cubemaps
 | 
					                # ignoring flags which pertain to volume textures and cubemaps
 | 
				
			||||||
| 
						 | 
					@ -189,19 +193,19 @@ class DdsImageFile(ImageFile.ImageFile):
 | 
				
			||||||
                if dxgi_format in (DXGI_FORMAT_BC5_TYPELESS, DXGI_FORMAT_BC5_UNORM):
 | 
					                if dxgi_format in (DXGI_FORMAT_BC5_TYPELESS, DXGI_FORMAT_BC5_UNORM):
 | 
				
			||||||
                    self.pixel_format = "BC5"
 | 
					                    self.pixel_format = "BC5"
 | 
				
			||||||
                    n = 5
 | 
					                    n = 5
 | 
				
			||||||
                    self.mode = "RGB"
 | 
					                    self._mode = "RGB"
 | 
				
			||||||
                elif dxgi_format == DXGI_FORMAT_BC5_SNORM:
 | 
					                elif dxgi_format == DXGI_FORMAT_BC5_SNORM:
 | 
				
			||||||
                    self.pixel_format = "BC5S"
 | 
					                    self.pixel_format = "BC5S"
 | 
				
			||||||
                    n = 5
 | 
					                    n = 5
 | 
				
			||||||
                    self.mode = "RGB"
 | 
					                    self._mode = "RGB"
 | 
				
			||||||
                elif dxgi_format == DXGI_FORMAT_BC6H_UF16:
 | 
					                elif dxgi_format == DXGI_FORMAT_BC6H_UF16:
 | 
				
			||||||
                    self.pixel_format = "BC6H"
 | 
					                    self.pixel_format = "BC6H"
 | 
				
			||||||
                    n = 6
 | 
					                    n = 6
 | 
				
			||||||
                    self.mode = "RGB"
 | 
					                    self._mode = "RGB"
 | 
				
			||||||
                elif dxgi_format == DXGI_FORMAT_BC6H_SF16:
 | 
					                elif dxgi_format == DXGI_FORMAT_BC6H_SF16:
 | 
				
			||||||
                    self.pixel_format = "BC6HS"
 | 
					                    self.pixel_format = "BC6HS"
 | 
				
			||||||
                    n = 6
 | 
					                    n = 6
 | 
				
			||||||
                    self.mode = "RGB"
 | 
					                    self._mode = "RGB"
 | 
				
			||||||
                elif dxgi_format in (DXGI_FORMAT_BC7_TYPELESS, DXGI_FORMAT_BC7_UNORM):
 | 
					                elif dxgi_format in (DXGI_FORMAT_BC7_TYPELESS, DXGI_FORMAT_BC7_UNORM):
 | 
				
			||||||
                    self.pixel_format = "BC7"
 | 
					                    self.pixel_format = "BC7"
 | 
				
			||||||
                    n = 7
 | 
					                    n = 7
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -37,33 +37,39 @@ from ._deprecate import deprecate
 | 
				
			||||||
split = re.compile(r"^%%([^:]*):[ \t]*(.*)[ \t]*$")
 | 
					split = re.compile(r"^%%([^:]*):[ \t]*(.*)[ \t]*$")
 | 
				
			||||||
field = re.compile(r"^%[%!\w]([^:]*)[ \t]*$")
 | 
					field = re.compile(r"^%[%!\w]([^:]*)[ \t]*$")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					gs_binary = None
 | 
				
			||||||
gs_windows_binary = None
 | 
					gs_windows_binary = None
 | 
				
			||||||
if sys.platform.startswith("win"):
 | 
					 | 
				
			||||||
    import shutil
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    for binary in ("gswin32c", "gswin64c", "gs"):
 | 
					 | 
				
			||||||
        if shutil.which(binary) is not None:
 | 
					 | 
				
			||||||
            gs_windows_binary = binary
 | 
					 | 
				
			||||||
            break
 | 
					 | 
				
			||||||
    else:
 | 
					 | 
				
			||||||
        gs_windows_binary = False
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def has_ghostscript():
 | 
					def has_ghostscript():
 | 
				
			||||||
    if gs_windows_binary:
 | 
					    global gs_binary, gs_windows_binary
 | 
				
			||||||
        return True
 | 
					    if gs_binary is None:
 | 
				
			||||||
    if not sys.platform.startswith("win"):
 | 
					        if sys.platform.startswith("win"):
 | 
				
			||||||
        try:
 | 
					            if gs_windows_binary is None:
 | 
				
			||||||
            subprocess.check_call(["gs", "--version"], stdout=subprocess.DEVNULL)
 | 
					                import shutil
 | 
				
			||||||
            return True
 | 
					
 | 
				
			||||||
        except OSError:
 | 
					                for binary in ("gswin32c", "gswin64c", "gs"):
 | 
				
			||||||
            # No Ghostscript
 | 
					                    if shutil.which(binary) is not None:
 | 
				
			||||||
            pass
 | 
					                        gs_windows_binary = binary
 | 
				
			||||||
    return False
 | 
					                        break
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    gs_windows_binary = False
 | 
				
			||||||
 | 
					            gs_binary = gs_windows_binary
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                subprocess.check_call(["gs", "--version"], stdout=subprocess.DEVNULL)
 | 
				
			||||||
 | 
					                gs_binary = "gs"
 | 
				
			||||||
 | 
					            except OSError:
 | 
				
			||||||
 | 
					                gs_binary = False
 | 
				
			||||||
 | 
					    return gs_binary is not False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def Ghostscript(tile, size, fp, scale=1, transparency=False):
 | 
					def Ghostscript(tile, size, fp, scale=1, transparency=False):
 | 
				
			||||||
    """Render an image using Ghostscript"""
 | 
					    """Render an image using Ghostscript"""
 | 
				
			||||||
 | 
					    global gs_binary
 | 
				
			||||||
 | 
					    if not has_ghostscript():
 | 
				
			||||||
 | 
					        msg = "Unable to locate Ghostscript on paths"
 | 
				
			||||||
 | 
					        raise OSError(msg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Unpack decoder tile
 | 
					    # Unpack decoder tile
 | 
				
			||||||
    decoder, tile, offset, data = tile[0]
 | 
					    decoder, tile, offset, data = tile[0]
 | 
				
			||||||
| 
						 | 
					@ -113,7 +119,7 @@ def Ghostscript(tile, size, fp, scale=1, transparency=False):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Build Ghostscript command
 | 
					    # Build Ghostscript command
 | 
				
			||||||
    command = [
 | 
					    command = [
 | 
				
			||||||
        "gs",
 | 
					        gs_binary,
 | 
				
			||||||
        "-q",  # quiet mode
 | 
					        "-q",  # quiet mode
 | 
				
			||||||
        "-g%dx%d" % size,  # set output geometry (pixels)
 | 
					        "-g%dx%d" % size,  # set output geometry (pixels)
 | 
				
			||||||
        "-r%fx%f" % res,  # set input DPI (dots per inch)
 | 
					        "-r%fx%f" % res,  # set input DPI (dots per inch)
 | 
				
			||||||
| 
						 | 
					@ -132,19 +138,6 @@ def Ghostscript(tile, size, fp, scale=1, transparency=False):
 | 
				
			||||||
        "showpage",
 | 
					        "showpage",
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if gs_windows_binary is not None:
 | 
					 | 
				
			||||||
        if not gs_windows_binary:
 | 
					 | 
				
			||||||
            try:
 | 
					 | 
				
			||||||
                os.unlink(outfile)
 | 
					 | 
				
			||||||
                if infile_temp:
 | 
					 | 
				
			||||||
                    os.unlink(infile_temp)
 | 
					 | 
				
			||||||
            except OSError:
 | 
					 | 
				
			||||||
                pass
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            msg = "Unable to locate Ghostscript on paths"
 | 
					 | 
				
			||||||
            raise OSError(msg)
 | 
					 | 
				
			||||||
        command[0] = gs_windows_binary
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # push data through Ghostscript
 | 
					    # push data through Ghostscript
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        startupinfo = None
 | 
					        startupinfo = None
 | 
				
			||||||
| 
						 | 
					@ -227,13 +220,15 @@ class EpsImageFile(ImageFile.ImageFile):
 | 
				
			||||||
        # go to offset - start of "%!PS"
 | 
					        # go to offset - start of "%!PS"
 | 
				
			||||||
        self.fp.seek(offset)
 | 
					        self.fp.seek(offset)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.mode = "RGB"
 | 
					        self._mode = "RGB"
 | 
				
			||||||
        self._size = None
 | 
					        self._size = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        byte_arr = bytearray(255)
 | 
					        byte_arr = bytearray(255)
 | 
				
			||||||
        bytes_mv = memoryview(byte_arr)
 | 
					        bytes_mv = memoryview(byte_arr)
 | 
				
			||||||
        bytes_read = 0
 | 
					        bytes_read = 0
 | 
				
			||||||
        reading_comments = True
 | 
					        reading_header_comments = True
 | 
				
			||||||
 | 
					        reading_trailer_comments = False
 | 
				
			||||||
 | 
					        trailer_reached = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        def check_required_header_comments():
 | 
					        def check_required_header_comments():
 | 
				
			||||||
            if "PS-Adobe" not in self.info:
 | 
					            if "PS-Adobe" not in self.info:
 | 
				
			||||||
| 
						 | 
					@ -243,6 +238,36 @@ class EpsImageFile(ImageFile.ImageFile):
 | 
				
			||||||
                msg = 'EPS header missing "%%BoundingBox" comment'
 | 
					                msg = 'EPS header missing "%%BoundingBox" comment'
 | 
				
			||||||
                raise SyntaxError(msg)
 | 
					                raise SyntaxError(msg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def _read_comment(s):
 | 
				
			||||||
 | 
					            nonlocal reading_trailer_comments
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                m = split.match(s)
 | 
				
			||||||
 | 
					            except re.error as e:
 | 
				
			||||||
 | 
					                msg = "not an EPS file"
 | 
				
			||||||
 | 
					                raise SyntaxError(msg) from e
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if m:
 | 
				
			||||||
 | 
					                k, v = m.group(1, 2)
 | 
				
			||||||
 | 
					                self.info[k] = v
 | 
				
			||||||
 | 
					                if k == "BoundingBox":
 | 
				
			||||||
 | 
					                    if v == "(atend)":
 | 
				
			||||||
 | 
					                        reading_trailer_comments = True
 | 
				
			||||||
 | 
					                    elif not self._size or (
 | 
				
			||||||
 | 
					                        trailer_reached and reading_trailer_comments
 | 
				
			||||||
 | 
					                    ):
 | 
				
			||||||
 | 
					                        try:
 | 
				
			||||||
 | 
					                            # Note: The DSC spec says that BoundingBox
 | 
				
			||||||
 | 
					                            # fields should be integers, but some drivers
 | 
				
			||||||
 | 
					                            # put floating point values there anyway.
 | 
				
			||||||
 | 
					                            box = [int(float(i)) for i in v.split()]
 | 
				
			||||||
 | 
					                            self._size = box[2] - box[0], box[3] - box[1]
 | 
				
			||||||
 | 
					                            self.tile = [
 | 
				
			||||||
 | 
					                                ("eps", (0, 0) + self.size, offset, (length, box))
 | 
				
			||||||
 | 
					                            ]
 | 
				
			||||||
 | 
					                        except Exception:
 | 
				
			||||||
 | 
					                            pass
 | 
				
			||||||
 | 
					                return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        while True:
 | 
					        while True:
 | 
				
			||||||
            byte = self.fp.read(1)
 | 
					            byte = self.fp.read(1)
 | 
				
			||||||
            if byte == b"":
 | 
					            if byte == b"":
 | 
				
			||||||
| 
						 | 
					@ -265,9 +290,9 @@ class EpsImageFile(ImageFile.ImageFile):
 | 
				
			||||||
                        msg = "not an EPS file"
 | 
					                        msg = "not an EPS file"
 | 
				
			||||||
                        raise SyntaxError(msg)
 | 
					                        raise SyntaxError(msg)
 | 
				
			||||||
                    else:
 | 
					                    else:
 | 
				
			||||||
                        if reading_comments:
 | 
					                        if reading_header_comments:
 | 
				
			||||||
                            check_required_header_comments()
 | 
					                            check_required_header_comments()
 | 
				
			||||||
                            reading_comments = False
 | 
					                            reading_header_comments = False
 | 
				
			||||||
                        # reset bytes_read so we can keep reading
 | 
					                        # reset bytes_read so we can keep reading
 | 
				
			||||||
                        # data until the end of the line
 | 
					                        # data until the end of the line
 | 
				
			||||||
                        bytes_read = 0
 | 
					                        bytes_read = 0
 | 
				
			||||||
| 
						 | 
					@ -275,7 +300,7 @@ class EpsImageFile(ImageFile.ImageFile):
 | 
				
			||||||
                bytes_read += 1
 | 
					                bytes_read += 1
 | 
				
			||||||
                continue
 | 
					                continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if reading_comments:
 | 
					            if reading_header_comments:
 | 
				
			||||||
                # Load EPS header
 | 
					                # Load EPS header
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                # if this line doesn't start with a "%",
 | 
					                # if this line doesn't start with a "%",
 | 
				
			||||||
| 
						 | 
					@ -283,33 +308,11 @@ class EpsImageFile(ImageFile.ImageFile):
 | 
				
			||||||
                # then we've reached the end of the header/comments
 | 
					                # then we've reached the end of the header/comments
 | 
				
			||||||
                if byte_arr[0] != ord("%") or bytes_mv[:13] == b"%%EndComments":
 | 
					                if byte_arr[0] != ord("%") or bytes_mv[:13] == b"%%EndComments":
 | 
				
			||||||
                    check_required_header_comments()
 | 
					                    check_required_header_comments()
 | 
				
			||||||
                    reading_comments = False
 | 
					                    reading_header_comments = False
 | 
				
			||||||
                    continue
 | 
					                    continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                s = str(bytes_mv[:bytes_read], "latin-1")
 | 
					                s = str(bytes_mv[:bytes_read], "latin-1")
 | 
				
			||||||
 | 
					                if not _read_comment(s):
 | 
				
			||||||
                try:
 | 
					 | 
				
			||||||
                    m = split.match(s)
 | 
					 | 
				
			||||||
                except re.error as e:
 | 
					 | 
				
			||||||
                    msg = "not an EPS file"
 | 
					 | 
				
			||||||
                    raise SyntaxError(msg) from e
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if m:
 | 
					 | 
				
			||||||
                    k, v = m.group(1, 2)
 | 
					 | 
				
			||||||
                    self.info[k] = v
 | 
					 | 
				
			||||||
                    if k == "BoundingBox":
 | 
					 | 
				
			||||||
                        try:
 | 
					 | 
				
			||||||
                            # Note: The DSC spec says that BoundingBox
 | 
					 | 
				
			||||||
                            # fields should be integers, but some drivers
 | 
					 | 
				
			||||||
                            # put floating point values there anyway.
 | 
					 | 
				
			||||||
                            box = [int(float(i)) for i in v.split()]
 | 
					 | 
				
			||||||
                            self._size = box[2] - box[0], box[3] - box[1]
 | 
					 | 
				
			||||||
                            self.tile = [
 | 
					 | 
				
			||||||
                                ("eps", (0, 0) + self.size, offset, (length, box))
 | 
					 | 
				
			||||||
                            ]
 | 
					 | 
				
			||||||
                        except Exception:
 | 
					 | 
				
			||||||
                            pass
 | 
					 | 
				
			||||||
                else:
 | 
					 | 
				
			||||||
                    m = field.match(s)
 | 
					                    m = field.match(s)
 | 
				
			||||||
                    if m:
 | 
					                    if m:
 | 
				
			||||||
                        k = m.group(1)
 | 
					                        k = m.group(1)
 | 
				
			||||||
| 
						 | 
					@ -339,15 +342,15 @@ class EpsImageFile(ImageFile.ImageFile):
 | 
				
			||||||
                # data start identifier (the image data follows after a single line
 | 
					                # data start identifier (the image data follows after a single line
 | 
				
			||||||
                #   consisting only of this quoted value)
 | 
					                #   consisting only of this quoted value)
 | 
				
			||||||
                image_data_values = byte_arr[11:bytes_read].split(None, 7)
 | 
					                image_data_values = byte_arr[11:bytes_read].split(None, 7)
 | 
				
			||||||
                columns, rows, bit_depth, mode_id = [
 | 
					                columns, rows, bit_depth, mode_id = (
 | 
				
			||||||
                    int(value) for value in image_data_values[:4]
 | 
					                    int(value) for value in image_data_values[:4]
 | 
				
			||||||
                ]
 | 
					                )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if bit_depth == 1:
 | 
					                if bit_depth == 1:
 | 
				
			||||||
                    self.mode = "1"
 | 
					                    self._mode = "1"
 | 
				
			||||||
                elif bit_depth == 8:
 | 
					                elif bit_depth == 8:
 | 
				
			||||||
                    try:
 | 
					                    try:
 | 
				
			||||||
                        self.mode = self.mode_map[mode_id]
 | 
					                        self._mode = self.mode_map[mode_id]
 | 
				
			||||||
                    except ValueError:
 | 
					                    except ValueError:
 | 
				
			||||||
                        break
 | 
					                        break
 | 
				
			||||||
                else:
 | 
					                else:
 | 
				
			||||||
| 
						 | 
					@ -355,7 +358,18 @@ class EpsImageFile(ImageFile.ImageFile):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                self._size = columns, rows
 | 
					                self._size = columns, rows
 | 
				
			||||||
                return
 | 
					                return
 | 
				
			||||||
 | 
					            elif trailer_reached and reading_trailer_comments:
 | 
				
			||||||
 | 
					                # Load EPS trailer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                # if this line starts with "%%EOF",
 | 
				
			||||||
 | 
					                # then we've reached the end of the file
 | 
				
			||||||
 | 
					                if bytes_mv[:5] == b"%%EOF":
 | 
				
			||||||
 | 
					                    break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                s = str(bytes_mv[:bytes_read], "latin-1")
 | 
				
			||||||
 | 
					                _read_comment(s)
 | 
				
			||||||
 | 
					            elif bytes_mv[:9] == b"%%Trailer":
 | 
				
			||||||
 | 
					                trailer_reached = True
 | 
				
			||||||
            bytes_read = 0
 | 
					            bytes_read = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        check_required_header_comments()
 | 
					        check_required_header_comments()
 | 
				
			||||||
| 
						 | 
					@ -391,7 +405,7 @@ class EpsImageFile(ImageFile.ImageFile):
 | 
				
			||||||
        # Load EPS via Ghostscript
 | 
					        # Load EPS via Ghostscript
 | 
				
			||||||
        if self.tile:
 | 
					        if self.tile:
 | 
				
			||||||
            self.im = Ghostscript(self.tile, self.size, self.fp, scale, transparency)
 | 
					            self.im = Ghostscript(self.tile, self.size, self.fp, scale, transparency)
 | 
				
			||||||
            self.mode = self.im.mode
 | 
					            self._mode = self.im.mode
 | 
				
			||||||
            self._size = self.im.size
 | 
					            self._size = self.im.size
 | 
				
			||||||
            self.tile = []
 | 
					            self.tile = []
 | 
				
			||||||
        return Image.Image.load(self)
 | 
					        return Image.Image.load(self)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -51,14 +51,14 @@ class FitsImageFile(ImageFile.ImageFile):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        number_of_bits = int(headers[b"BITPIX"])
 | 
					        number_of_bits = int(headers[b"BITPIX"])
 | 
				
			||||||
        if number_of_bits == 8:
 | 
					        if number_of_bits == 8:
 | 
				
			||||||
            self.mode = "L"
 | 
					            self._mode = "L"
 | 
				
			||||||
        elif number_of_bits == 16:
 | 
					        elif number_of_bits == 16:
 | 
				
			||||||
            self.mode = "I"
 | 
					            self._mode = "I"
 | 
				
			||||||
            # rawmode = "I;16S"
 | 
					            # rawmode = "I;16S"
 | 
				
			||||||
        elif number_of_bits == 32:
 | 
					        elif number_of_bits == 32:
 | 
				
			||||||
            self.mode = "I"
 | 
					            self._mode = "I"
 | 
				
			||||||
        elif number_of_bits in (-32, -64):
 | 
					        elif number_of_bits in (-32, -64):
 | 
				
			||||||
            self.mode = "F"
 | 
					            self._mode = "F"
 | 
				
			||||||
            # rawmode = "F" if number_of_bits == -32 else "F;64F"
 | 
					            # rawmode = "F" if number_of_bits == -32 else "F;64F"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        offset = math.ceil(self.fp.tell() / 2880) * 2880
 | 
					        offset = math.ceil(self.fp.tell() / 2880) * 2880
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -56,7 +56,7 @@ class FliImageFile(ImageFile.ImageFile):
 | 
				
			||||||
        self.is_animated = self.n_frames > 1
 | 
					        self.is_animated = self.n_frames > 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # image characteristics
 | 
					        # image characteristics
 | 
				
			||||||
        self.mode = "P"
 | 
					        self._mode = "P"
 | 
				
			||||||
        self._size = i16(s, 8), i16(s, 10)
 | 
					        self._size = i16(s, 8), i16(s, 10)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # animation speed
 | 
					        # animation speed
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -106,7 +106,7 @@ class FpxImageFile(ImageFile.ImageFile):
 | 
				
			||||||
            # note: for now, we ignore the "uncalibrated" flag
 | 
					            # note: for now, we ignore the "uncalibrated" flag
 | 
				
			||||||
            colors.append(i32(s, 8 + i * 4) & 0x7FFFFFFF)
 | 
					            colors.append(i32(s, 8 + i * 4) & 0x7FFFFFFF)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.mode, self.rawmode = MODES[tuple(colors)]
 | 
					        self._mode, self.rawmode = MODES[tuple(colors)]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # load JPEG tables, if any
 | 
					        # load JPEG tables, if any
 | 
				
			||||||
        self.jpeg = {}
 | 
					        self.jpeg = {}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -77,7 +77,7 @@ class FtexImageFile(ImageFile.ImageFile):
 | 
				
			||||||
        self._size = struct.unpack("<2i", self.fp.read(8))
 | 
					        self._size = struct.unpack("<2i", self.fp.read(8))
 | 
				
			||||||
        mipmap_count, format_count = struct.unpack("<2i", self.fp.read(8))
 | 
					        mipmap_count, format_count = struct.unpack("<2i", self.fp.read(8))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.mode = "RGB"
 | 
					        self._mode = "RGB"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Only support single-format files.
 | 
					        # Only support single-format files.
 | 
				
			||||||
        # I don't know of any multi-format file.
 | 
					        # I don't know of any multi-format file.
 | 
				
			||||||
| 
						 | 
					@ -90,7 +90,7 @@ class FtexImageFile(ImageFile.ImageFile):
 | 
				
			||||||
        data = self.fp.read(mipmap_size)
 | 
					        data = self.fp.read(mipmap_size)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if format == Format.DXT1:
 | 
					        if format == Format.DXT1:
 | 
				
			||||||
            self.mode = "RGBA"
 | 
					            self._mode = "RGBA"
 | 
				
			||||||
            self.tile = [("bcn", (0, 0) + self.size, 0, 1)]
 | 
					            self.tile = [("bcn", (0, 0) + self.size, 0, 1)]
 | 
				
			||||||
        elif format == Format.UNCOMPRESSED:
 | 
					        elif format == Format.UNCOMPRESSED:
 | 
				
			||||||
            self.tile = [("raw", (0, 0) + self.size, 0, ("RGB", 0, 1))]
 | 
					            self.tile = [("raw", (0, 0) + self.size, 0, ("RGB", 0, 1))]
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -73,9 +73,9 @@ class GbrImageFile(ImageFile.ImageFile):
 | 
				
			||||||
        comment = self.fp.read(comment_length)[:-1]
 | 
					        comment = self.fp.read(comment_length)[:-1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if color_depth == 1:
 | 
					        if color_depth == 1:
 | 
				
			||||||
            self.mode = "L"
 | 
					            self._mode = "L"
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            self.mode = "RGBA"
 | 
					            self._mode = "RGBA"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self._size = width, height
 | 
					        self._size = width, height
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -51,7 +51,7 @@ class GdImageFile(ImageFile.ImageFile):
 | 
				
			||||||
            msg = "Not a valid GD 2.x .gd file"
 | 
					            msg = "Not a valid GD 2.x .gd file"
 | 
				
			||||||
            raise SyntaxError(msg)
 | 
					            raise SyntaxError(msg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.mode = "L"  # FIXME: "P"
 | 
					        self._mode = "L"  # FIXME: "P"
 | 
				
			||||||
        self._size = i16(s, 2), i16(s, 4)
 | 
					        self._size = i16(s, 2), i16(s, 4)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        true_color = s[6]
 | 
					        true_color = s[6]
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -304,11 +304,11 @@ class GifImageFile(ImageFile.ImageFile):
 | 
				
			||||||
        if frame == 0:
 | 
					        if frame == 0:
 | 
				
			||||||
            if self._frame_palette:
 | 
					            if self._frame_palette:
 | 
				
			||||||
                if LOADING_STRATEGY == LoadingStrategy.RGB_ALWAYS:
 | 
					                if LOADING_STRATEGY == LoadingStrategy.RGB_ALWAYS:
 | 
				
			||||||
                    self.mode = "RGBA" if frame_transparency is not None else "RGB"
 | 
					                    self._mode = "RGBA" if frame_transparency is not None else "RGB"
 | 
				
			||||||
                else:
 | 
					                else:
 | 
				
			||||||
                    self.mode = "P"
 | 
					                    self._mode = "P"
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                self.mode = "L"
 | 
					                self._mode = "L"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if not palette and self.global_palette:
 | 
					            if not palette and self.global_palette:
 | 
				
			||||||
                from copy import copy
 | 
					                from copy import copy
 | 
				
			||||||
| 
						 | 
					@ -325,10 +325,10 @@ class GifImageFile(ImageFile.ImageFile):
 | 
				
			||||||
                    if "transparency" in self.info:
 | 
					                    if "transparency" in self.info:
 | 
				
			||||||
                        self.im.putpalettealpha(self.info["transparency"], 0)
 | 
					                        self.im.putpalettealpha(self.info["transparency"], 0)
 | 
				
			||||||
                        self.im = self.im.convert("RGBA", Image.Dither.FLOYDSTEINBERG)
 | 
					                        self.im = self.im.convert("RGBA", Image.Dither.FLOYDSTEINBERG)
 | 
				
			||||||
                        self.mode = "RGBA"
 | 
					                        self._mode = "RGBA"
 | 
				
			||||||
                        del self.info["transparency"]
 | 
					                        del self.info["transparency"]
 | 
				
			||||||
                    else:
 | 
					                    else:
 | 
				
			||||||
                        self.mode = "RGB"
 | 
					                        self._mode = "RGB"
 | 
				
			||||||
                        self.im = self.im.convert("RGB", Image.Dither.FLOYDSTEINBERG)
 | 
					                        self.im = self.im.convert("RGB", Image.Dither.FLOYDSTEINBERG)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        def _rgb(color):
 | 
					        def _rgb(color):
 | 
				
			||||||
| 
						 | 
					@ -424,7 +424,7 @@ class GifImageFile(ImageFile.ImageFile):
 | 
				
			||||||
                self.im.putpalette(*self._frame_palette.getdata())
 | 
					                self.im.putpalette(*self._frame_palette.getdata())
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                self.im = None
 | 
					                self.im = None
 | 
				
			||||||
        self.mode = temp_mode
 | 
					        self._mode = temp_mode
 | 
				
			||||||
        self._frame_palette = None
 | 
					        self._frame_palette = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        super().load_prepare()
 | 
					        super().load_prepare()
 | 
				
			||||||
| 
						 | 
					@ -434,9 +434,9 @@ class GifImageFile(ImageFile.ImageFile):
 | 
				
			||||||
            if self.mode == "P" and LOADING_STRATEGY == LoadingStrategy.RGB_ALWAYS:
 | 
					            if self.mode == "P" and LOADING_STRATEGY == LoadingStrategy.RGB_ALWAYS:
 | 
				
			||||||
                if self._frame_transparency is not None:
 | 
					                if self._frame_transparency is not None:
 | 
				
			||||||
                    self.im.putpalettealpha(self._frame_transparency, 0)
 | 
					                    self.im.putpalettealpha(self._frame_transparency, 0)
 | 
				
			||||||
                    self.mode = "RGBA"
 | 
					                    self._mode = "RGBA"
 | 
				
			||||||
                else:
 | 
					                else:
 | 
				
			||||||
                    self.mode = "RGB"
 | 
					                    self._mode = "RGB"
 | 
				
			||||||
                self.im = self.im.convert(self.mode, Image.Dither.FLOYDSTEINBERG)
 | 
					                self.im = self.im.convert(self.mode, Image.Dither.FLOYDSTEINBERG)
 | 
				
			||||||
            return
 | 
					            return
 | 
				
			||||||
        if not self._prev_im:
 | 
					        if not self._prev_im:
 | 
				
			||||||
| 
						 | 
					@ -449,7 +449,7 @@ class GifImageFile(ImageFile.ImageFile):
 | 
				
			||||||
        frame_im = self._crop(frame_im, self.dispose_extent)
 | 
					        frame_im = self._crop(frame_im, self.dispose_extent)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.im = self._prev_im
 | 
					        self.im = self._prev_im
 | 
				
			||||||
        self.mode = self.im.mode
 | 
					        self._mode = self.im.mode
 | 
				
			||||||
        if frame_im.mode == "RGBA":
 | 
					        if frame_im.mode == "RGBA":
 | 
				
			||||||
            self.im.paste(frame_im, self.dispose_extent, frame_im)
 | 
					            self.im.paste(frame_im, self.dispose_extent, frame_im)
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
| 
						 | 
					@ -683,11 +683,7 @@ def get_interlace(im):
 | 
				
			||||||
def _write_local_header(fp, im, offset, flags):
 | 
					def _write_local_header(fp, im, offset, flags):
 | 
				
			||||||
    transparent_color_exists = False
 | 
					    transparent_color_exists = False
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        if "transparency" in im.encoderinfo:
 | 
					        transparency = int(im.encoderinfo["transparency"])
 | 
				
			||||||
            transparency = im.encoderinfo["transparency"]
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            transparency = im.info["transparency"]
 | 
					 | 
				
			||||||
        transparency = int(transparency)
 | 
					 | 
				
			||||||
    except (KeyError, ValueError):
 | 
					    except (KeyError, ValueError):
 | 
				
			||||||
        pass
 | 
					        pass
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
| 
						 | 
					@ -916,7 +912,7 @@ def _get_global_header(im, info):
 | 
				
			||||||
        info
 | 
					        info
 | 
				
			||||||
        and (
 | 
					        and (
 | 
				
			||||||
            "transparency" in info
 | 
					            "transparency" in info
 | 
				
			||||||
            or "loop" in info
 | 
					            or info.get("loop") is not None
 | 
				
			||||||
            or info.get("duration")
 | 
					            or info.get("duration")
 | 
				
			||||||
            or info.get("comment")
 | 
					            or info.get("comment")
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
| 
						 | 
					@ -941,7 +937,7 @@ def _get_global_header(im, info):
 | 
				
			||||||
        # Global Color Table
 | 
					        # Global Color Table
 | 
				
			||||||
        _get_header_palette(palette_bytes),
 | 
					        _get_header_palette(palette_bytes),
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
    if "loop" in info:
 | 
					    if info.get("loop") is not None:
 | 
				
			||||||
        header.append(
 | 
					        header.append(
 | 
				
			||||||
            b"!"
 | 
					            b"!"
 | 
				
			||||||
            + o8(255)  # extension intro
 | 
					            + o8(255)  # extension intro
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -46,7 +46,7 @@ class GribStubImageFile(ImageFile.StubImageFile):
 | 
				
			||||||
        self.fp.seek(offset)
 | 
					        self.fp.seek(offset)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # make something up
 | 
					        # make something up
 | 
				
			||||||
        self.mode = "F"
 | 
					        self._mode = "F"
 | 
				
			||||||
        self._size = 1, 1
 | 
					        self._size = 1, 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        loader = self._load()
 | 
					        loader = self._load()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -46,7 +46,7 @@ class HDF5StubImageFile(ImageFile.StubImageFile):
 | 
				
			||||||
        self.fp.seek(offset)
 | 
					        self.fp.seek(offset)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # make something up
 | 
					        # make something up
 | 
				
			||||||
        self.mode = "F"
 | 
					        self._mode = "F"
 | 
				
			||||||
        self._size = 1, 1
 | 
					        self._size = 1, 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        loader = self._load()
 | 
					        loader = self._load()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -253,7 +253,7 @@ class IcnsImageFile(ImageFile.ImageFile):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _open(self):
 | 
					    def _open(self):
 | 
				
			||||||
        self.icns = IcnsFile(self.fp)
 | 
					        self.icns = IcnsFile(self.fp)
 | 
				
			||||||
        self.mode = "RGBA"
 | 
					        self._mode = "RGBA"
 | 
				
			||||||
        self.info["sizes"] = self.icns.itersizes()
 | 
					        self.info["sizes"] = self.icns.itersizes()
 | 
				
			||||||
        self.best_size = self.icns.bestsize()
 | 
					        self.best_size = self.icns.bestsize()
 | 
				
			||||||
        self.size = (
 | 
					        self.size = (
 | 
				
			||||||
| 
						 | 
					@ -305,7 +305,7 @@ class IcnsImageFile(ImageFile.ImageFile):
 | 
				
			||||||
        px = im.load()
 | 
					        px = im.load()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.im = im.im
 | 
					        self.im = im.im
 | 
				
			||||||
        self.mode = im.mode
 | 
					        self._mode = im.mode
 | 
				
			||||||
        self.size = im.size
 | 
					        self.size = im.size
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return px
 | 
					        return px
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -330,7 +330,7 @@ class IcoImageFile(ImageFile.ImageFile):
 | 
				
			||||||
        im.load()
 | 
					        im.load()
 | 
				
			||||||
        self.im = im.im
 | 
					        self.im = im.im
 | 
				
			||||||
        self.pyaccess = None
 | 
					        self.pyaccess = None
 | 
				
			||||||
        self.mode = im.mode
 | 
					        self._mode = im.mode
 | 
				
			||||||
        if im.size != self.size:
 | 
					        if im.size != self.size:
 | 
				
			||||||
            warnings.warn("Image was not the expected size")
 | 
					            warnings.warn("Image was not the expected size")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -205,7 +205,7 @@ class ImImageFile(ImageFile.ImageFile):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Basic attributes
 | 
					        # Basic attributes
 | 
				
			||||||
        self._size = self.info[SIZE]
 | 
					        self._size = self.info[SIZE]
 | 
				
			||||||
        self.mode = self.info[MODE]
 | 
					        self._mode = self.info[MODE]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Skip forward to start of image data
 | 
					        # Skip forward to start of image data
 | 
				
			||||||
        while s and s[:1] != b"\x1A":
 | 
					        while s and s[:1] != b"\x1A":
 | 
				
			||||||
| 
						 | 
					@ -231,9 +231,9 @@ class ImImageFile(ImageFile.ImageFile):
 | 
				
			||||||
                        self.lut = list(palette[:256])
 | 
					                        self.lut = list(palette[:256])
 | 
				
			||||||
                else:
 | 
					                else:
 | 
				
			||||||
                    if self.mode in ["L", "P"]:
 | 
					                    if self.mode in ["L", "P"]:
 | 
				
			||||||
                        self.mode = self.rawmode = "P"
 | 
					                        self._mode = self.rawmode = "P"
 | 
				
			||||||
                    elif self.mode in ["LA", "PA"]:
 | 
					                    elif self.mode in ["LA", "PA"]:
 | 
				
			||||||
                        self.mode = "PA"
 | 
					                        self._mode = "PA"
 | 
				
			||||||
                        self.rawmode = "PA;L"
 | 
					                        self.rawmode = "PA;L"
 | 
				
			||||||
                    self.palette = ImagePalette.raw("RGB;L", palette)
 | 
					                    self.palette = ImagePalette.raw("RGB;L", palette)
 | 
				
			||||||
            elif self.mode == "RGB":
 | 
					            elif self.mode == "RGB":
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -298,7 +298,11 @@ _initialized = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def preinit():
 | 
					def preinit():
 | 
				
			||||||
    """Explicitly load standard file format drivers."""
 | 
					    """
 | 
				
			||||||
 | 
					    Explicitly loads BMP, GIF, JPEG, PPM and PPM file format drivers.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    It is called when opening or saving images.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    global _initialized
 | 
					    global _initialized
 | 
				
			||||||
    if _initialized >= 1:
 | 
					    if _initialized >= 1:
 | 
				
			||||||
| 
						 | 
					@ -334,11 +338,6 @@ def preinit():
 | 
				
			||||||
        assert PngImagePlugin
 | 
					        assert PngImagePlugin
 | 
				
			||||||
    except ImportError:
 | 
					    except ImportError:
 | 
				
			||||||
        pass
 | 
					        pass
 | 
				
			||||||
    # try:
 | 
					 | 
				
			||||||
    #     import TiffImagePlugin
 | 
					 | 
				
			||||||
    #     assert TiffImagePlugin
 | 
					 | 
				
			||||||
    # except ImportError:
 | 
					 | 
				
			||||||
    #     pass
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    _initialized = 1
 | 
					    _initialized = 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -347,6 +346,9 @@ def init():
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    Explicitly initializes the Python Imaging Library. This function
 | 
					    Explicitly initializes the Python Imaging Library. This function
 | 
				
			||||||
    loads all available file format drivers.
 | 
					    loads all available file format drivers.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    It is called when opening or saving images if :py:meth:`~preinit()` is
 | 
				
			||||||
 | 
					    insufficient, and by :py:meth:`~PIL.features.pilinfo`.
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    global _initialized
 | 
					    global _initialized
 | 
				
			||||||
| 
						 | 
					@ -482,7 +484,7 @@ class Image:
 | 
				
			||||||
        # FIXME: take "new" parameters / other image?
 | 
					        # FIXME: take "new" parameters / other image?
 | 
				
			||||||
        # FIXME: turn mode and size into delegating properties?
 | 
					        # FIXME: turn mode and size into delegating properties?
 | 
				
			||||||
        self.im = None
 | 
					        self.im = None
 | 
				
			||||||
        self.mode = ""
 | 
					        self._mode = ""
 | 
				
			||||||
        self._size = (0, 0)
 | 
					        self._size = (0, 0)
 | 
				
			||||||
        self.palette = None
 | 
					        self.palette = None
 | 
				
			||||||
        self.info = {}
 | 
					        self.info = {}
 | 
				
			||||||
| 
						 | 
					@ -502,10 +504,14 @@ class Image:
 | 
				
			||||||
    def size(self):
 | 
					    def size(self):
 | 
				
			||||||
        return self._size
 | 
					        return self._size
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def mode(self):
 | 
				
			||||||
 | 
					        return self._mode
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _new(self, im):
 | 
					    def _new(self, im):
 | 
				
			||||||
        new = Image()
 | 
					        new = Image()
 | 
				
			||||||
        new.im = im
 | 
					        new.im = im
 | 
				
			||||||
        new.mode = im.mode
 | 
					        new._mode = im.mode
 | 
				
			||||||
        new._size = im.size
 | 
					        new._size = im.size
 | 
				
			||||||
        if im.mode in ("P", "PA"):
 | 
					        if im.mode in ("P", "PA"):
 | 
				
			||||||
            if self.palette:
 | 
					            if self.palette:
 | 
				
			||||||
| 
						 | 
					@ -641,9 +647,8 @@ class Image:
 | 
				
			||||||
        b = io.BytesIO()
 | 
					        b = io.BytesIO()
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            self.save(b, image_format, **kwargs)
 | 
					            self.save(b, image_format, **kwargs)
 | 
				
			||||||
        except Exception as e:
 | 
					        except Exception:
 | 
				
			||||||
            msg = f"Could not save to {image_format} for display"
 | 
					            return None
 | 
				
			||||||
            raise ValueError(msg) from e
 | 
					 | 
				
			||||||
        return b.getvalue()
 | 
					        return b.getvalue()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _repr_png_(self):
 | 
					    def _repr_png_(self):
 | 
				
			||||||
| 
						 | 
					@ -693,7 +698,7 @@ class Image:
 | 
				
			||||||
        Image.__init__(self)
 | 
					        Image.__init__(self)
 | 
				
			||||||
        info, mode, size, palette, data = state
 | 
					        info, mode, size, palette, data = state
 | 
				
			||||||
        self.info = info
 | 
					        self.info = info
 | 
				
			||||||
        self.mode = mode
 | 
					        self._mode = mode
 | 
				
			||||||
        self._size = size
 | 
					        self._size = size
 | 
				
			||||||
        self.im = core.new(mode, size)
 | 
					        self.im = core.new(mode, size)
 | 
				
			||||||
        if mode in ("L", "LA", "P", "PA") and palette:
 | 
					        if mode in ("L", "LA", "P", "PA") and palette:
 | 
				
			||||||
| 
						 | 
					@ -910,7 +915,7 @@ class Image:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.load()
 | 
					        self.load()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        has_transparency = self.info.get("transparency") is not None
 | 
					        has_transparency = "transparency" in self.info
 | 
				
			||||||
        if not mode and self.mode == "P":
 | 
					        if not mode and self.mode == "P":
 | 
				
			||||||
            # determine default mode
 | 
					            # determine default mode
 | 
				
			||||||
            if self.palette:
 | 
					            if self.palette:
 | 
				
			||||||
| 
						 | 
					@ -1069,7 +1074,7 @@ class Image:
 | 
				
			||||||
        if mode == "P" and palette != Palette.ADAPTIVE:
 | 
					        if mode == "P" and palette != Palette.ADAPTIVE:
 | 
				
			||||||
            from . import ImagePalette
 | 
					            from . import ImagePalette
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            new_im.palette = ImagePalette.ImagePalette("RGB", list(range(256)) * 3)
 | 
					            new_im.palette = ImagePalette.ImagePalette("RGB", im.getpalette("RGB"))
 | 
				
			||||||
        if delete_trns:
 | 
					        if delete_trns:
 | 
				
			||||||
            # crash fail if we leave a bytes transparency in an rgb/l mode.
 | 
					            # crash fail if we leave a bytes transparency in an rgb/l mode.
 | 
				
			||||||
            del new_im.info["transparency"]
 | 
					            del new_im.info["transparency"]
 | 
				
			||||||
| 
						 | 
					@ -1526,6 +1531,24 @@ class Image:
 | 
				
			||||||
            rawmode = mode
 | 
					            rawmode = mode
 | 
				
			||||||
        return list(self.im.getpalette(mode, rawmode))
 | 
					        return list(self.im.getpalette(mode, rawmode))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def has_transparency_data(self) -> bool:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Determine if an image has transparency data, whether in the form of an
 | 
				
			||||||
 | 
					        alpha channel, a palette with an alpha channel, or a "transparency" key
 | 
				
			||||||
 | 
					        in the info dictionary.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Note the image might still appear solid, if all of the values shown
 | 
				
			||||||
 | 
					        within are opaque.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :returns: A boolean.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        return (
 | 
				
			||||||
 | 
					            self.mode in ("LA", "La", "PA", "RGBA", "RGBa")
 | 
				
			||||||
 | 
					            or (self.mode == "P" and self.palette.mode.endswith("A"))
 | 
				
			||||||
 | 
					            or "transparency" in self.info
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def apply_transparency(self):
 | 
					    def apply_transparency(self):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        If a P mode image has a "transparency" key in the info dictionary,
 | 
					        If a P mode image has a "transparency" key in the info dictionary,
 | 
				
			||||||
| 
						 | 
					@ -1562,7 +1585,7 @@ class Image:
 | 
				
			||||||
        self.load()
 | 
					        self.load()
 | 
				
			||||||
        if self.pyaccess:
 | 
					        if self.pyaccess:
 | 
				
			||||||
            return self.pyaccess.getpixel(xy)
 | 
					            return self.pyaccess.getpixel(xy)
 | 
				
			||||||
        return self.im.getpixel(xy)
 | 
					        return self.im.getpixel(tuple(xy))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def getprojection(self):
 | 
					    def getprojection(self):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
| 
						 | 
					@ -1840,7 +1863,7 @@ class Image:
 | 
				
			||||||
                        raise ValueError from e  # sanity check
 | 
					                        raise ValueError from e  # sanity check
 | 
				
			||||||
                    self.im = im
 | 
					                    self.im = im
 | 
				
			||||||
                self.pyaccess = None
 | 
					                self.pyaccess = None
 | 
				
			||||||
                self.mode = self.im.mode
 | 
					                self._mode = self.im.mode
 | 
				
			||||||
            except KeyError as e:
 | 
					            except KeyError as e:
 | 
				
			||||||
                msg = "illegal image mode"
 | 
					                msg = "illegal image mode"
 | 
				
			||||||
                raise ValueError(msg) from e
 | 
					                raise ValueError(msg) from e
 | 
				
			||||||
| 
						 | 
					@ -1918,7 +1941,7 @@ class Image:
 | 
				
			||||||
            if not isinstance(data, bytes):
 | 
					            if not isinstance(data, bytes):
 | 
				
			||||||
                data = bytes(data)
 | 
					                data = bytes(data)
 | 
				
			||||||
            palette = ImagePalette.raw(rawmode, data)
 | 
					            palette = ImagePalette.raw(rawmode, data)
 | 
				
			||||||
        self.mode = "PA" if "A" in self.mode else "P"
 | 
					        self._mode = "PA" if "A" in self.mode else "P"
 | 
				
			||||||
        self.palette = palette
 | 
					        self.palette = palette
 | 
				
			||||||
        self.palette.mode = "RGB"
 | 
					        self.palette.mode = "RGB"
 | 
				
			||||||
        self.load()  # install new palette
 | 
					        self.load()  # install new palette
 | 
				
			||||||
| 
						 | 
					@ -2026,7 +2049,7 @@ class Image:
 | 
				
			||||||
        mapping_palette = bytearray(new_positions)
 | 
					        mapping_palette = bytearray(new_positions)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        m_im = self.copy()
 | 
					        m_im = self.copy()
 | 
				
			||||||
        m_im.mode = "P"
 | 
					        m_im._mode = "P"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        m_im.palette = ImagePalette.ImagePalette(
 | 
					        m_im.palette = ImagePalette.ImagePalette(
 | 
				
			||||||
            palette_mode, palette=mapping_palette * bands
 | 
					            palette_mode, palette=mapping_palette * bands
 | 
				
			||||||
| 
						 | 
					@ -2601,7 +2624,7 @@ class Image:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            self.im = im.im
 | 
					            self.im = im.im
 | 
				
			||||||
            self._size = size
 | 
					            self._size = size
 | 
				
			||||||
            self.mode = self.im.mode
 | 
					            self._mode = self.im.mode
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.readonly = 0
 | 
					        self.readonly = 0
 | 
				
			||||||
        self.pyaccess = None
 | 
					        self.pyaccess = None
 | 
				
			||||||
| 
						 | 
					@ -2997,7 +3020,7 @@ def frombuffer(mode, size, data, decoder_name="raw", *args):
 | 
				
			||||||
        if args == ():
 | 
					        if args == ():
 | 
				
			||||||
            args = mode, 0, 1
 | 
					            args = mode, 0, 1
 | 
				
			||||||
        if args[0] in _MAPMODES:
 | 
					        if args[0] in _MAPMODES:
 | 
				
			||||||
            im = new(mode, (1, 1))
 | 
					            im = new(mode, (0, 0))
 | 
				
			||||||
            im = im._new(core.map_buffer(data, size, decoder_name, 0, args))
 | 
					            im = im._new(core.map_buffer(data, size, decoder_name, 0, args))
 | 
				
			||||||
            if mode == "P":
 | 
					            if mode == "P":
 | 
				
			||||||
                from . import ImagePalette
 | 
					                from . import ImagePalette
 | 
				
			||||||
| 
						 | 
					@ -3404,8 +3427,12 @@ def register_open(id, factory, accept=None):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def register_mime(id, mimetype):
 | 
					def register_mime(id, mimetype):
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    Registers an image MIME type.  This function should not be used
 | 
					    Registers an image MIME type by populating ``Image.MIME``. This function
 | 
				
			||||||
    in application code.
 | 
					    should not be used in application code.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ``Image.MIME`` provides a mapping from image format identifiers to mime
 | 
				
			||||||
 | 
					    formats, but :py:meth:`~PIL.ImageFile.ImageFile.get_format_mimetype` can
 | 
				
			||||||
 | 
					    provide a different result for specific images.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    :param id: An image format identifier.
 | 
					    :param id: An image format identifier.
 | 
				
			||||||
    :param mimetype: The image MIME type for this format.
 | 
					    :param mimetype: The image MIME type for this format.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -157,7 +157,8 @@ class GaussianBlur(MultibandFilter):
 | 
				
			||||||
    approximates a Gaussian kernel. For details on accuracy see
 | 
					    approximates a Gaussian kernel. For details on accuracy see
 | 
				
			||||||
    <https://www.mia.uni-saarland.de/Publications/gwosdek-ssvm11.pdf>
 | 
					    <https://www.mia.uni-saarland.de/Publications/gwosdek-ssvm11.pdf>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    :param radius: Standard deviation of the Gaussian kernel.
 | 
					    :param radius: Standard deviation of the Gaussian kernel. Either a sequence of two
 | 
				
			||||||
 | 
					                   numbers for x and y, or a single number for both.
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    name = "GaussianBlur"
 | 
					    name = "GaussianBlur"
 | 
				
			||||||
| 
						 | 
					@ -166,7 +167,12 @@ class GaussianBlur(MultibandFilter):
 | 
				
			||||||
        self.radius = radius
 | 
					        self.radius = radius
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def filter(self, image):
 | 
					    def filter(self, image):
 | 
				
			||||||
        return image.gaussian_blur(self.radius)
 | 
					        xy = self.radius
 | 
				
			||||||
 | 
					        if not isinstance(xy, (tuple, list)):
 | 
				
			||||||
 | 
					            xy = (xy, xy)
 | 
				
			||||||
 | 
					        if xy == (0, 0):
 | 
				
			||||||
 | 
					            return image.copy()
 | 
				
			||||||
 | 
					        return image.gaussian_blur(xy)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class BoxBlur(MultibandFilter):
 | 
					class BoxBlur(MultibandFilter):
 | 
				
			||||||
| 
						 | 
					@ -176,21 +182,31 @@ class BoxBlur(MultibandFilter):
 | 
				
			||||||
    which runs in linear time relative to the size of the image
 | 
					    which runs in linear time relative to the size of the image
 | 
				
			||||||
    for any radius value.
 | 
					    for any radius value.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    :param radius: Size of the box in one direction. Radius 0 does not blur,
 | 
					    :param radius: Size of the box in a direction. Either a sequence of two numbers for
 | 
				
			||||||
                   returns an identical image. Radius 1 takes 1 pixel
 | 
					                   x and y, or a single number for both.
 | 
				
			||||||
                   in each direction, i.e. 9 pixels in total.
 | 
					
 | 
				
			||||||
 | 
					                   Radius 0 does not blur, returns an identical image.
 | 
				
			||||||
 | 
					                   Radius 1 takes 1 pixel in each direction, i.e. 9 pixels in total.
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    name = "BoxBlur"
 | 
					    name = "BoxBlur"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, radius):
 | 
					    def __init__(self, radius):
 | 
				
			||||||
        if radius < 0:
 | 
					        xy = radius
 | 
				
			||||||
 | 
					        if not isinstance(xy, (tuple, list)):
 | 
				
			||||||
 | 
					            xy = (xy, xy)
 | 
				
			||||||
 | 
					        if xy[0] < 0 or xy[1] < 0:
 | 
				
			||||||
            msg = "radius must be >= 0"
 | 
					            msg = "radius must be >= 0"
 | 
				
			||||||
            raise ValueError(msg)
 | 
					            raise ValueError(msg)
 | 
				
			||||||
        self.radius = radius
 | 
					        self.radius = radius
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def filter(self, image):
 | 
					    def filter(self, image):
 | 
				
			||||||
        return image.box_blur(self.radius)
 | 
					        xy = self.radius
 | 
				
			||||||
 | 
					        if not isinstance(xy, (tuple, list)):
 | 
				
			||||||
 | 
					            xy = (xy, xy)
 | 
				
			||||||
 | 
					        if xy == (0, 0):
 | 
				
			||||||
 | 
					            return image.copy()
 | 
				
			||||||
 | 
					        return image.box_blur(xy)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class UnsharpMask(MultibandFilter):
 | 
					class UnsharpMask(MultibandFilter):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
		Reference in New Issue
	
	Block a user