Merge branch 'main' into i16n
| 
						 | 
				
			
			@ -21,9 +21,11 @@ environment:
 | 
			
		|||
install:
 | 
			
		||||
- '%PYTHON%\%EXECUTABLE% --version'
 | 
			
		||||
- curl -fsSL -o pillow-depends.zip https://github.com/python-pillow/pillow-depends/archive/main.zip
 | 
			
		||||
- curl -fsSL -o pillow-test-images.zip https://github.com/python-pillow/test-images/archive/main.zip
 | 
			
		||||
- 7z x pillow-depends.zip -oc:\
 | 
			
		||||
- 7z x pillow-test-images.zip -oc:\
 | 
			
		||||
- mv c:\pillow-depends-main c:\pillow-depends
 | 
			
		||||
- xcopy /S /Y c:\pillow-depends\test_images\* c:\pillow\tests\images
 | 
			
		||||
- xcopy /S /Y c:\test-images-main\* c:\pillow\tests\images
 | 
			
		||||
- 7z x ..\pillow-depends\nasm-2.15.05-win64.zip -oc:\
 | 
			
		||||
- ..\pillow-depends\gs1000w32.exe /S
 | 
			
		||||
- path c:\nasm-2.15.05;C:\Program Files (x86)\gs\gs10.0.0\bin;%PATH%
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -37,7 +37,8 @@ python3 -m pip install -U pytest-timeout
 | 
			
		|||
python3 -m pip install pyroma
 | 
			
		||||
 | 
			
		||||
if [[ $(uname) != CYGWIN* ]]; then
 | 
			
		||||
    python3 -m pip install numpy
 | 
			
		||||
    # TODO Remove condition when NumPy supports 3.12
 | 
			
		||||
    if ! [ "$GHA_PYTHON_VERSION" == "3.12-dev" ]; then python3 -m pip install numpy ; fi
 | 
			
		||||
 | 
			
		||||
    # PyQt6 doesn't support PyPy3
 | 
			
		||||
    if [[ $GHA_PYTHON_VERSION == 3.* ]]; then
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										2
									
								
								.github/workflows/cifuzz.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| 
						 | 
				
			
			@ -3,10 +3,12 @@ name: CIFuzz
 | 
			
		|||
on:
 | 
			
		||||
  push:
 | 
			
		||||
    paths:
 | 
			
		||||
      - ".github/workflows/cifuzz.yml"
 | 
			
		||||
      - "**.c"
 | 
			
		||||
      - "**.h"
 | 
			
		||||
  pull_request:
 | 
			
		||||
    paths:
 | 
			
		||||
      - ".github/workflows/cifuzz.yml"
 | 
			
		||||
      - "**.c"
 | 
			
		||||
      - "**.h"
 | 
			
		||||
  workflow_dispatch:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										52
									
								
								.github/workflows/docs.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,52 @@
 | 
			
		|||
name: Docs
 | 
			
		||||
 | 
			
		||||
on:
 | 
			
		||||
  push:
 | 
			
		||||
    paths:
 | 
			
		||||
      - ".github/workflows/docs.yml"
 | 
			
		||||
      - "docs/**"
 | 
			
		||||
  pull_request:
 | 
			
		||||
    paths:
 | 
			
		||||
      - ".github/workflows/docs.yml"
 | 
			
		||||
      - "docs/**"
 | 
			
		||||
  workflow_dispatch:
 | 
			
		||||
 | 
			
		||||
permissions:
 | 
			
		||||
  contents: read
 | 
			
		||||
 | 
			
		||||
concurrency:
 | 
			
		||||
  group: ${{ github.workflow }}-${{ github.ref }}
 | 
			
		||||
  cancel-in-progress: true
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  build:
 | 
			
		||||
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    name: Docs
 | 
			
		||||
 | 
			
		||||
    steps:
 | 
			
		||||
    - uses: actions/checkout@v3
 | 
			
		||||
 | 
			
		||||
    - name: Set up Python
 | 
			
		||||
      uses: actions/setup-python@v4
 | 
			
		||||
      with:
 | 
			
		||||
        python-version: "3.x"
 | 
			
		||||
        cache: pip
 | 
			
		||||
        cache-dependency-path: ".ci/*.sh"
 | 
			
		||||
 | 
			
		||||
    - name: Build system information
 | 
			
		||||
      run: python3 .github/workflows/system-info.py
 | 
			
		||||
 | 
			
		||||
    - name: Install Linux dependencies
 | 
			
		||||
      run: |
 | 
			
		||||
        .ci/install.sh
 | 
			
		||||
      env:
 | 
			
		||||
        GHA_PYTHON_VERSION: "3.x"
 | 
			
		||||
 | 
			
		||||
    - name: Build
 | 
			
		||||
      run: |
 | 
			
		||||
        .ci/build.sh
 | 
			
		||||
 | 
			
		||||
    - name: Docs
 | 
			
		||||
      run: |
 | 
			
		||||
        make doccheck
 | 
			
		||||
							
								
								
									
										3
									
								
								.github/workflows/macos-install.sh
									
									
									
									
										vendored
									
									
								
							
							
						
						| 
						 | 
				
			
			@ -13,7 +13,8 @@ python3 -m pip install -U pytest-cov
 | 
			
		|||
python3 -m pip install -U pytest-timeout
 | 
			
		||||
python3 -m pip install pyroma
 | 
			
		||||
 | 
			
		||||
python3 -m pip install numpy
 | 
			
		||||
# TODO Remove condition when NumPy supports 3.12
 | 
			
		||||
if ! [ "$GHA_PYTHON_VERSION" == "3.12-dev" ]; then python3 -m pip install numpy ; fi
 | 
			
		||||
 | 
			
		||||
# extra test images
 | 
			
		||||
pushd depends && ./install_extra_test_images.sh && popd
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										39
									
								
								.github/workflows/test-cygwin.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| 
						 | 
				
			
			@ -1,6 +1,15 @@
 | 
			
		|||
name: Test Cygwin
 | 
			
		||||
 | 
			
		||||
on: [push, pull_request, workflow_dispatch]
 | 
			
		||||
on:
 | 
			
		||||
  push:
 | 
			
		||||
    paths-ignore:
 | 
			
		||||
      - ".github/workflows/docs.yml"
 | 
			
		||||
      - "docs/**"
 | 
			
		||||
  pull_request:
 | 
			
		||||
    paths-ignore:
 | 
			
		||||
      - ".github/workflows/docs.yml"
 | 
			
		||||
      - "docs/**"
 | 
			
		||||
  workflow_dispatch:
 | 
			
		||||
 | 
			
		||||
permissions:
 | 
			
		||||
  contents: read
 | 
			
		||||
| 
						 | 
				
			
			@ -34,18 +43,34 @@ jobs:
 | 
			
		|||
        with:
 | 
			
		||||
          platform: x86_64
 | 
			
		||||
          packages: >
 | 
			
		||||
            ImageMagick gcc-g++ ghostscript jpeg libfreetype-devel
 | 
			
		||||
            libimagequant-devel libjpeg-devel liblapack-devel
 | 
			
		||||
            liblcms2-devel libopenjp2-devel libraqm-devel
 | 
			
		||||
            libtiff-devel libwebp-devel libxcb-devel libxcb-xinerama0
 | 
			
		||||
            make netpbm perl
 | 
			
		||||
            gcc-g++
 | 
			
		||||
            ghostscript
 | 
			
		||||
            ImageMagick
 | 
			
		||||
            jpeg
 | 
			
		||||
            libfreetype-devel
 | 
			
		||||
            libimagequant-devel
 | 
			
		||||
            libjpeg-devel
 | 
			
		||||
            liblapack-devel
 | 
			
		||||
            liblcms2-devel
 | 
			
		||||
            libopenjp2-devel
 | 
			
		||||
            libraqm-devel
 | 
			
		||||
            libtiff-devel
 | 
			
		||||
            libwebp-devel
 | 
			
		||||
            libxcb-devel
 | 
			
		||||
            libxcb-xinerama0
 | 
			
		||||
            make
 | 
			
		||||
            netpbm
 | 
			
		||||
            perl
 | 
			
		||||
            python3${{ matrix.python-minor-version }}-cffi
 | 
			
		||||
            python3${{ matrix.python-minor-version }}-cython
 | 
			
		||||
            python3${{ matrix.python-minor-version }}-devel
 | 
			
		||||
            python3${{ matrix.python-minor-version }}-numpy
 | 
			
		||||
            python3${{ matrix.python-minor-version }}-sip
 | 
			
		||||
            python3${{ matrix.python-minor-version }}-tkinter
 | 
			
		||||
            qt5-devel-tools subversion xorg-server-extra zlib-devel
 | 
			
		||||
            qt5-devel-tools
 | 
			
		||||
            wget
 | 
			
		||||
            xorg-server-extra
 | 
			
		||||
            zlib-devel
 | 
			
		||||
 | 
			
		||||
      - name: Add Lapack to PATH
 | 
			
		||||
        uses: egor-tensin/cleanup-path@v3
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										12
									
								
								.github/workflows/test-docker.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| 
						 | 
				
			
			@ -1,6 +1,15 @@
 | 
			
		|||
name: Test Docker
 | 
			
		||||
 | 
			
		||||
on: [push, pull_request, workflow_dispatch]
 | 
			
		||||
on:
 | 
			
		||||
  push:
 | 
			
		||||
    paths-ignore:
 | 
			
		||||
      - ".github/workflows/docs.yml"
 | 
			
		||||
      - "docs/**"
 | 
			
		||||
  pull_request:
 | 
			
		||||
    paths-ignore:
 | 
			
		||||
      - ".github/workflows/docs.yml"
 | 
			
		||||
      - "docs/**"
 | 
			
		||||
  workflow_dispatch:
 | 
			
		||||
 | 
			
		||||
permissions:
 | 
			
		||||
  contents: read
 | 
			
		||||
| 
						 | 
				
			
			@ -87,6 +96,7 @@ jobs:
 | 
			
		|||
      with:
 | 
			
		||||
        flags: GHA_Docker
 | 
			
		||||
        name: ${{ matrix.docker }}
 | 
			
		||||
        gcov: true
 | 
			
		||||
 | 
			
		||||
  success:
 | 
			
		||||
    permissions:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										28
									
								
								.github/workflows/test-mingw.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| 
						 | 
				
			
			@ -1,6 +1,15 @@
 | 
			
		|||
name: Test MinGW
 | 
			
		||||
 | 
			
		||||
on: [push, pull_request, workflow_dispatch]
 | 
			
		||||
on:
 | 
			
		||||
  push:
 | 
			
		||||
    paths-ignore:
 | 
			
		||||
      - ".github/workflows/docs.yml"
 | 
			
		||||
      - "docs/**"
 | 
			
		||||
  pull_request:
 | 
			
		||||
    paths-ignore:
 | 
			
		||||
      - ".github/workflows/docs.yml"
 | 
			
		||||
      - "docs/**"
 | 
			
		||||
  workflow_dispatch:
 | 
			
		||||
 | 
			
		||||
permissions:
 | 
			
		||||
  contents: read
 | 
			
		||||
| 
						 | 
				
			
			@ -45,12 +54,6 @@ jobs:
 | 
			
		|||
      - name: Install dependencies
 | 
			
		||||
        run: |
 | 
			
		||||
          pacman -S --noconfirm \
 | 
			
		||||
              ${{ matrix.package }}-python3-cffi \
 | 
			
		||||
              ${{ matrix.package }}-python3-numpy \
 | 
			
		||||
              ${{ matrix.package }}-python3-olefile \
 | 
			
		||||
              ${{ matrix.package }}-python3-pip \
 | 
			
		||||
              ${{ matrix.package }}-python-pyqt6 \
 | 
			
		||||
              ${{ matrix.package }}-python3-setuptools \
 | 
			
		||||
              ${{ matrix.package }}-freetype \
 | 
			
		||||
              ${{ matrix.package }}-gcc \
 | 
			
		||||
              ${{ matrix.package }}-ghostscript \
 | 
			
		||||
| 
						 | 
				
			
			@ -61,7 +64,16 @@ jobs:
 | 
			
		|||
              ${{ matrix.package }}-libtiff \
 | 
			
		||||
              ${{ matrix.package }}-libwebp \
 | 
			
		||||
              ${{ matrix.package }}-openjpeg2 \
 | 
			
		||||
              subversion
 | 
			
		||||
              ${{ matrix.package }}-python3-cffi \
 | 
			
		||||
              ${{ matrix.package }}-python3-numpy \
 | 
			
		||||
              ${{ matrix.package }}-python3-olefile \
 | 
			
		||||
              ${{ matrix.package }}-python3-pip \
 | 
			
		||||
              ${{ matrix.package }}-python3-setuptools
 | 
			
		||||
 | 
			
		||||
          if [ ${{ matrix.package }} == "mingw-w64-x86_64" ]; then
 | 
			
		||||
              pacman -S --noconfirm \
 | 
			
		||||
                  ${{ matrix.package }}-python-pyqt6
 | 
			
		||||
          fi
 | 
			
		||||
 | 
			
		||||
          python3 -m pip install pyroma pytest pytest-cov pytest-timeout
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										4
									
								
								.github/workflows/test-valgrind.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| 
						 | 
				
			
			@ -5,10 +5,12 @@ name: Test Valgrind
 | 
			
		|||
on:
 | 
			
		||||
  push:
 | 
			
		||||
    paths:
 | 
			
		||||
      - ".github/workflows/test-valgrind.yml"
 | 
			
		||||
      - "**.c"
 | 
			
		||||
      - "**.h"
 | 
			
		||||
  pull_request:
 | 
			
		||||
    paths:
 | 
			
		||||
      - ".github/workflows/test-valgrind.yml"
 | 
			
		||||
      - "**.c"
 | 
			
		||||
      - "**.h"
 | 
			
		||||
  workflow_dispatch:
 | 
			
		||||
| 
						 | 
				
			
			@ -48,5 +50,5 @@ jobs:
 | 
			
		|||
      run: |
 | 
			
		||||
        # The Pillow user in the docker container is UID 1000
 | 
			
		||||
        sudo chown -R 1000 $GITHUB_WORKSPACE
 | 
			
		||||
        docker run --name pillow_container  -v $GITHUB_WORKSPACE:/Pillow pythonpillow/${{ matrix.docker }}:${{ matrix.dockerTag }}
 | 
			
		||||
        docker run --name pillow_container -e "PILLOW_VALGRIND_TEST=true" -v $GITHUB_WORKSPACE:/Pillow pythonpillow/${{ matrix.docker }}:${{ matrix.dockerTag }}
 | 
			
		||||
        sudo chown -R runner $GITHUB_WORKSPACE
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										22
									
								
								.github/workflows/test-windows.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| 
						 | 
				
			
			@ -1,6 +1,15 @@
 | 
			
		|||
name: Test Windows
 | 
			
		||||
 | 
			
		||||
on: [push, pull_request, workflow_dispatch]
 | 
			
		||||
on:
 | 
			
		||||
  push:
 | 
			
		||||
    paths-ignore:
 | 
			
		||||
      - ".github/workflows/docs.yml"
 | 
			
		||||
      - "docs/**"
 | 
			
		||||
  pull_request:
 | 
			
		||||
    paths-ignore:
 | 
			
		||||
      - ".github/workflows/docs.yml"
 | 
			
		||||
      - "docs/**"
 | 
			
		||||
  workflow_dispatch:
 | 
			
		||||
 | 
			
		||||
permissions:
 | 
			
		||||
  contents: read
 | 
			
		||||
| 
						 | 
				
			
			@ -15,7 +24,7 @@ jobs:
 | 
			
		|||
    strategy:
 | 
			
		||||
      fail-fast: false
 | 
			
		||||
      matrix:
 | 
			
		||||
        python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
 | 
			
		||||
        python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12-dev"]
 | 
			
		||||
        architecture: ["x86", "x64"]
 | 
			
		||||
        include:
 | 
			
		||||
          # PyPy 7.3.4+ only ships 64-bit binaries for Windows
 | 
			
		||||
| 
						 | 
				
			
			@ -38,6 +47,12 @@ jobs:
 | 
			
		|||
        repository: python-pillow/pillow-depends
 | 
			
		||||
        path: winbuild\depends
 | 
			
		||||
 | 
			
		||||
    - name: Checkout extra test images
 | 
			
		||||
      uses: actions/checkout@v3
 | 
			
		||||
      with:
 | 
			
		||||
        repository: python-pillow/test-images
 | 
			
		||||
        path: Tests\test-images
 | 
			
		||||
 | 
			
		||||
    # sets env: pythonLocation
 | 
			
		||||
    - name: Set up Python
 | 
			
		||||
      uses: actions/setup-python@v4
 | 
			
		||||
| 
						 | 
				
			
			@ -62,7 +77,8 @@ jobs:
 | 
			
		|||
        winbuild\depends\gs1000w32.exe /S
 | 
			
		||||
        echo "C:\Program Files (x86)\gs\gs10.0.0\bin" >> $env:GITHUB_PATH
 | 
			
		||||
 | 
			
		||||
        xcopy /S /Y winbuild\depends\test_images\* Tests\images\
 | 
			
		||||
        # Install extra test images
 | 
			
		||||
        xcopy /S /Y Tests\test-images\* Tests\images
 | 
			
		||||
 | 
			
		||||
        # make cache key depend on VS version
 | 
			
		||||
        & "C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" `
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										19
									
								
								.github/workflows/test.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| 
						 | 
				
			
			@ -1,6 +1,15 @@
 | 
			
		|||
name: Test
 | 
			
		||||
 | 
			
		||||
on: [push, pull_request, workflow_dispatch]
 | 
			
		||||
on:
 | 
			
		||||
  push:
 | 
			
		||||
    paths-ignore:
 | 
			
		||||
      - ".github/workflows/docs.yml"
 | 
			
		||||
      - "docs/**"
 | 
			
		||||
  pull_request:
 | 
			
		||||
    paths-ignore:
 | 
			
		||||
      - ".github/workflows/docs.yml"
 | 
			
		||||
      - "docs/**"
 | 
			
		||||
  workflow_dispatch:
 | 
			
		||||
 | 
			
		||||
permissions:
 | 
			
		||||
  contents: read
 | 
			
		||||
| 
						 | 
				
			
			@ -22,6 +31,7 @@ jobs:
 | 
			
		|||
        python-version: [
 | 
			
		||||
          "pypy3.9",
 | 
			
		||||
          "pypy3.8",
 | 
			
		||||
          "3.12-dev",
 | 
			
		||||
          "3.11",
 | 
			
		||||
          "3.10",
 | 
			
		||||
          "3.9",
 | 
			
		||||
| 
						 | 
				
			
			@ -95,11 +105,6 @@ jobs:
 | 
			
		|||
        name: errors
 | 
			
		||||
        path: Tests/errors
 | 
			
		||||
 | 
			
		||||
    - name: Docs
 | 
			
		||||
      if: startsWith(matrix.os, 'ubuntu') && matrix.python-version == 3.11
 | 
			
		||||
      run: |
 | 
			
		||||
        make doccheck
 | 
			
		||||
 | 
			
		||||
    - name: After success
 | 
			
		||||
      run: |
 | 
			
		||||
        .ci/after_success.sh
 | 
			
		||||
| 
						 | 
				
			
			@ -107,9 +112,9 @@ jobs:
 | 
			
		|||
    - name: Upload coverage
 | 
			
		||||
      uses: codecov/codecov-action@v3
 | 
			
		||||
      with:
 | 
			
		||||
        file: ./coverage.xml
 | 
			
		||||
        flags: ${{ matrix.os == 'macos-latest' && 'GHA_macOS' || 'GHA_Ubuntu' }}
 | 
			
		||||
        name: ${{ matrix.os }} Python ${{ matrix.python-version }}
 | 
			
		||||
        gcov: true
 | 
			
		||||
 | 
			
		||||
  success:
 | 
			
		||||
    permissions:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						| 
						 | 
				
			
			@ -79,7 +79,7 @@ docs/_build/
 | 
			
		|||
# JetBrains
 | 
			
		||||
.idea
 | 
			
		||||
 | 
			
		||||
# Extra test images installed from pillow-depends/test_images
 | 
			
		||||
# Extra test images installed from python-pillow/test-images
 | 
			
		||||
Tests/images/README.md
 | 
			
		||||
Tests/images/crash_1.tif
 | 
			
		||||
Tests/images/crash_2.tif
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
repos:
 | 
			
		||||
  - repo: https://github.com/psf/black
 | 
			
		||||
    rev: 22.12.0
 | 
			
		||||
    rev: 23.1.0
 | 
			
		||||
    hooks:
 | 
			
		||||
      - id: black
 | 
			
		||||
        args: [--target-version=py37]
 | 
			
		||||
| 
						 | 
				
			
			@ -9,7 +9,7 @@ repos:
 | 
			
		|||
        types: []
 | 
			
		||||
 | 
			
		||||
  - repo: https://github.com/PyCQA/isort
 | 
			
		||||
    rev: 5.11.1
 | 
			
		||||
    rev: 5.12.0
 | 
			
		||||
    hooks:
 | 
			
		||||
      - id: isort
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -26,7 +26,7 @@ repos:
 | 
			
		|||
      - id: yesqa
 | 
			
		||||
 | 
			
		||||
  - repo: https://github.com/Lucas-C/pre-commit-hooks
 | 
			
		||||
    rev: v1.3.1
 | 
			
		||||
    rev: v1.4.2
 | 
			
		||||
    hooks:
 | 
			
		||||
      - id: remove-tabs
 | 
			
		||||
        exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.opt$)
 | 
			
		||||
| 
						 | 
				
			
			@ -35,10 +35,11 @@ repos:
 | 
			
		|||
    rev: 6.0.0
 | 
			
		||||
    hooks:
 | 
			
		||||
      - id: flake8
 | 
			
		||||
        additional_dependencies: [flake8-2020, flake8-implicit-str-concat]
 | 
			
		||||
        additional_dependencies:
 | 
			
		||||
          [flake8-2020, flake8-errmsg, flake8-implicit-str-concat]
 | 
			
		||||
 | 
			
		||||
  - repo: https://github.com/pre-commit/pygrep-hooks
 | 
			
		||||
    rev: v1.9.0
 | 
			
		||||
    rev: v1.10.0
 | 
			
		||||
    hooks:
 | 
			
		||||
      - id: python-check-blanket-noqa
 | 
			
		||||
      - id: rst-backticks
 | 
			
		||||
| 
						 | 
				
			
			@ -56,7 +57,7 @@ repos:
 | 
			
		|||
      - id: sphinx-lint
 | 
			
		||||
 | 
			
		||||
  - repo: https://github.com/tox-dev/tox-ini-fmt
 | 
			
		||||
    rev: 0.5.2
 | 
			
		||||
    rev: 0.6.1
 | 
			
		||||
    hooks:
 | 
			
		||||
      - id: tox-ini-fmt
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										104
									
								
								CHANGES.rst
									
									
									
									
									
								
							
							
						
						| 
						 | 
				
			
			@ -2,9 +2,111 @@
 | 
			
		|||
Changelog (Pillow)
 | 
			
		||||
==================
 | 
			
		||||
 | 
			
		||||
9.4.0 (unreleased)
 | 
			
		||||
9.5.0 (unreleased)
 | 
			
		||||
------------------
 | 
			
		||||
 | 
			
		||||
- Added QOI reading #6852
 | 
			
		||||
  [radarhere, hugovk]
 | 
			
		||||
 | 
			
		||||
- Added saving RGBA images as PDFs #6925
 | 
			
		||||
  [radarhere]
 | 
			
		||||
 | 
			
		||||
- Do not raise an error if os.environ does not contain PATH #6935
 | 
			
		||||
  [radarhere, hugovk]
 | 
			
		||||
 | 
			
		||||
- Close OleFileIO instance when closing or exiting FPX or MIC #7005
 | 
			
		||||
  [radarhere]
 | 
			
		||||
 | 
			
		||||
- Added __int__ to IFDRational for Python >= 3.11 #6998
 | 
			
		||||
  [radarhere]
 | 
			
		||||
 | 
			
		||||
- Added memoryview support to Dib.frombytes() #6988
 | 
			
		||||
  [radarhere, nulano]
 | 
			
		||||
 | 
			
		||||
- Close file pointer copy in the libtiff encoder if still open #6986
 | 
			
		||||
  [fcarron, radarhere]
 | 
			
		||||
 | 
			
		||||
- Raise an error if ImageDraw co-ordinates are incorrectly ordered #6978
 | 
			
		||||
  [radarhere]
 | 
			
		||||
 | 
			
		||||
- Added "corners" argument to ImageDraw rounded_rectangle() #6954
 | 
			
		||||
  [radarhere]
 | 
			
		||||
 | 
			
		||||
- Added memoryview support to frombytes() #6974
 | 
			
		||||
  [radarhere]
 | 
			
		||||
 | 
			
		||||
- Allow comments in FITS images #6973
 | 
			
		||||
  [radarhere]
 | 
			
		||||
 | 
			
		||||
- Support saving PDF with different X and Y resolutions #6961
 | 
			
		||||
  [jvanderneutstulen, radarhere, hugovk]
 | 
			
		||||
 | 
			
		||||
- Fixed writing int as UNDEFINED tag #6950
 | 
			
		||||
  [radarhere]
 | 
			
		||||
 | 
			
		||||
- Raise an error if EXIF data is too long when saving JPEG #6939
 | 
			
		||||
  [radarhere]
 | 
			
		||||
 | 
			
		||||
- Handle more than one directory returned by pkg-config #6896
 | 
			
		||||
  [sebastic, radarhere]
 | 
			
		||||
 | 
			
		||||
- Do not retry past formats when loading all formats for the first time #6902
 | 
			
		||||
  [radarhere]
 | 
			
		||||
 | 
			
		||||
- Do not retry specified formats if they failed when opening #6893
 | 
			
		||||
  [radarhere]
 | 
			
		||||
 | 
			
		||||
- Do not unintentionally load TIFF format at first #6892
 | 
			
		||||
  [radarhere]
 | 
			
		||||
 | 
			
		||||
- Stop reading when EPS line becomes too long #6897
 | 
			
		||||
  [radarhere]
 | 
			
		||||
 | 
			
		||||
- Allow writing IFDRational to BYTE tag #6890
 | 
			
		||||
  [radarhere]
 | 
			
		||||
 | 
			
		||||
- Raise ValueError for BoxBlur filter with negative radius #6874
 | 
			
		||||
  [hugovk, radarhere]
 | 
			
		||||
 | 
			
		||||
- Support arbitrary number of loaded modules on Windows #6761
 | 
			
		||||
  [javidcf, radarhere, nulano]
 | 
			
		||||
 | 
			
		||||
9.4.0 (2023-01-02)
 | 
			
		||||
------------------
 | 
			
		||||
 | 
			
		||||
- Fixed null pointer dereference crash with malformed font #6846
 | 
			
		||||
  [wiredfool, radarhere]
 | 
			
		||||
 | 
			
		||||
- Return from ImagingFill early if image has a zero dimension #6842
 | 
			
		||||
  [radarhere]
 | 
			
		||||
 | 
			
		||||
- Reversed deprecations for Image constants, except for duplicate Resampling attributes #6830
 | 
			
		||||
  [radarhere]
 | 
			
		||||
 | 
			
		||||
- Improve exception traceback readability #6836
 | 
			
		||||
  [hugovk, radarhere]
 | 
			
		||||
 | 
			
		||||
- Do not attempt to read IFD1 if absent #6840
 | 
			
		||||
  [radarhere]
 | 
			
		||||
 | 
			
		||||
- Fixed writing int as ASCII tag #6800
 | 
			
		||||
  [radarhere]
 | 
			
		||||
 | 
			
		||||
- If available, use wl-paste or xclip for grabclipboard() on Linux #6783
 | 
			
		||||
  [radarhere]
 | 
			
		||||
 | 
			
		||||
- Added signed option when saving JPEG2000 images #6709
 | 
			
		||||
  [radarhere]
 | 
			
		||||
 | 
			
		||||
- Patch OpenJPEG to include ARM64 fix #6718
 | 
			
		||||
  [radarhere]
 | 
			
		||||
 | 
			
		||||
- Added support for I;16 modes in putdata() #6825
 | 
			
		||||
  [radarhere]
 | 
			
		||||
 | 
			
		||||
- Added conversion from RGBa to RGB #6708
 | 
			
		||||
  [radarhere]
 | 
			
		||||
 | 
			
		||||
- Added DDS support for uncompressed L and LA images #6820
 | 
			
		||||
  [radarhere, REDxEYE]
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										6
									
								
								LICENSE
									
									
									
									
									
								
							
							
						
						| 
						 | 
				
			
			@ -5,7 +5,7 @@ The Python Imaging Library (PIL) is
 | 
			
		|||
 | 
			
		||||
Pillow is the friendly PIL fork. It is
 | 
			
		||||
 | 
			
		||||
    Copyright © 2010-2022 by Alex Clark and contributors
 | 
			
		||||
    Copyright © 2010-2023 by Jeffrey A. Clark (Alex) and contributors.
 | 
			
		||||
 | 
			
		||||
Like PIL, Pillow is licensed under the open source HPND License:
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -13,8 +13,8 @@ By obtaining, using, and/or copying this software and/or its associated
 | 
			
		|||
documentation, you agree that you have read, understood, and will comply
 | 
			
		||||
with the following terms and conditions:
 | 
			
		||||
 | 
			
		||||
Permission to use, copy, modify, and distribute this software and its
 | 
			
		||||
associated documentation for any purpose and without fee is hereby granted,
 | 
			
		||||
Permission to use, copy, modify and distribute this software and its
 | 
			
		||||
documentation for any purpose and without fee is hereby granted,
 | 
			
		||||
provided that the above copyright notice appears in all copies, and that
 | 
			
		||||
both that copyright notice and this permission notice appear in supporting
 | 
			
		||||
documentation, and that the name of Secret Labs AB or the author not be
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,8 +6,8 @@
 | 
			
		|||
 | 
			
		||||
## Python Imaging Library (Fork)
 | 
			
		||||
 | 
			
		||||
Pillow is the friendly PIL fork by [Alex Clark and
 | 
			
		||||
Contributors](https://github.com/python-pillow/Pillow/graphs/contributors).
 | 
			
		||||
Pillow is the friendly PIL fork by [Jeffrey A. Clark (Alex) and
 | 
			
		||||
contributors](https://github.com/python-pillow/Pillow/graphs/contributors).
 | 
			
		||||
PIL is the Python Imaging Library by Fredrik Lundh and Contributors.
 | 
			
		||||
As of 2019, Pillow development is
 | 
			
		||||
[supported by Tidelift](https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=readme&utm_campaign=enterprise).
 | 
			
		||||
| 
						 | 
				
			
			@ -88,6 +88,10 @@ As of 2019, Pillow development is
 | 
			
		|||
            <a href="https://twitter.com/PythonPillow"><img
 | 
			
		||||
                alt="Follow on https://twitter.com/PythonPillow"
 | 
			
		||||
                src="https://img.shields.io/badge/tweet-on%20Twitter-00aced.svg"></a>
 | 
			
		||||
            <a href="https://fosstodon.org/@pillow"><img
 | 
			
		||||
                alt="Follow on https://fosstodon.org/@pillow"
 | 
			
		||||
                src="https://img.shields.io/badge/publish-on%20Mastodon-595aff.svg"
 | 
			
		||||
                rel="me"></a>
 | 
			
		||||
        </td>
 | 
			
		||||
    </tr>
 | 
			
		||||
</table>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -111,7 +111,7 @@ Released as needed privately to individual vendors for critical security-related
 | 
			
		|||
 | 
			
		||||
## Publicize Release
 | 
			
		||||
 | 
			
		||||
* [ ] Announce release availability via [Twitter](https://twitter.com/pythonpillow) e.g. https://twitter.com/PythonPillow/status/1013789184354603010
 | 
			
		||||
* [ ] Announce release availability via [Twitter](https://twitter.com/pythonpillow) and [Mastodon](https://fosstodon.org/@pillow) e.g. https://twitter.com/PythonPillow/status/1013789184354603010
 | 
			
		||||
 | 
			
		||||
## Documentation
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,7 +4,6 @@ TEST_FILE = "Tests/images/fli_overflow.fli"
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
def test_fli_overflow():
 | 
			
		||||
 | 
			
		||||
    # this should not crash with a malloc error or access violation
 | 
			
		||||
    with Image.open(TEST_FILE) as im:
 | 
			
		||||
        im.load()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -23,7 +23,6 @@ def test_ignore_dos_text():
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
def test_dos_text():
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        im = Image.open(TEST_FILE)
 | 
			
		||||
        im.load()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										10
									
								
								Tests/fonts/fuzz_font-5203009437302784
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,10 @@
 | 
			
		|||
STARTFONT
 | 
			
		||||
FONT ÿ
 | 
			
		||||
SIZE 10
 | 
			
		||||
FONTBOUNDINGBOX
 | 
			
		||||
CHARS
 | 
			
		||||
STARTCHAR 
 | 
			
		||||
ENCODING
 | 
			
		||||
BBX 2 5
 | 
			
		||||
ENDCHAR
 | 
			
		||||
ENDFONT
 | 
			
		||||
| 
						 | 
				
			
			@ -20,7 +20,7 @@ logger = logging.getLogger(__name__)
 | 
			
		|||
 | 
			
		||||
HAS_UPLOADER = False
 | 
			
		||||
 | 
			
		||||
if os.environ.get("SHOW_ERRORS", None):
 | 
			
		||||
if os.environ.get("SHOW_ERRORS"):
 | 
			
		||||
    # local img.show for errors.
 | 
			
		||||
    HAS_UPLOADER = True
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -271,7 +271,7 @@ def netpbm_available():
 | 
			
		|||
 | 
			
		||||
def magick_command():
 | 
			
		||||
    if sys.platform == "win32":
 | 
			
		||||
        magickhome = os.environ.get("MAGICK_HOME", "")
 | 
			
		||||
        magickhome = os.environ.get("MAGICK_HOME")
 | 
			
		||||
        if magickhome:
 | 
			
		||||
            imagemagick = [os.path.join(magickhome, "convert.exe")]
 | 
			
		||||
            graphicsmagick = [os.path.join(magickhome, "gm.exe"), "convert"]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										
											BIN
										
									
								
								Tests/images/hopper.qoi
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB  | 
| 
		 Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/imagedraw_rounded_rectangle_corners_nnnn.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 544 B  | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/imagedraw_rounded_rectangle_corners_nnny.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 685 B  | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/imagedraw_rounded_rectangle_corners_nnyn.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 649 B  | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/imagedraw_rounded_rectangle_corners_nnyy.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 755 B  | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/imagedraw_rounded_rectangle_corners_nynn.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 643 B  | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/imagedraw_rounded_rectangle_corners_nyny.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 775 B  | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/imagedraw_rounded_rectangle_corners_nyyn.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 741 B  | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/imagedraw_rounded_rectangle_corners_nyyy.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 844 B  | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/imagedraw_rounded_rectangle_corners_ynnn.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 656 B  | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/imagedraw_rounded_rectangle_corners_ynny.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 785 B  | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/imagedraw_rounded_rectangle_corners_ynyn.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 752 B  | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/imagedraw_rounded_rectangle_corners_ynyy.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 856 B  | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/imagedraw_rounded_rectangle_corners_yynn.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 737 B  | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/imagedraw_rounded_rectangle_corners_yyny.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 870 B  | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/imagedraw_rounded_rectangle_corners_yyyn.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 835 B  | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/imagedraw_rounded_rectangle_corners_yyyy.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 934 B  | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/pil123rgba.qoi
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -57,6 +57,6 @@ def test_fuzz_fonts(path):
 | 
			
		|||
    with open(path, "rb") as f:
 | 
			
		||||
        try:
 | 
			
		||||
            fuzzers.fuzz_font(f.read())
 | 
			
		||||
        except (Image.DecompressionBombError, Image.DecompressionBombWarning):
 | 
			
		||||
        except (Image.DecompressionBombError, Image.DecompressionBombWarning, OSError):
 | 
			
		||||
            pass
 | 
			
		||||
        assert True
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,7 +18,6 @@ def test_bad():
 | 
			
		|||
    """These shouldn't crash/dos, but they shouldn't return anything
 | 
			
		||||
    either"""
 | 
			
		||||
    for f in get_files("b"):
 | 
			
		||||
 | 
			
		||||
        # Assert that there is no unclosed file warning
 | 
			
		||||
        with warnings.catch_warnings():
 | 
			
		||||
            try:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -177,13 +177,14 @@ class TestEnvVars:
 | 
			
		|||
        Image._apply_env_variables({"PILLOW_BLOCK_SIZE": "2m"})
 | 
			
		||||
        assert Image.core.get_block_size() == 2 * 1024 * 1024
 | 
			
		||||
 | 
			
		||||
    def test_warnings(self):
 | 
			
		||||
        pytest.warns(
 | 
			
		||||
            UserWarning, Image._apply_env_variables, {"PILLOW_ALIGNMENT": "15"}
 | 
			
		||||
        )
 | 
			
		||||
        pytest.warns(
 | 
			
		||||
            UserWarning, Image._apply_env_variables, {"PILLOW_BLOCK_SIZE": "1024"}
 | 
			
		||||
        )
 | 
			
		||||
        pytest.warns(
 | 
			
		||||
            UserWarning, Image._apply_env_variables, {"PILLOW_BLOCKS_MAX": "wat"}
 | 
			
		||||
    @pytest.mark.parametrize(
 | 
			
		||||
        "var",
 | 
			
		||||
        (
 | 
			
		||||
            {"PILLOW_ALIGNMENT": "15"},
 | 
			
		||||
            {"PILLOW_BLOCK_SIZE": "1024"},
 | 
			
		||||
            {"PILLOW_BLOCKS_MAX": "wat"},
 | 
			
		||||
        ),
 | 
			
		||||
    )
 | 
			
		||||
    def test_warnings(self, var):
 | 
			
		||||
        with pytest.warns(UserWarning):
 | 
			
		||||
            Image._apply_env_variables(var)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -36,12 +36,10 @@ class TestDecompressionBomb:
 | 
			
		|||
        Image.MAX_IMAGE_PIXELS = 128 * 128 - 1
 | 
			
		||||
        assert Image.MAX_IMAGE_PIXELS == 128 * 128 - 1
 | 
			
		||||
 | 
			
		||||
        def open():
 | 
			
		||||
        with pytest.warns(Image.DecompressionBombWarning):
 | 
			
		||||
            with Image.open(TEST_FILE):
 | 
			
		||||
                pass
 | 
			
		||||
 | 
			
		||||
        pytest.warns(Image.DecompressionBombWarning, open)
 | 
			
		||||
 | 
			
		||||
    def test_exception(self):
 | 
			
		||||
        # Set limit to trigger exception on the test file
 | 
			
		||||
        Image.MAX_IMAGE_PIXELS = 64 * 128 - 1
 | 
			
		||||
| 
						 | 
				
			
			@ -87,7 +85,8 @@ class TestDecompressionCrop:
 | 
			
		|||
        # same decompression bomb warnings on them.
 | 
			
		||||
        with hopper() as src:
 | 
			
		||||
            box = (0, 0, src.width * 2, src.height * 2)
 | 
			
		||||
            pytest.warns(Image.DecompressionBombWarning, src.crop, box)
 | 
			
		||||
            with pytest.warns(Image.DecompressionBombWarning):
 | 
			
		||||
                src.crop(box)
 | 
			
		||||
 | 
			
		||||
    def test_crop_decompression_checks(self):
 | 
			
		||||
        im = Image.new("RGB", (100, 100))
 | 
			
		||||
| 
						 | 
				
			
			@ -95,7 +94,8 @@ class TestDecompressionCrop:
 | 
			
		|||
        for value in ((-9999, -9999, -9990, -9990), (-999, -999, -990, -990)):
 | 
			
		||||
            assert im.crop(value).size == (9, 9)
 | 
			
		||||
 | 
			
		||||
        pytest.warns(Image.DecompressionBombWarning, im.crop, (-160, -160, 99, 99))
 | 
			
		||||
        with pytest.warns(Image.DecompressionBombWarning):
 | 
			
		||||
            im.crop((-160, -160, 99, 99))
 | 
			
		||||
 | 
			
		||||
        with pytest.raises(Image.DecompressionBombError):
 | 
			
		||||
            im.crop((-99909, -99990, 99999, 99999))
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,6 +11,11 @@ from PIL import _deprecate
 | 
			
		|||
            "Old thing is deprecated and will be removed in Pillow 10 "
 | 
			
		||||
            r"\(2023-07-01\)\. Use new thing instead\.",
 | 
			
		||||
        ),
 | 
			
		||||
        (
 | 
			
		||||
            11,
 | 
			
		||||
            "Old thing is deprecated and will be removed in Pillow 11 "
 | 
			
		||||
            r"\(2024-10-15\)\. Use new thing instead\.",
 | 
			
		||||
        ),
 | 
			
		||||
        (
 | 
			
		||||
            None,
 | 
			
		||||
            r"Old thing is deprecated and will be removed in a future version\. "
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -263,13 +263,11 @@ def test_apng_chunk_errors():
 | 
			
		|||
    with Image.open("Tests/images/apng/chunk_no_actl.png") as im:
 | 
			
		||||
        assert not im.is_animated
 | 
			
		||||
 | 
			
		||||
    def open():
 | 
			
		||||
    with pytest.warns(UserWarning):
 | 
			
		||||
        with Image.open("Tests/images/apng/chunk_multi_actl.png") as im:
 | 
			
		||||
            im.load()
 | 
			
		||||
        assert not im.is_animated
 | 
			
		||||
 | 
			
		||||
    pytest.warns(UserWarning, open)
 | 
			
		||||
 | 
			
		||||
    with Image.open("Tests/images/apng/chunk_actl_after_idat.png") as im:
 | 
			
		||||
        assert not im.is_animated
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -287,21 +285,17 @@ def test_apng_chunk_errors():
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
def test_apng_syntax_errors():
 | 
			
		||||
    def open_frames_zero():
 | 
			
		||||
    with pytest.warns(UserWarning):
 | 
			
		||||
        with Image.open("Tests/images/apng/syntax_num_frames_zero.png") as im:
 | 
			
		||||
            assert not im.is_animated
 | 
			
		||||
            with pytest.raises(OSError):
 | 
			
		||||
                im.load()
 | 
			
		||||
 | 
			
		||||
    pytest.warns(UserWarning, open_frames_zero)
 | 
			
		||||
 | 
			
		||||
    def open_frames_zero_default():
 | 
			
		||||
    with pytest.warns(UserWarning):
 | 
			
		||||
        with Image.open("Tests/images/apng/syntax_num_frames_zero_default.png") as im:
 | 
			
		||||
            assert not im.is_animated
 | 
			
		||||
            im.load()
 | 
			
		||||
 | 
			
		||||
    pytest.warns(UserWarning, open_frames_zero_default)
 | 
			
		||||
 | 
			
		||||
    # we can handle this case gracefully
 | 
			
		||||
    exception = None
 | 
			
		||||
    with Image.open("Tests/images/apng/syntax_num_frames_low.png") as im:
 | 
			
		||||
| 
						 | 
				
			
			@ -316,13 +310,11 @@ def test_apng_syntax_errors():
 | 
			
		|||
            im.seek(im.n_frames - 1)
 | 
			
		||||
            im.load()
 | 
			
		||||
 | 
			
		||||
    def open():
 | 
			
		||||
    with pytest.warns(UserWarning):
 | 
			
		||||
        with Image.open("Tests/images/apng/syntax_num_frames_invalid.png") as im:
 | 
			
		||||
            assert not im.is_animated
 | 
			
		||||
            im.load()
 | 
			
		||||
 | 
			
		||||
    pytest.warns(UserWarning, open)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.mark.parametrize(
 | 
			
		||||
    "test_file",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -141,7 +141,6 @@ def test_rgba_bitfields():
 | 
			
		|||
    # This test image has been manually hexedited
 | 
			
		||||
    # to change the bitfield compression in the header from XBGR to RGBA
 | 
			
		||||
    with Image.open("Tests/images/rgb32bf-rgba.bmp") as im:
 | 
			
		||||
 | 
			
		||||
        # So before the comparing the image, swap the channels
 | 
			
		||||
        b, g, r = im.split()[1:]
 | 
			
		||||
        im = Image.merge("RGB", (r, g, b))
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,7 +10,6 @@ TEST_FILE = "Tests/images/gfs.t06z.rassda.tm00.bufr_d"
 | 
			
		|||
def test_open():
 | 
			
		||||
    # Act
 | 
			
		||||
    with Image.open(TEST_FILE) as im:
 | 
			
		||||
 | 
			
		||||
        # Assert
 | 
			
		||||
        assert im.format == "BUFR"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -31,7 +30,6 @@ def test_invalid_file():
 | 
			
		|||
def test_load():
 | 
			
		||||
    # Arrange
 | 
			
		||||
    with Image.open(TEST_FILE) as im:
 | 
			
		||||
 | 
			
		||||
        # Act / Assert: stub cannot load without an implemented handler
 | 
			
		||||
        with pytest.raises(OSError):
 | 
			
		||||
            im.load()
 | 
			
		||||
| 
						 | 
				
			
			@ -58,6 +56,7 @@ def test_handler(tmp_path):
 | 
			
		|||
 | 
			
		||||
        def load(self, im):
 | 
			
		||||
            self.loaded = True
 | 
			
		||||
            im.fp.close()
 | 
			
		||||
            return Image.new("RGB", (1, 1))
 | 
			
		||||
 | 
			
		||||
        def save(self, im, fp, filename):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,7 +15,6 @@ def test_sanity():
 | 
			
		|||
 | 
			
		||||
    # Act
 | 
			
		||||
    with Image.open(TEST_FILE) as im:
 | 
			
		||||
 | 
			
		||||
        # Assert
 | 
			
		||||
        assert im.size == (128, 128)
 | 
			
		||||
        assert isinstance(im, DcxImagePlugin.DcxImageFile)
 | 
			
		||||
| 
						 | 
				
			
			@ -29,7 +28,8 @@ def test_unclosed_file():
 | 
			
		|||
        im = Image.open(TEST_FILE)
 | 
			
		||||
        im.load()
 | 
			
		||||
 | 
			
		||||
    pytest.warns(ResourceWarning, open)
 | 
			
		||||
    with pytest.warns(ResourceWarning):
 | 
			
		||||
        open()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_closed_file():
 | 
			
		||||
| 
						 | 
				
			
			@ -54,7 +54,6 @@ def test_invalid_file():
 | 
			
		|||
def test_tell():
 | 
			
		||||
    # Arrange
 | 
			
		||||
    with Image.open(TEST_FILE) as im:
 | 
			
		||||
 | 
			
		||||
        # Act
 | 
			
		||||
        frame = im.tell()
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -80,7 +80,6 @@ def test_invalid_file():
 | 
			
		|||
@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")
 | 
			
		||||
def test_cmyk():
 | 
			
		||||
    with Image.open("Tests/images/pil_sample_cmyk.eps") as cmyk_image:
 | 
			
		||||
 | 
			
		||||
        assert cmyk_image.mode == "CMYK"
 | 
			
		||||
        assert cmyk_image.size == (100, 100)
 | 
			
		||||
        assert cmyk_image.format == "EPS"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,7 +12,6 @@ TEST_FILE = "Tests/images/hopper.fits"
 | 
			
		|||
def test_open():
 | 
			
		||||
    # Act
 | 
			
		||||
    with Image.open(TEST_FILE) as im:
 | 
			
		||||
 | 
			
		||||
        # Assert
 | 
			
		||||
        assert im.format == "FITS"
 | 
			
		||||
        assert im.size == (128, 128)
 | 
			
		||||
| 
						 | 
				
			
			@ -45,6 +44,12 @@ def test_naxis_zero():
 | 
			
		|||
            pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_comment():
 | 
			
		||||
    image_data = b"SIMPLE  =                    T / comment string"
 | 
			
		||||
    with pytest.raises(OSError):
 | 
			
		||||
        FitsImagePlugin.FitsImageFile(BytesIO(image_data))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_stub_deprecated():
 | 
			
		||||
    class Handler:
 | 
			
		||||
        opened = False
 | 
			
		||||
| 
						 | 
				
			
			@ -55,6 +60,7 @@ def test_stub_deprecated():
 | 
			
		|||
 | 
			
		||||
        def load(self, im):
 | 
			
		||||
            self.loaded = True
 | 
			
		||||
            im.fp.close()
 | 
			
		||||
            return Image.new("RGB", (1, 1))
 | 
			
		||||
 | 
			
		||||
    handler = Handler()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -36,7 +36,8 @@ def test_unclosed_file():
 | 
			
		|||
        im = Image.open(static_test_file)
 | 
			
		||||
        im.load()
 | 
			
		||||
 | 
			
		||||
    pytest.warns(ResourceWarning, open)
 | 
			
		||||
    with pytest.warns(ResourceWarning):
 | 
			
		||||
        open()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_closed_file():
 | 
			
		||||
| 
						 | 
				
			
			@ -64,7 +65,6 @@ def test_context_manager():
 | 
			
		|||
def test_tell():
 | 
			
		||||
    # Arrange
 | 
			
		||||
    with Image.open(static_test_file) as im:
 | 
			
		||||
 | 
			
		||||
        # Act
 | 
			
		||||
        frame = im.tell()
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -110,7 +110,6 @@ def test_eoferror():
 | 
			
		|||
 | 
			
		||||
def test_seek_tell():
 | 
			
		||||
    with Image.open(animated_test_file) as im:
 | 
			
		||||
 | 
			
		||||
        layer_number = im.tell()
 | 
			
		||||
        assert layer_number == 0
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,6 +18,16 @@ def test_sanity():
 | 
			
		|||
        assert_image_equal_tofile(im, "Tests/images/input_bw_one_band.png")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_close():
 | 
			
		||||
    with Image.open("Tests/images/input_bw_one_band.fpx") as im:
 | 
			
		||||
        pass
 | 
			
		||||
    assert im.ole.fp.closed
 | 
			
		||||
 | 
			
		||||
    im = Image.open("Tests/images/input_bw_one_band.fpx")
 | 
			
		||||
    im.close()
 | 
			
		||||
    assert im.ole.fp.closed
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_invalid_file():
 | 
			
		||||
    # Test an invalid OLE file
 | 
			
		||||
    invalid_file = "Tests/images/flower.jpg"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -36,7 +36,8 @@ def test_unclosed_file():
 | 
			
		|||
        im = Image.open(TEST_GIF)
 | 
			
		||||
        im.load()
 | 
			
		||||
 | 
			
		||||
    pytest.warns(ResourceWarning, open)
 | 
			
		||||
    with pytest.warns(ResourceWarning):
 | 
			
		||||
        open()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_closed_file():
 | 
			
		||||
| 
						 | 
				
			
			@ -158,12 +159,28 @@ def test_optimize():
 | 
			
		|||
    assert test_bilevel(1) == 799
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_optimize_correctness():
 | 
			
		||||
    # 256 color Palette image, posterize to > 128 and < 128 levels
 | 
			
		||||
    # Size bigger and smaller than 512x512
 | 
			
		||||
@pytest.mark.parametrize(
 | 
			
		||||
    "colors, size, expected_palette_length",
 | 
			
		||||
    (
 | 
			
		||||
        # These do optimize the palette
 | 
			
		||||
        (256, 511, 256),
 | 
			
		||||
        (255, 511, 255),
 | 
			
		||||
        (129, 511, 129),
 | 
			
		||||
        (128, 511, 128),
 | 
			
		||||
        (64, 511, 64),
 | 
			
		||||
        (4, 511, 4),
 | 
			
		||||
        # These don't optimize the palette
 | 
			
		||||
        (128, 513, 256),
 | 
			
		||||
        (64, 513, 256),
 | 
			
		||||
        (4, 513, 256),
 | 
			
		||||
    ),
 | 
			
		||||
)
 | 
			
		||||
def test_optimize_correctness(colors, size, expected_palette_length):
 | 
			
		||||
    # 256 color Palette image, posterize to > 128 and < 128 levels.
 | 
			
		||||
    # Size bigger and smaller than 512x512.
 | 
			
		||||
    # Check the palette for number of colors allocated.
 | 
			
		||||
    # Check for correctness after conversion back to RGB
 | 
			
		||||
    def check(colors, size, expected_palette_length):
 | 
			
		||||
    # Check for correctness after conversion back to RGB.
 | 
			
		||||
 | 
			
		||||
    # make an image with empty colors in the start of the palette range
 | 
			
		||||
    im = Image.frombytes(
 | 
			
		||||
        "P", (colors, colors), bytes(range(256 - colors, 256)) * colors
 | 
			
		||||
| 
						 | 
				
			
			@ -179,19 +196,6 @@ def test_optimize_correctness():
 | 
			
		|||
 | 
			
		||||
        assert_image_equal(im.convert("RGB"), reloaded.convert("RGB"))
 | 
			
		||||
 | 
			
		||||
    # These do optimize the palette
 | 
			
		||||
    check(256, 511, 256)
 | 
			
		||||
    check(255, 511, 255)
 | 
			
		||||
    check(129, 511, 129)
 | 
			
		||||
    check(128, 511, 128)
 | 
			
		||||
    check(64, 511, 64)
 | 
			
		||||
    check(4, 511, 4)
 | 
			
		||||
 | 
			
		||||
    # These don't optimize the palette
 | 
			
		||||
    check(128, 513, 256)
 | 
			
		||||
    check(64, 513, 256)
 | 
			
		||||
    check(4, 513, 256)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_optimize_full_l():
 | 
			
		||||
    im = Image.frombytes("L", (16, 16), bytes(range(256)))
 | 
			
		||||
| 
						 | 
				
			
			@ -206,7 +210,7 @@ def test_optimize_if_palette_can_be_reduced_by_half():
 | 
			
		|||
        im = im.resize((591, 443))
 | 
			
		||||
    im_rgb = im.convert("RGB")
 | 
			
		||||
 | 
			
		||||
    for (optimize, colors) in ((False, 256), (True, 8)):
 | 
			
		||||
    for optimize, colors in ((False, 256), (True, 8)):
 | 
			
		||||
        out = BytesIO()
 | 
			
		||||
        im_rgb.save(out, "GIF", optimize=optimize)
 | 
			
		||||
        with Image.open(out) as reloaded:
 | 
			
		||||
| 
						 | 
				
			
			@ -218,7 +222,6 @@ def test_roundtrip(tmp_path):
 | 
			
		|||
    im = hopper()
 | 
			
		||||
    im.save(out)
 | 
			
		||||
    with Image.open(out) as reread:
 | 
			
		||||
 | 
			
		||||
        assert_image_similar(reread.convert("RGB"), im, 50)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -229,7 +232,6 @@ def test_roundtrip2(tmp_path):
 | 
			
		|||
        im2 = im.copy()
 | 
			
		||||
        im2.save(out)
 | 
			
		||||
    with Image.open(out) as reread:
 | 
			
		||||
 | 
			
		||||
        assert_image_similar(reread.convert("RGB"), hopper(), 50)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -239,7 +241,6 @@ def test_roundtrip_save_all(tmp_path):
 | 
			
		|||
    im = hopper()
 | 
			
		||||
    im.save(out, save_all=True)
 | 
			
		||||
    with Image.open(out) as reread:
 | 
			
		||||
 | 
			
		||||
        assert_image_similar(reread.convert("RGB"), im, 50)
 | 
			
		||||
 | 
			
		||||
    # Multiframe image
 | 
			
		||||
| 
						 | 
				
			
			@ -281,13 +282,11 @@ def test_headers_saving_for_animated_gifs(tmp_path):
 | 
			
		|||
    important_headers = ["background", "version", "duration", "loop"]
 | 
			
		||||
    # Multiframe image
 | 
			
		||||
    with Image.open("Tests/images/dispose_bgnd.gif") as im:
 | 
			
		||||
 | 
			
		||||
        info = im.info.copy()
 | 
			
		||||
 | 
			
		||||
        out = str(tmp_path / "temp.gif")
 | 
			
		||||
        im.save(out, save_all=True)
 | 
			
		||||
    with Image.open(out) as reread:
 | 
			
		||||
 | 
			
		||||
        for header in important_headers:
 | 
			
		||||
            assert info[header] == reread.info[header]
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -305,7 +304,6 @@ def test_palette_handling(tmp_path):
 | 
			
		|||
        im2.save(f, optimize=True)
 | 
			
		||||
 | 
			
		||||
    with Image.open(f) as reloaded:
 | 
			
		||||
 | 
			
		||||
        assert_image_similar(im, reloaded.convert("RGB"), 10)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -321,7 +319,6 @@ def test_palette_434(tmp_path):
 | 
			
		|||
 | 
			
		||||
    orig = "Tests/images/test.colors.gif"
 | 
			
		||||
    with Image.open(orig) as im:
 | 
			
		||||
 | 
			
		||||
        with roundtrip(im) as reloaded:
 | 
			
		||||
            assert_image_similar(im, reloaded, 1)
 | 
			
		||||
        with roundtrip(im, optimize=True) as reloaded:
 | 
			
		||||
| 
						 | 
				
			
			@ -572,7 +569,6 @@ def test_save_dispose(tmp_path):
 | 
			
		|||
    )
 | 
			
		||||
 | 
			
		||||
    with Image.open(out) as img:
 | 
			
		||||
 | 
			
		||||
        for i in range(2):
 | 
			
		||||
            img.seek(img.tell() + 1)
 | 
			
		||||
            assert img.disposal_method == i + 1
 | 
			
		||||
| 
						 | 
				
			
			@ -770,7 +766,6 @@ def test_multiple_duration(tmp_path):
 | 
			
		|||
        out, save_all=True, append_images=im_list[1:], duration=duration_list
 | 
			
		||||
    )
 | 
			
		||||
    with Image.open(out) as reread:
 | 
			
		||||
 | 
			
		||||
        for duration in duration_list:
 | 
			
		||||
            assert reread.info["duration"] == duration
 | 
			
		||||
            try:
 | 
			
		||||
| 
						 | 
				
			
			@ -783,7 +778,6 @@ def test_multiple_duration(tmp_path):
 | 
			
		|||
        out, save_all=True, append_images=im_list[1:], duration=tuple(duration_list)
 | 
			
		||||
    )
 | 
			
		||||
    with Image.open(out) as reread:
 | 
			
		||||
 | 
			
		||||
        for duration in duration_list:
 | 
			
		||||
            assert reread.info["duration"] == duration
 | 
			
		||||
            try:
 | 
			
		||||
| 
						 | 
				
			
			@ -841,7 +835,6 @@ def test_identical_frames(tmp_path):
 | 
			
		|||
        out, save_all=True, append_images=im_list[1:], duration=duration_list
 | 
			
		||||
    )
 | 
			
		||||
    with Image.open(out) as reread:
 | 
			
		||||
 | 
			
		||||
        # Assert that the first three frames were combined
 | 
			
		||||
        assert reread.n_frames == 2
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1095,7 +1088,8 @@ def test_rgb_transparency(tmp_path):
 | 
			
		|||
    im = Image.new("RGB", (1, 1))
 | 
			
		||||
    im.info["transparency"] = b""
 | 
			
		||||
    ims = [Image.new("RGB", (1, 1))]
 | 
			
		||||
    pytest.warns(UserWarning, im.save, out, save_all=True, append_images=ims)
 | 
			
		||||
    with pytest.warns(UserWarning):
 | 
			
		||||
        im.save(out, save_all=True, append_images=ims)
 | 
			
		||||
 | 
			
		||||
    with Image.open(out) as reloaded:
 | 
			
		||||
        assert "transparency" not in reloaded.info
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,7 +10,6 @@ TEST_FILE = "Tests/images/WAlaska.wind.7days.grb"
 | 
			
		|||
def test_open():
 | 
			
		||||
    # Act
 | 
			
		||||
    with Image.open(TEST_FILE) as im:
 | 
			
		||||
 | 
			
		||||
        # Assert
 | 
			
		||||
        assert im.format == "GRIB"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -31,7 +30,6 @@ def test_invalid_file():
 | 
			
		|||
def test_load():
 | 
			
		||||
    # Arrange
 | 
			
		||||
    with Image.open(TEST_FILE) as im:
 | 
			
		||||
 | 
			
		||||
        # Act / Assert: stub cannot load without an implemented handler
 | 
			
		||||
        with pytest.raises(OSError):
 | 
			
		||||
            im.load()
 | 
			
		||||
| 
						 | 
				
			
			@ -58,6 +56,7 @@ def test_handler(tmp_path):
 | 
			
		|||
 | 
			
		||||
        def load(self, im):
 | 
			
		||||
            self.loaded = True
 | 
			
		||||
            im.fp.close()
 | 
			
		||||
            return Image.new("RGB", (1, 1))
 | 
			
		||||
 | 
			
		||||
        def save(self, im, fp, filename):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,7 +8,6 @@ TEST_FILE = "Tests/images/hdf5.h5"
 | 
			
		|||
def test_open():
 | 
			
		||||
    # Act
 | 
			
		||||
    with Image.open(TEST_FILE) as im:
 | 
			
		||||
 | 
			
		||||
        # Assert
 | 
			
		||||
        assert im.format == "HDF5"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -29,7 +28,6 @@ def test_invalid_file():
 | 
			
		|||
def test_load():
 | 
			
		||||
    # Arrange
 | 
			
		||||
    with Image.open(TEST_FILE) as im:
 | 
			
		||||
 | 
			
		||||
        # Act / Assert: stub cannot load without an implemented handler
 | 
			
		||||
        with pytest.raises(OSError):
 | 
			
		||||
            im.load()
 | 
			
		||||
| 
						 | 
				
			
			@ -59,6 +57,7 @@ def test_handler(tmp_path):
 | 
			
		|||
 | 
			
		||||
        def load(self, im):
 | 
			
		||||
            self.loaded = True
 | 
			
		||||
            im.fp.close()
 | 
			
		||||
            return Image.new("RGB", (1, 1))
 | 
			
		||||
 | 
			
		||||
        def save(self, im, fp, filename):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,7 +16,6 @@ def test_sanity():
 | 
			
		|||
    # Loading this icon by default should result in the largest size
 | 
			
		||||
    # (512x512@2x) being loaded
 | 
			
		||||
    with Image.open(TEST_FILE) as im:
 | 
			
		||||
 | 
			
		||||
        # Assert that there is no unclosed file warning
 | 
			
		||||
        with warnings.catch_warnings():
 | 
			
		||||
            im.load()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -175,7 +175,6 @@ def test_save_256x256(tmp_path):
 | 
			
		|||
        # Act
 | 
			
		||||
        im.save(outfile)
 | 
			
		||||
    with Image.open(outfile) as im_saved:
 | 
			
		||||
 | 
			
		||||
        # Assert
 | 
			
		||||
        assert im_saved.size == (256, 256)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -213,12 +212,10 @@ def test_save_append_images(tmp_path):
 | 
			
		|||
def test_unexpected_size():
 | 
			
		||||
    # This image has been manually hexedited to state that it is 16x32
 | 
			
		||||
    # while the image within is still 16x16
 | 
			
		||||
    def open():
 | 
			
		||||
    with pytest.warns(UserWarning):
 | 
			
		||||
        with Image.open("Tests/images/hopper_unexpected.ico") as im:
 | 
			
		||||
            assert im.size == (16, 16)
 | 
			
		||||
 | 
			
		||||
    pytest.warns(UserWarning, open)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_draw_reloaded(tmp_path):
 | 
			
		||||
    with Image.open(TEST_ICO_FILE) as im:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -32,7 +32,8 @@ def test_unclosed_file():
 | 
			
		|||
        im = Image.open(TEST_IM)
 | 
			
		||||
        im.load()
 | 
			
		||||
 | 
			
		||||
    pytest.warns(ResourceWarning, open)
 | 
			
		||||
    with pytest.warns(ResourceWarning):
 | 
			
		||||
        open()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_closed_file():
 | 
			
		||||
| 
						 | 
				
			
			@ -51,7 +52,6 @@ def test_context_manager():
 | 
			
		|||
def test_tell():
 | 
			
		||||
    # Arrange
 | 
			
		||||
    with Image.open(TEST_IM) as im:
 | 
			
		||||
 | 
			
		||||
        # Act
 | 
			
		||||
        frame = im.tell()
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,7 +11,6 @@ TEST_FILE = "Tests/images/iptc.jpg"
 | 
			
		|||
def test_getiptcinfo_jpg_none():
 | 
			
		||||
    # Arrange
 | 
			
		||||
    with hopper() as im:
 | 
			
		||||
 | 
			
		||||
        # Act
 | 
			
		||||
        iptc = IptcImagePlugin.getiptcinfo(im)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -22,7 +21,6 @@ def test_getiptcinfo_jpg_none():
 | 
			
		|||
def test_getiptcinfo_jpg_found():
 | 
			
		||||
    # Arrange
 | 
			
		||||
    with Image.open(TEST_FILE) as im:
 | 
			
		||||
 | 
			
		||||
        # Act
 | 
			
		||||
        iptc = IptcImagePlugin.getiptcinfo(im)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -35,7 +33,6 @@ def test_getiptcinfo_jpg_found():
 | 
			
		|||
def test_getiptcinfo_tiff_none():
 | 
			
		||||
    # Arrange
 | 
			
		||||
    with Image.open("Tests/images/hopper.tif") as im:
 | 
			
		||||
 | 
			
		||||
        # Act
 | 
			
		||||
        iptc = IptcImagePlugin.getiptcinfo(im)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -57,7 +57,6 @@ class TestFileJpeg:
 | 
			
		|||
        return Image.frombytes(mode, size, os.urandom(size[0] * size[1] * len(mode)))
 | 
			
		||||
 | 
			
		||||
    def test_sanity(self):
 | 
			
		||||
 | 
			
		||||
        # internal version number
 | 
			
		||||
        assert re.search(r"\d+\.\d+$", features.version_codec("jpg"))
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -271,7 +270,10 @@ class TestFileJpeg:
 | 
			
		|||
        # https://github.com/python-pillow/Pillow/issues/148
 | 
			
		||||
        f = str(tmp_path / "temp.jpg")
 | 
			
		||||
        im = hopper()
 | 
			
		||||
        im.save(f, "JPEG", quality=90, exif=b"1" * 65532)
 | 
			
		||||
        im.save(f, "JPEG", quality=90, exif=b"1" * 65533)
 | 
			
		||||
 | 
			
		||||
        with pytest.raises(ValueError):
 | 
			
		||||
            im.save(f, "JPEG", quality=90, exif=b"1" * 65534)
 | 
			
		||||
 | 
			
		||||
    def test_exif_typeerror(self):
 | 
			
		||||
        with Image.open("Tests/images/exif_typeerror.jpg") as im:
 | 
			
		||||
| 
						 | 
				
			
			@ -368,7 +370,6 @@ class TestFileJpeg:
 | 
			
		|||
 | 
			
		||||
    def test_exif_gps_typeerror(self):
 | 
			
		||||
        with Image.open("Tests/images/exif_gps_typeerror.jpg") as im:
 | 
			
		||||
 | 
			
		||||
            # Should not raise a TypeError
 | 
			
		||||
            im._getexif()
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -447,7 +448,7 @@ class TestFileJpeg:
 | 
			
		|||
            ims = im.get_child_images()
 | 
			
		||||
 | 
			
		||||
        assert len(ims) == 1
 | 
			
		||||
        assert_image_equal_tofile(ims[0], "Tests/images/flower_thumbnail.png")
 | 
			
		||||
        assert_image_similar_tofile(ims[0], "Tests/images/flower_thumbnail.png", 2.1)
 | 
			
		||||
 | 
			
		||||
    def test_mp(self):
 | 
			
		||||
        with Image.open("Tests/images/pil_sample_rgb.jpg") as im:
 | 
			
		||||
| 
						 | 
				
			
			@ -682,7 +683,6 @@ class TestFileJpeg:
 | 
			
		|||
        # Shouldn't raise error
 | 
			
		||||
        fn = "Tests/images/sugarshack_bad_mpo_header.jpg"
 | 
			
		||||
        with pytest.warns(UserWarning, Image.open, fn) as im:
 | 
			
		||||
 | 
			
		||||
            # Assert
 | 
			
		||||
            assert im.format == "JPEG"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -704,7 +704,6 @@ class TestFileJpeg:
 | 
			
		|||
        # Arrange
 | 
			
		||||
        outfile = str(tmp_path / "temp.tif")
 | 
			
		||||
        with Image.open("Tests/images/hopper.tif") as im:
 | 
			
		||||
 | 
			
		||||
            # Act
 | 
			
		||||
            im.save(outfile, "JPEG", dpi=im.info["dpi"])
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -731,7 +730,6 @@ class TestFileJpeg:
 | 
			
		|||
        # This Photoshop CC 2017 image has DPI in EXIF not metadata
 | 
			
		||||
        # EXIF XResolution is (2000000, 10000)
 | 
			
		||||
        with Image.open("Tests/images/photoshop-200dpi.jpg") as im:
 | 
			
		||||
 | 
			
		||||
            # Act / Assert
 | 
			
		||||
            assert im.info.get("dpi") == (200, 200)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -740,7 +738,6 @@ class TestFileJpeg:
 | 
			
		|||
        # This image has DPI in EXIF not metadata
 | 
			
		||||
        # EXIF XResolution is 72
 | 
			
		||||
        with Image.open("Tests/images/exif-72dpi-int.jpg") as im:
 | 
			
		||||
 | 
			
		||||
            # Act / Assert
 | 
			
		||||
            assert im.info.get("dpi") == (72, 72)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -749,7 +746,6 @@ class TestFileJpeg:
 | 
			
		|||
        # This is photoshop-200dpi.jpg with EXIF resolution unit set to cm:
 | 
			
		||||
        # exiftool -exif:ResolutionUnit=cm photoshop-200dpi.jpg
 | 
			
		||||
        with Image.open("Tests/images/exif-200dpcm.jpg") as im:
 | 
			
		||||
 | 
			
		||||
            # Act / Assert
 | 
			
		||||
            assert im.info.get("dpi") == (508, 508)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -758,7 +754,6 @@ class TestFileJpeg:
 | 
			
		|||
        # This is photoshop-200dpi.jpg with EXIF resolution set to 0/0:
 | 
			
		||||
        # exiftool -XResolution=0/0 -YResolution=0/0 photoshop-200dpi.jpg
 | 
			
		||||
        with Image.open("Tests/images/exif-dpi-zerodivision.jpg") as im:
 | 
			
		||||
 | 
			
		||||
            # Act / Assert
 | 
			
		||||
            # This should return the default, and not raise a ZeroDivisionError
 | 
			
		||||
            assert im.info.get("dpi") == (72, 72)
 | 
			
		||||
| 
						 | 
				
			
			@ -767,7 +762,6 @@ class TestFileJpeg:
 | 
			
		|||
        # Arrange
 | 
			
		||||
        # 0x011A tag in this exif contains string '300300\x02'
 | 
			
		||||
        with Image.open("Tests/images/broken_exif_dpi.jpg") as im:
 | 
			
		||||
 | 
			
		||||
            # Act / Assert
 | 
			
		||||
            # This should return the default
 | 
			
		||||
            assert im.info.get("dpi") == (72, 72)
 | 
			
		||||
| 
						 | 
				
			
			@ -777,7 +771,6 @@ class TestFileJpeg:
 | 
			
		|||
        # This is photoshop-200dpi.jpg with resolution removed from EXIF:
 | 
			
		||||
        # exiftool "-*resolution*"= photoshop-200dpi.jpg
 | 
			
		||||
        with Image.open("Tests/images/no-dpi-in-exif.jpg") as im:
 | 
			
		||||
 | 
			
		||||
            # Act / Assert
 | 
			
		||||
            # "When the image resolution is unknown, 72 [dpi] is designated."
 | 
			
		||||
            # https://exiv2.org/tags.html
 | 
			
		||||
| 
						 | 
				
			
			@ -787,7 +780,6 @@ class TestFileJpeg:
 | 
			
		|||
        # This is no-dpi-in-exif with the tiff header of the exif block
 | 
			
		||||
        # hexedited from MM * to FF FF FF FF
 | 
			
		||||
        with Image.open("Tests/images/invalid-exif.jpg") as im:
 | 
			
		||||
 | 
			
		||||
            # This should return the default, and not a SyntaxError or
 | 
			
		||||
            # OSError for unidentified image.
 | 
			
		||||
            assert im.info.get("dpi") == (72, 72)
 | 
			
		||||
| 
						 | 
				
			
			@ -810,7 +802,6 @@ class TestFileJpeg:
 | 
			
		|||
    def test_invalid_exif_x_resolution(self):
 | 
			
		||||
        # When no x or y resolution is defined in EXIF
 | 
			
		||||
        with Image.open("Tests/images/invalid-exif-without-x-resolution.jpg") as im:
 | 
			
		||||
 | 
			
		||||
            # This should return the default, and not a ValueError or
 | 
			
		||||
            # OSError for an unidentified image.
 | 
			
		||||
            assert im.info.get("dpi") == (72, 72)
 | 
			
		||||
| 
						 | 
				
			
			@ -820,7 +811,6 @@ class TestFileJpeg:
 | 
			
		|||
        # This image has been manually hexedited to have an IFD offset of 10,
 | 
			
		||||
        # in contrast to normal 8
 | 
			
		||||
        with Image.open("Tests/images/exif-ifd-offset.jpg") as im:
 | 
			
		||||
 | 
			
		||||
            # Act / Assert
 | 
			
		||||
            assert im._getexif()[306] == "2017:03:13 23:03:09"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -252,11 +252,24 @@ def test_mct():
 | 
			
		|||
            assert_image_similar(im, jp2, 1.0e-3)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_sgnd(tmp_path):
 | 
			
		||||
    outfile = str(tmp_path / "temp.jp2")
 | 
			
		||||
 | 
			
		||||
    im = Image.new("L", (1, 1))
 | 
			
		||||
    im.save(outfile)
 | 
			
		||||
    with Image.open(outfile) as reloaded:
 | 
			
		||||
        assert reloaded.getpixel((0, 0)) == 0
 | 
			
		||||
 | 
			
		||||
    im = Image.new("L", (1, 1))
 | 
			
		||||
    im.save(outfile, signed=True)
 | 
			
		||||
    with Image.open(outfile) as reloaded_signed:
 | 
			
		||||
        assert reloaded_signed.getpixel((0, 0)) == 128
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_rgba():
 | 
			
		||||
    # Arrange
 | 
			
		||||
    with Image.open("Tests/images/rgb_trns_ycbc.j2k") as j2k:
 | 
			
		||||
        with Image.open("Tests/images/rgb_trns_ycbc.jp2") as jp2:
 | 
			
		||||
 | 
			
		||||
            # Act
 | 
			
		||||
            j2k.load()
 | 
			
		||||
            jp2.load()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -645,7 +645,6 @@ class TestFileLibTiff(LibTiffTestCase):
 | 
			
		|||
        pilim = hopper()
 | 
			
		||||
 | 
			
		||||
        def save_bytesio(compression=None):
 | 
			
		||||
 | 
			
		||||
            buffer_io = io.BytesIO()
 | 
			
		||||
            pilim.save(buffer_io, format="tiff", compression=compression)
 | 
			
		||||
            buffer_io.seek(0)
 | 
			
		||||
| 
						 | 
				
			
			@ -740,7 +739,6 @@ class TestFileLibTiff(LibTiffTestCase):
 | 
			
		|||
 | 
			
		||||
    def test_multipage_compression(self):
 | 
			
		||||
        with Image.open("Tests/images/compression.tif") as im:
 | 
			
		||||
 | 
			
		||||
            im.seek(0)
 | 
			
		||||
            assert im._compression == "tiff_ccitt"
 | 
			
		||||
            assert im.size == (10, 10)
 | 
			
		||||
| 
						 | 
				
			
			@ -986,6 +984,36 @@ class TestFileLibTiff(LibTiffTestCase):
 | 
			
		|||
        ) as im:
 | 
			
		||||
            assert_image_equal_tofile(im, "Tests/images/old-style-jpeg-compression.png")
 | 
			
		||||
 | 
			
		||||
    @pytest.mark.parametrize(
 | 
			
		||||
        "file_name, mode, size, tile",
 | 
			
		||||
        [
 | 
			
		||||
            (
 | 
			
		||||
                "tiff_wrong_bits_per_sample.tiff",
 | 
			
		||||
                "RGBA",
 | 
			
		||||
                (52, 53),
 | 
			
		||||
                [("raw", (0, 0, 52, 53), 160, ("RGBA", 0, 1))],
 | 
			
		||||
            ),
 | 
			
		||||
            (
 | 
			
		||||
                "tiff_wrong_bits_per_sample_2.tiff",
 | 
			
		||||
                "RGB",
 | 
			
		||||
                (16, 16),
 | 
			
		||||
                [("raw", (0, 0, 16, 16), 8, ("RGB", 0, 1))],
 | 
			
		||||
            ),
 | 
			
		||||
            (
 | 
			
		||||
                "tiff_wrong_bits_per_sample_3.tiff",
 | 
			
		||||
                "RGBA",
 | 
			
		||||
                (512, 256),
 | 
			
		||||
                [("libtiff", (0, 0, 512, 256), 0, ("RGBA", "tiff_lzw", False, 48782))],
 | 
			
		||||
            ),
 | 
			
		||||
        ],
 | 
			
		||||
    )
 | 
			
		||||
    def test_wrong_bits_per_sample(self, file_name, mode, size, tile):
 | 
			
		||||
        with Image.open("Tests/images/" + file_name) as im:
 | 
			
		||||
            assert im.mode == mode
 | 
			
		||||
            assert im.size == size
 | 
			
		||||
            assert im.tile == tile
 | 
			
		||||
            im.load()
 | 
			
		||||
 | 
			
		||||
    def test_no_rows_per_strip(self):
 | 
			
		||||
        # This image does not have a RowsPerStrip TIFF tag
 | 
			
		||||
        infile = "Tests/images/no_rows_per_strip.tif"
 | 
			
		||||
| 
						 | 
				
			
			@ -1067,3 +1095,27 @@ class TestFileLibTiff(LibTiffTestCase):
 | 
			
		|||
        out = str(tmp_path / "temp.tif")
 | 
			
		||||
        with pytest.raises(SystemError):
 | 
			
		||||
            im.save(out, compression=compression)
 | 
			
		||||
 | 
			
		||||
    def test_save_many_compressed(self, tmp_path):
 | 
			
		||||
        im = hopper()
 | 
			
		||||
        out = str(tmp_path / "temp.tif")
 | 
			
		||||
        for _ in range(10000):
 | 
			
		||||
            im.save(out, compression="jpeg")
 | 
			
		||||
 | 
			
		||||
    @pytest.mark.parametrize(
 | 
			
		||||
        "path, sizes",
 | 
			
		||||
        (
 | 
			
		||||
            ("Tests/images/hopper.tif", ()),
 | 
			
		||||
            ("Tests/images/child_ifd.tiff", (16, 8)),
 | 
			
		||||
            ("Tests/images/child_ifd_jpeg.tiff", (20,)),
 | 
			
		||||
        ),
 | 
			
		||||
    )
 | 
			
		||||
    def test_get_child_images(self, path, sizes):
 | 
			
		||||
        with Image.open(path) as im:
 | 
			
		||||
            ims = im.get_child_images()
 | 
			
		||||
 | 
			
		||||
        assert len(ims) == len(sizes)
 | 
			
		||||
        for i, im in enumerate(ims):
 | 
			
		||||
            w = sizes[i]
 | 
			
		||||
            expected = Image.new("RGB", (w, w), "#f00")
 | 
			
		||||
            assert_image_similar(im, expected, 1)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -51,6 +51,16 @@ def test_seek():
 | 
			
		|||
        assert im.tell() == 0
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_close():
 | 
			
		||||
    with Image.open(TEST_FILE) as im:
 | 
			
		||||
        pass
 | 
			
		||||
    assert im.ole.fp.closed
 | 
			
		||||
 | 
			
		||||
    im = Image.open(TEST_FILE)
 | 
			
		||||
    im.close()
 | 
			
		||||
    assert im.ole.fp.closed
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_invalid_file():
 | 
			
		||||
    # Test an invalid OLE file
 | 
			
		||||
    invalid_file = "Tests/images/flower.jpg"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -42,7 +42,8 @@ def test_unclosed_file():
 | 
			
		|||
        im = Image.open(test_files[0])
 | 
			
		||||
        im.load()
 | 
			
		||||
 | 
			
		||||
    pytest.warns(ResourceWarning, open)
 | 
			
		||||
    with pytest.warns(ResourceWarning):
 | 
			
		||||
        open()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_closed_file():
 | 
			
		||||
| 
						 | 
				
			
			@ -168,8 +169,7 @@ def test_mp_no_data():
 | 
			
		|||
def test_mp_attribute(test_file):
 | 
			
		||||
    with Image.open(test_file) as im:
 | 
			
		||||
        mpinfo = im._getmp()
 | 
			
		||||
    frame_number = 0
 | 
			
		||||
    for mpentry in mpinfo[0xB002]:
 | 
			
		||||
    for frame_number, mpentry in enumerate(mpinfo[0xB002]):
 | 
			
		||||
        mpattr = mpentry["Attribute"]
 | 
			
		||||
        if frame_number:
 | 
			
		||||
            assert not mpattr["RepresentativeImageFlag"]
 | 
			
		||||
| 
						 | 
				
			
			@ -180,7 +180,6 @@ def test_mp_attribute(test_file):
 | 
			
		|||
        assert mpattr["ImageDataFormat"] == "JPEG"
 | 
			
		||||
        assert mpattr["MPType"] == "Multi-Frame Image: (Disparity)"
 | 
			
		||||
        assert mpattr["Reserved"] == 0
 | 
			
		||||
        frame_number += 1
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.mark.parametrize("test_file", test_files)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -44,7 +44,6 @@ def test_open_windows_v1():
 | 
			
		|||
    # Arrange
 | 
			
		||||
    # Act
 | 
			
		||||
    with Image.open(TEST_FILE) as im:
 | 
			
		||||
 | 
			
		||||
        # Assert
 | 
			
		||||
        assert_image_equal(im, hopper("1"))
 | 
			
		||||
        assert isinstance(im, MspImagePlugin.MspImageFile)
 | 
			
		||||
| 
						 | 
				
			
			@ -59,7 +58,6 @@ def _assert_file_image_equal(source_path, target_path):
 | 
			
		|||
    not os.path.exists(EXTRA_DIR), reason="Extra image files not installed"
 | 
			
		||||
)
 | 
			
		||||
def test_open_windows_v2():
 | 
			
		||||
 | 
			
		||||
    files = (
 | 
			
		||||
        os.path.join(EXTRA_DIR, f)
 | 
			
		||||
        for f in os.listdir(EXTRA_DIR)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,7 +8,7 @@ import pytest
 | 
			
		|||
 | 
			
		||||
from PIL import Image, PdfParser, features
 | 
			
		||||
 | 
			
		||||
from .helper import hopper, mark_if_feature_version
 | 
			
		||||
from .helper import hopper, mark_if_feature_version, skip_unless_feature
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def helper_save_as_pdf(tmp_path, mode, **kwargs):
 | 
			
		||||
| 
						 | 
				
			
			@ -42,6 +42,11 @@ def test_save(tmp_path, mode):
 | 
			
		|||
    helper_save_as_pdf(tmp_path, mode)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@skip_unless_feature("jpg_2000")
 | 
			
		||||
def test_save_rgba(tmp_path):
 | 
			
		||||
    helper_save_as_pdf(tmp_path, "RGBA")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_monochrome(tmp_path):
 | 
			
		||||
    # Arrange
 | 
			
		||||
    mode = "1"
 | 
			
		||||
| 
						 | 
				
			
			@ -80,6 +85,34 @@ def test_resolution(tmp_path):
 | 
			
		|||
    assert size == (61.44, 61.44)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.mark.parametrize(
 | 
			
		||||
    "params",
 | 
			
		||||
    (
 | 
			
		||||
        {"dpi": (75, 150)},
 | 
			
		||||
        {"dpi": (75, 150), "resolution": 200},
 | 
			
		||||
    ),
 | 
			
		||||
)
 | 
			
		||||
def test_dpi(params, tmp_path):
 | 
			
		||||
    im = hopper()
 | 
			
		||||
 | 
			
		||||
    outfile = str(tmp_path / "temp.pdf")
 | 
			
		||||
    im.save(outfile, **params)
 | 
			
		||||
 | 
			
		||||
    with open(outfile, "rb") as fp:
 | 
			
		||||
        contents = fp.read()
 | 
			
		||||
 | 
			
		||||
    size = tuple(
 | 
			
		||||
        float(d)
 | 
			
		||||
        for d in contents.split(b"stream\nq ")[1].split(b" 0 0 cm")[0].split(b" 0 0 ")
 | 
			
		||||
    )
 | 
			
		||||
    assert size == (122.88, 61.44)
 | 
			
		||||
 | 
			
		||||
    size = tuple(
 | 
			
		||||
        float(d) for d in contents.split(b"/MediaBox [ 0 0 ")[1].split(b"]")[0].split()
 | 
			
		||||
    )
 | 
			
		||||
    assert size == (122.88, 61.44)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@mark_if_feature_version(
 | 
			
		||||
    pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			@ -89,7 +122,6 @@ def test_save_all(tmp_path):
 | 
			
		|||
 | 
			
		||||
    # Multiframe image
 | 
			
		||||
    with Image.open("Tests/images/dispose_bgnd.gif") as im:
 | 
			
		||||
 | 
			
		||||
        outfile = str(tmp_path / "temp.pdf")
 | 
			
		||||
        im.save(outfile, save_all=True)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -123,7 +155,6 @@ def test_save_all(tmp_path):
 | 
			
		|||
def test_multiframe_normal_save(tmp_path):
 | 
			
		||||
    # Test saving a multiframe image without save_all
 | 
			
		||||
    with Image.open("Tests/images/dispose_bgnd.gif") as im:
 | 
			
		||||
 | 
			
		||||
        outfile = str(tmp_path / "temp.pdf")
 | 
			
		||||
        im.save(outfile)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -286,6 +317,7 @@ def test_pdf_append_to_bytesio():
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
@pytest.mark.timeout(1)
 | 
			
		||||
@pytest.mark.skipif("PILLOW_VALGRIND_TEST" in os.environ, reason="Valgrind is slower")
 | 
			
		||||
@pytest.mark.parametrize("newline", (b"\r", b"\n"))
 | 
			
		||||
def test_redos(newline):
 | 
			
		||||
    malicious = b" trailer<<>>" + newline * 3456
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -78,7 +78,6 @@ class TestFilePng:
 | 
			
		|||
        return chunks
 | 
			
		||||
 | 
			
		||||
    def test_sanity(self, tmp_path):
 | 
			
		||||
 | 
			
		||||
        # internal version number
 | 
			
		||||
        assert re.search(r"\d+\.\d+\.\d+(\.\d+)?$", features.version_codec("zlib"))
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -156,7 +155,6 @@ class TestFilePng:
 | 
			
		|||
        assert im.info == {"spam": "egg"}
 | 
			
		||||
 | 
			
		||||
    def test_bad_itxt(self):
 | 
			
		||||
 | 
			
		||||
        im = load(HEAD + chunk(b"iTXt") + TAIL)
 | 
			
		||||
        assert im.info == {}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -201,7 +199,6 @@ class TestFilePng:
 | 
			
		|||
        assert im.info["spam"].tkey == "Spam"
 | 
			
		||||
 | 
			
		||||
    def test_interlace(self):
 | 
			
		||||
 | 
			
		||||
        test_file = "Tests/images/pil123p.png"
 | 
			
		||||
        with Image.open(test_file) as im:
 | 
			
		||||
            assert_image(im, "P", (162, 150))
 | 
			
		||||
| 
						 | 
				
			
			@ -495,7 +492,6 @@ class TestFilePng:
 | 
			
		|||
        # Check reading images with null tRNS value, issue #1239
 | 
			
		||||
        test_file = "Tests/images/tRNS_null_1x1.png"
 | 
			
		||||
        with Image.open(test_file) as im:
 | 
			
		||||
 | 
			
		||||
            assert im.info["transparency"] == 0
 | 
			
		||||
 | 
			
		||||
    def test_save_icc_profile(self):
 | 
			
		||||
| 
						 | 
				
			
			@ -593,7 +589,7 @@ class TestFilePng:
 | 
			
		|||
 | 
			
		||||
    def test_textual_chunks_after_idat(self):
 | 
			
		||||
        with Image.open("Tests/images/hopper.png") as im:
 | 
			
		||||
            assert "comment" in im.text.keys()
 | 
			
		||||
            assert "comment" in im.text
 | 
			
		||||
            for k, v in {
 | 
			
		||||
                "date:create": "2014-09-04T09:37:08+03:00",
 | 
			
		||||
                "date:modify": "2014-09-04T09:37:08+03:00",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -27,7 +27,8 @@ def test_unclosed_file():
 | 
			
		|||
        im = Image.open(test_file)
 | 
			
		||||
        im.load()
 | 
			
		||||
 | 
			
		||||
    pytest.warns(ResourceWarning, open)
 | 
			
		||||
    with pytest.warns(ResourceWarning):
 | 
			
		||||
        open()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_closed_file():
 | 
			
		||||
| 
						 | 
				
			
			@ -77,7 +78,6 @@ def test_eoferror():
 | 
			
		|||
 | 
			
		||||
def test_seek_tell():
 | 
			
		||||
    with Image.open(test_file) as im:
 | 
			
		||||
 | 
			
		||||
        layer_number = im.tell()
 | 
			
		||||
        assert layer_number == 1
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -95,7 +95,6 @@ def test_seek_tell():
 | 
			
		|||
 | 
			
		||||
def test_seek_eoferror():
 | 
			
		||||
    with Image.open(test_file) as im:
 | 
			
		||||
 | 
			
		||||
        with pytest.raises(EOFError):
 | 
			
		||||
            im.seek(-1)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										28
									
								
								Tests/test_file_qoi.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,28 @@
 | 
			
		|||
import pytest
 | 
			
		||||
 | 
			
		||||
from PIL import Image, QoiImagePlugin
 | 
			
		||||
 | 
			
		||||
from .helper import assert_image_equal_tofile, assert_image_similar_tofile
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_sanity():
 | 
			
		||||
    with Image.open("Tests/images/hopper.qoi") as im:
 | 
			
		||||
        assert im.mode == "RGB"
 | 
			
		||||
        assert im.size == (128, 128)
 | 
			
		||||
        assert im.format == "QOI"
 | 
			
		||||
 | 
			
		||||
        assert_image_equal_tofile(im, "Tests/images/hopper.png")
 | 
			
		||||
 | 
			
		||||
    with Image.open("Tests/images/pil123rgba.qoi") as im:
 | 
			
		||||
        assert im.mode == "RGBA"
 | 
			
		||||
        assert im.size == (162, 150)
 | 
			
		||||
        assert im.format == "QOI"
 | 
			
		||||
 | 
			
		||||
        assert_image_similar_tofile(im, "Tests/images/pil123rgba.png", 0.03)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_invalid_file():
 | 
			
		||||
    invalid_file = "Tests/images/flower.jpg"
 | 
			
		||||
 | 
			
		||||
    with pytest.raises(SyntaxError):
 | 
			
		||||
        QoiImagePlugin.QoiImageFile(invalid_file)
 | 
			
		||||
| 
						 | 
				
			
			@ -25,7 +25,8 @@ def test_unclosed_file():
 | 
			
		|||
        im = Image.open(TEST_FILE)
 | 
			
		||||
        im.load()
 | 
			
		||||
 | 
			
		||||
    pytest.warns(ResourceWarning, open)
 | 
			
		||||
    with pytest.warns(ResourceWarning):
 | 
			
		||||
        open()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_closed_file():
 | 
			
		||||
| 
						 | 
				
			
			@ -79,7 +80,6 @@ def test_is_spider_image():
 | 
			
		|||
def test_tell():
 | 
			
		||||
    # Arrange
 | 
			
		||||
    with Image.open(TEST_FILE) as im:
 | 
			
		||||
 | 
			
		||||
        # Act
 | 
			
		||||
        index = im.tell()
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,7 +16,6 @@ def test_sanity():
 | 
			
		|||
 | 
			
		||||
    # Act
 | 
			
		||||
    with Image.open(test_file) as im:
 | 
			
		||||
 | 
			
		||||
        # Assert
 | 
			
		||||
        assert im.size == (128, 128)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,11 +10,14 @@ from .helper import is_pypy
 | 
			
		|||
TEST_TAR_FILE = "Tests/images/hopper.tar"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_sanity():
 | 
			
		||||
    for codec, test_path, format in [
 | 
			
		||||
        ["zlib", "hopper.png", "PNG"],
 | 
			
		||||
        ["jpg", "hopper.jpg", "JPEG"],
 | 
			
		||||
    ]:
 | 
			
		||||
@pytest.mark.parametrize(
 | 
			
		||||
    "codec, test_path, format",
 | 
			
		||||
    (
 | 
			
		||||
        ("zlib", "hopper.png", "PNG"),
 | 
			
		||||
        ("jpg", "hopper.jpg", "JPEG"),
 | 
			
		||||
    ),
 | 
			
		||||
)
 | 
			
		||||
def test_sanity(codec, test_path, format):
 | 
			
		||||
    if features.check(codec):
 | 
			
		||||
        with TarIO.TarIO(TEST_TAR_FILE, test_path) as tar:
 | 
			
		||||
            with Image.open(tar) as im:
 | 
			
		||||
| 
						 | 
				
			
			@ -26,11 +29,9 @@ def test_sanity():
 | 
			
		|||
 | 
			
		||||
@pytest.mark.skipif(is_pypy(), reason="Requires CPython")
 | 
			
		||||
def test_unclosed_file():
 | 
			
		||||
    def open():
 | 
			
		||||
    with pytest.warns(ResourceWarning):
 | 
			
		||||
        TarIO.TarIO(TEST_TAR_FILE, "hopper.jpg")
 | 
			
		||||
 | 
			
		||||
    pytest.warns(ResourceWarning, open)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_close():
 | 
			
		||||
    with warnings.catch_warnings():
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -78,7 +78,6 @@ def test_id_field():
 | 
			
		|||
 | 
			
		||||
    # Act
 | 
			
		||||
    with Image.open(test_file) as im:
 | 
			
		||||
 | 
			
		||||
        # Assert
 | 
			
		||||
        assert im.size == (100, 100)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -89,7 +88,6 @@ def test_id_field_rle():
 | 
			
		|||
 | 
			
		||||
    # Act
 | 
			
		||||
    with Image.open(test_file) as im:
 | 
			
		||||
 | 
			
		||||
        # Assert
 | 
			
		||||
        assert im.size == (199, 199)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -165,13 +163,14 @@ def test_save_id_section(tmp_path):
 | 
			
		|||
 | 
			
		||||
    # Save with custom id section greater than 255 characters
 | 
			
		||||
    id_section = b"Test content" * 25
 | 
			
		||||
    pytest.warns(UserWarning, lambda: im.save(out, id_section=id_section))
 | 
			
		||||
    with pytest.warns(UserWarning):
 | 
			
		||||
        im.save(out, id_section=id_section)
 | 
			
		||||
 | 
			
		||||
    with Image.open(out) as test_im:
 | 
			
		||||
        assert test_im.info["id_section"] == id_section[:255]
 | 
			
		||||
 | 
			
		||||
    test_file = "Tests/images/tga_id_field.tga"
 | 
			
		||||
    with Image.open(test_file) as im:
 | 
			
		||||
 | 
			
		||||
        # Save with no id section
 | 
			
		||||
        im.save(out, id_section="")
 | 
			
		||||
    with Image.open(out) as test_im:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,7 +25,6 @@ except ImportError:
 | 
			
		|||
 | 
			
		||||
class TestFileTiff:
 | 
			
		||||
    def test_sanity(self, tmp_path):
 | 
			
		||||
 | 
			
		||||
        filename = str(tmp_path / "temp.tif")
 | 
			
		||||
 | 
			
		||||
        hopper("RGB").save(filename)
 | 
			
		||||
| 
						 | 
				
			
			@ -62,7 +61,8 @@ class TestFileTiff:
 | 
			
		|||
            im = Image.open("Tests/images/multipage.tiff")
 | 
			
		||||
            im.load()
 | 
			
		||||
 | 
			
		||||
        pytest.warns(ResourceWarning, open)
 | 
			
		||||
        with pytest.warns(ResourceWarning):
 | 
			
		||||
            open()
 | 
			
		||||
 | 
			
		||||
    def test_closed_file(self):
 | 
			
		||||
        with warnings.catch_warnings():
 | 
			
		||||
| 
						 | 
				
			
			@ -84,24 +84,6 @@ class TestFileTiff:
 | 
			
		|||
            with Image.open("Tests/images/multipage.tiff") as im:
 | 
			
		||||
                im.load()
 | 
			
		||||
 | 
			
		||||
    @pytest.mark.parametrize(
 | 
			
		||||
        "path, sizes",
 | 
			
		||||
        (
 | 
			
		||||
            ("Tests/images/hopper.tif", ()),
 | 
			
		||||
            ("Tests/images/child_ifd.tiff", (16, 8)),
 | 
			
		||||
            ("Tests/images/child_ifd_jpeg.tiff", (20,)),
 | 
			
		||||
        ),
 | 
			
		||||
    )
 | 
			
		||||
    def test_get_child_images(self, path, sizes):
 | 
			
		||||
        with Image.open(path) as im:
 | 
			
		||||
            ims = im.get_child_images()
 | 
			
		||||
 | 
			
		||||
        assert len(ims) == len(sizes)
 | 
			
		||||
        for i, im in enumerate(ims):
 | 
			
		||||
            w = sizes[i]
 | 
			
		||||
            expected = Image.new("RGB", (w, w), "#f00")
 | 
			
		||||
            assert_image_similar(im, expected, 1)
 | 
			
		||||
 | 
			
		||||
    def test_mac_tiff(self):
 | 
			
		||||
        # Read RGBa images from macOS [@PIL136]
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -118,36 +100,6 @@ class TestFileTiff:
 | 
			
		|||
        with Image.open("Tests/images/hopper_bigtiff.tif") as im:
 | 
			
		||||
            assert_image_equal_tofile(im, "Tests/images/hopper.tif")
 | 
			
		||||
 | 
			
		||||
    @pytest.mark.parametrize(
 | 
			
		||||
        "file_name,mode,size,tile",
 | 
			
		||||
        [
 | 
			
		||||
            (
 | 
			
		||||
                "tiff_wrong_bits_per_sample.tiff",
 | 
			
		||||
                "RGBA",
 | 
			
		||||
                (52, 53),
 | 
			
		||||
                [("raw", (0, 0, 52, 53), 160, ("RGBA", 0, 1))],
 | 
			
		||||
            ),
 | 
			
		||||
            (
 | 
			
		||||
                "tiff_wrong_bits_per_sample_2.tiff",
 | 
			
		||||
                "RGB",
 | 
			
		||||
                (16, 16),
 | 
			
		||||
                [("raw", (0, 0, 16, 16), 8, ("RGB", 0, 1))],
 | 
			
		||||
            ),
 | 
			
		||||
            (
 | 
			
		||||
                "tiff_wrong_bits_per_sample_3.tiff",
 | 
			
		||||
                "RGBA",
 | 
			
		||||
                (512, 256),
 | 
			
		||||
                [("libtiff", (0, 0, 512, 256), 0, ("RGBA", "tiff_lzw", False, 48782))],
 | 
			
		||||
            ),
 | 
			
		||||
        ],
 | 
			
		||||
    )
 | 
			
		||||
    def test_wrong_bits_per_sample(self, file_name, mode, size, tile):
 | 
			
		||||
        with Image.open("Tests/images/" + file_name) as im:
 | 
			
		||||
            assert im.mode == mode
 | 
			
		||||
            assert im.size == size
 | 
			
		||||
            assert im.tile == tile
 | 
			
		||||
            im.load()
 | 
			
		||||
 | 
			
		||||
    def test_set_legacy_api(self):
 | 
			
		||||
        ifd = TiffImagePlugin.ImageFileDirectory_v2()
 | 
			
		||||
        with pytest.raises(Exception) as e:
 | 
			
		||||
| 
						 | 
				
			
			@ -157,7 +109,6 @@ class TestFileTiff:
 | 
			
		|||
    def test_xyres_tiff(self):
 | 
			
		||||
        filename = "Tests/images/pil168.tif"
 | 
			
		||||
        with Image.open(filename) as im:
 | 
			
		||||
 | 
			
		||||
            # legacy api
 | 
			
		||||
            assert isinstance(im.tag[X_RESOLUTION][0], tuple)
 | 
			
		||||
            assert isinstance(im.tag[Y_RESOLUTION][0], tuple)
 | 
			
		||||
| 
						 | 
				
			
			@ -171,7 +122,6 @@ class TestFileTiff:
 | 
			
		|||
    def test_xyres_fallback_tiff(self):
 | 
			
		||||
        filename = "Tests/images/compression.tif"
 | 
			
		||||
        with Image.open(filename) as im:
 | 
			
		||||
 | 
			
		||||
            # v2 api
 | 
			
		||||
            assert isinstance(im.tag_v2[X_RESOLUTION], TiffImagePlugin.IFDRational)
 | 
			
		||||
            assert isinstance(im.tag_v2[Y_RESOLUTION], TiffImagePlugin.IFDRational)
 | 
			
		||||
| 
						 | 
				
			
			@ -186,7 +136,6 @@ class TestFileTiff:
 | 
			
		|||
    def test_int_resolution(self):
 | 
			
		||||
        filename = "Tests/images/pil168.tif"
 | 
			
		||||
        with Image.open(filename) as im:
 | 
			
		||||
 | 
			
		||||
            # Try to read a file where X,Y_RESOLUTION are ints
 | 
			
		||||
            im.tag_v2[X_RESOLUTION] = 71
 | 
			
		||||
            im.tag_v2[Y_RESOLUTION] = 71
 | 
			
		||||
| 
						 | 
				
			
			@ -235,7 +184,8 @@ class TestFileTiff:
 | 
			
		|||
    def test_bad_exif(self):
 | 
			
		||||
        with Image.open("Tests/images/hopper_bad_exif.jpg") as i:
 | 
			
		||||
            # Should not raise struct.error.
 | 
			
		||||
            pytest.warns(UserWarning, i._getexif)
 | 
			
		||||
            with pytest.warns(UserWarning):
 | 
			
		||||
                i._getexif()
 | 
			
		||||
 | 
			
		||||
    def test_save_rgba(self, tmp_path):
 | 
			
		||||
        im = hopper("RGBA")
 | 
			
		||||
| 
						 | 
				
			
			@ -381,7 +331,6 @@ class TestFileTiff:
 | 
			
		|||
    def test___str__(self):
 | 
			
		||||
        filename = "Tests/images/pil136.tiff"
 | 
			
		||||
        with Image.open(filename) as im:
 | 
			
		||||
 | 
			
		||||
            # Act
 | 
			
		||||
            ret = str(im.ifd)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -392,7 +341,6 @@ class TestFileTiff:
 | 
			
		|||
        # Arrange
 | 
			
		||||
        filename = "Tests/images/pil136.tiff"
 | 
			
		||||
        with Image.open(filename) as im:
 | 
			
		||||
 | 
			
		||||
            # v2 interface
 | 
			
		||||
            v2_tags = {
 | 
			
		||||
                256: 55,
 | 
			
		||||
| 
						 | 
				
			
			@ -630,7 +578,6 @@ class TestFileTiff:
 | 
			
		|||
        filename = str(tmp_path / "temp.tif")
 | 
			
		||||
        hopper("RGB").save(filename, **kwargs)
 | 
			
		||||
        with Image.open(filename) as im:
 | 
			
		||||
 | 
			
		||||
            # legacy interface
 | 
			
		||||
            assert im.tag[X_RESOLUTION][0][0] == 72
 | 
			
		||||
            assert im.tag[Y_RESOLUTION][0][0] == 36
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -54,7 +54,6 @@ def test_rt_metadata(tmp_path):
 | 
			
		|||
    img.save(f, tiffinfo=info)
 | 
			
		||||
 | 
			
		||||
    with Image.open(f) as loaded:
 | 
			
		||||
 | 
			
		||||
        assert loaded.tag[ImageJMetaDataByteCounts] == (len(bin_data),)
 | 
			
		||||
        assert loaded.tag_v2[ImageJMetaDataByteCounts] == (len(bin_data),)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -74,14 +73,12 @@ def test_rt_metadata(tmp_path):
 | 
			
		|||
    info[ImageJMetaDataByteCounts] = (8, len(bin_data) - 8)
 | 
			
		||||
    img.save(f, tiffinfo=info)
 | 
			
		||||
    with Image.open(f) as loaded:
 | 
			
		||||
 | 
			
		||||
        assert loaded.tag[ImageJMetaDataByteCounts] == (8, len(bin_data) - 8)
 | 
			
		||||
        assert loaded.tag_v2[ImageJMetaDataByteCounts] == (8, len(bin_data) - 8)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_read_metadata():
 | 
			
		||||
    with Image.open("Tests/images/hopper_g4.tif") as img:
 | 
			
		||||
 | 
			
		||||
        assert {
 | 
			
		||||
            "YResolution": IFDRational(4294967295, 113653537),
 | 
			
		||||
            "PlanarConfiguration": 1,
 | 
			
		||||
| 
						 | 
				
			
			@ -185,30 +182,32 @@ def test_iptc(tmp_path):
 | 
			
		|||
        im.save(out)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_writing_bytes_to_ascii(tmp_path):
 | 
			
		||||
    im = hopper()
 | 
			
		||||
@pytest.mark.parametrize("value, expected", ((b"test", "test"), (1, "1")))
 | 
			
		||||
def test_writing_other_types_to_ascii(value, expected, tmp_path):
 | 
			
		||||
    info = TiffImagePlugin.ImageFileDirectory_v2()
 | 
			
		||||
 | 
			
		||||
    tag = TiffTags.TAGS_V2[271]
 | 
			
		||||
    assert tag.type == TiffTags.ASCII
 | 
			
		||||
 | 
			
		||||
    info[271] = b"test"
 | 
			
		||||
    info[271] = value
 | 
			
		||||
 | 
			
		||||
    im = hopper()
 | 
			
		||||
    out = str(tmp_path / "temp.tiff")
 | 
			
		||||
    im.save(out, tiffinfo=info)
 | 
			
		||||
 | 
			
		||||
    with Image.open(out) as reloaded:
 | 
			
		||||
        assert reloaded.tag_v2[271] == "test"
 | 
			
		||||
        assert reloaded.tag_v2[271] == expected
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_writing_int_to_bytes(tmp_path):
 | 
			
		||||
@pytest.mark.parametrize("value", (1, IFDRational(1)))
 | 
			
		||||
def test_writing_other_types_to_bytes(value, tmp_path):
 | 
			
		||||
    im = hopper()
 | 
			
		||||
    info = TiffImagePlugin.ImageFileDirectory_v2()
 | 
			
		||||
 | 
			
		||||
    tag = TiffTags.TAGS_V2[700]
 | 
			
		||||
    assert tag.type == TiffTags.BYTE
 | 
			
		||||
 | 
			
		||||
    info[700] = 1
 | 
			
		||||
    info[700] = value
 | 
			
		||||
 | 
			
		||||
    out = str(tmp_path / "temp.tiff")
 | 
			
		||||
    im.save(out, tiffinfo=info)
 | 
			
		||||
| 
						 | 
				
			
			@ -217,6 +216,22 @@ def test_writing_int_to_bytes(tmp_path):
 | 
			
		|||
        assert reloaded.tag_v2[700] == b"\x01"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_writing_other_types_to_undefined(tmp_path):
 | 
			
		||||
    im = hopper()
 | 
			
		||||
    info = TiffImagePlugin.ImageFileDirectory_v2()
 | 
			
		||||
 | 
			
		||||
    tag = TiffTags.TAGS_V2[33723]
 | 
			
		||||
    assert tag.type == TiffTags.UNDEFINED
 | 
			
		||||
 | 
			
		||||
    info[33723] = 1
 | 
			
		||||
 | 
			
		||||
    out = str(tmp_path / "temp.tiff")
 | 
			
		||||
    im.save(out, tiffinfo=info)
 | 
			
		||||
 | 
			
		||||
    with Image.open(out) as reloaded:
 | 
			
		||||
        assert reloaded.tag_v2[33723] == b"1"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_undefined_zero(tmp_path):
 | 
			
		||||
    # Check that the tag has not been changed since this test was created
 | 
			
		||||
    tag = TiffTags.TAGS_V2[45059]
 | 
			
		||||
| 
						 | 
				
			
			@ -237,7 +252,8 @@ def test_empty_metadata():
 | 
			
		|||
    head = f.read(8)
 | 
			
		||||
    info = TiffImagePlugin.ImageFileDirectory(head)
 | 
			
		||||
    # Should not raise struct.error.
 | 
			
		||||
    pytest.warns(UserWarning, info.load, f)
 | 
			
		||||
    with pytest.warns(UserWarning):
 | 
			
		||||
        info.load(f)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_iccprofile(tmp_path):
 | 
			
		||||
| 
						 | 
				
			
			@ -403,11 +419,12 @@ def test_too_many_entries():
 | 
			
		|||
    ifd = TiffImagePlugin.ImageFileDirectory_v2()
 | 
			
		||||
 | 
			
		||||
    #    277: ("SamplesPerPixel", SHORT, 1),
 | 
			
		||||
    ifd._tagdata[277] = struct.pack("hh", 4, 4)
 | 
			
		||||
    ifd._tagdata[277] = struct.pack("<hh", 4, 4)
 | 
			
		||||
    ifd.tagtype[277] = TiffTags.SHORT
 | 
			
		||||
 | 
			
		||||
    # Should not raise ValueError.
 | 
			
		||||
    pytest.warns(UserWarning, lambda: ifd[277])
 | 
			
		||||
    with pytest.warns(UserWarning):
 | 
			
		||||
        assert ifd[277] == 4
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_tag_group_data():
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -29,7 +29,10 @@ class TestUnsupportedWebp:
 | 
			
		|||
            WebPImagePlugin.SUPPORTED = False
 | 
			
		||||
 | 
			
		||||
        file_path = "Tests/images/hopper.webp"
 | 
			
		||||
        pytest.warns(UserWarning, lambda: pytest.raises(OSError, Image.open, file_path))
 | 
			
		||||
        with pytest.warns(UserWarning):
 | 
			
		||||
            with pytest.raises(OSError):
 | 
			
		||||
                with Image.open(file_path):
 | 
			
		||||
                    pass
 | 
			
		||||
 | 
			
		||||
        if HAVE_WEBP:
 | 
			
		||||
            WebPImagePlugin.SUPPORTED = True
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,10 +18,8 @@ except ImportError:
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
def test_read_exif_metadata():
 | 
			
		||||
 | 
			
		||||
    file_path = "Tests/images/flower.webp"
 | 
			
		||||
    with Image.open(file_path) as image:
 | 
			
		||||
 | 
			
		||||
        assert image.format == "WEBP"
 | 
			
		||||
        exif_data = image.info.get("exif", None)
 | 
			
		||||
        assert exif_data
 | 
			
		||||
| 
						 | 
				
			
			@ -64,10 +62,8 @@ def test_write_exif_metadata():
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
def test_read_icc_profile():
 | 
			
		||||
 | 
			
		||||
    file_path = "Tests/images/flower2.webp"
 | 
			
		||||
    with Image.open(file_path) as image:
 | 
			
		||||
 | 
			
		||||
        assert image.format == "WEBP"
 | 
			
		||||
        assert image.info.get("icc_profile", None)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,7 +6,6 @@ from .helper import assert_image_similar_tofile, hopper
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
def test_load_raw():
 | 
			
		||||
 | 
			
		||||
    # Test basic EMF open and rendering
 | 
			
		||||
    with Image.open("Tests/images/drawing.emf") as im:
 | 
			
		||||
        if hasattr(Image.core, "drawwmf"):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -44,7 +44,6 @@ def test_open():
 | 
			
		|||
 | 
			
		||||
    # Act
 | 
			
		||||
    with Image.open(filename) as im:
 | 
			
		||||
 | 
			
		||||
        # Assert
 | 
			
		||||
        assert im.mode == "1"
 | 
			
		||||
        assert im.size == (128, 128)
 | 
			
		||||
| 
						 | 
				
			
			@ -57,7 +56,6 @@ def test_open_filename_with_underscore():
 | 
			
		|||
 | 
			
		||||
    # Act
 | 
			
		||||
    with Image.open(filename) as im:
 | 
			
		||||
 | 
			
		||||
        # Assert
 | 
			
		||||
        assert im.mode == "1"
 | 
			
		||||
        assert im.size == (128, 128)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,7 +10,6 @@ TEST_FILE = "Tests/images/hopper.p7"
 | 
			
		|||
def test_open():
 | 
			
		||||
    # Act
 | 
			
		||||
    with Image.open(TEST_FILE) as im:
 | 
			
		||||
 | 
			
		||||
        # Assert
 | 
			
		||||
        assert im.format == "XVThumb"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										22
									
								
								Tests/test_font_crash.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,22 @@
 | 
			
		|||
import pytest
 | 
			
		||||
 | 
			
		||||
from PIL import Image, ImageDraw, ImageFont
 | 
			
		||||
 | 
			
		||||
from .helper import skip_unless_feature
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestFontCrash:
 | 
			
		||||
    def _fuzz_font(self, font):
 | 
			
		||||
        # from fuzzers.fuzz_font
 | 
			
		||||
        font.getbbox("ABC")
 | 
			
		||||
        font.getmask("test text")
 | 
			
		||||
        with Image.new(mode="RGBA", size=(200, 200)) as im:
 | 
			
		||||
            draw = ImageDraw.Draw(im)
 | 
			
		||||
            draw.multiline_textbbox((10, 10), "ABC\nAaaa", font, stroke_width=2)
 | 
			
		||||
            draw.text((10, 10), "Test Text", font=font, fill="#000")
 | 
			
		||||
 | 
			
		||||
    @skip_unless_feature("freetype2")
 | 
			
		||||
    def test_segfault(self):
 | 
			
		||||
        with pytest.raises(OSError):
 | 
			
		||||
            font = ImageFont.truetype("Tests/fonts/fuzz_font-5203009437302784")
 | 
			
		||||
            self._fuzz_font(font)
 | 
			
		||||
| 
						 | 
				
			
			@ -69,7 +69,6 @@ class TestImage:
 | 
			
		|||
        assert issubclass(UnidentifiedImageError, OSError)
 | 
			
		||||
 | 
			
		||||
    def test_sanity(self):
 | 
			
		||||
 | 
			
		||||
        im = Image.new("L", (100, 100))
 | 
			
		||||
        assert repr(im)[:45] == "<PIL.Image.Image image mode=L size=100x100 at"
 | 
			
		||||
        assert im.mode == "L"
 | 
			
		||||
| 
						 | 
				
			
			@ -398,6 +397,17 @@ class TestImage:
 | 
			
		|||
        with pytest.raises(ValueError):
 | 
			
		||||
            source.alpha_composite(over, (0, 0), (0, -1))
 | 
			
		||||
 | 
			
		||||
    def test_register_open_duplicates(self):
 | 
			
		||||
        # Arrange
 | 
			
		||||
        factory, accept = Image.OPEN["JPEG"]
 | 
			
		||||
        id_length = len(Image.ID)
 | 
			
		||||
 | 
			
		||||
        # Act
 | 
			
		||||
        Image.register_open("JPEG", factory, accept)
 | 
			
		||||
 | 
			
		||||
        # Assert
 | 
			
		||||
        assert len(Image.ID) == id_length
 | 
			
		||||
 | 
			
		||||
    def test_registered_extensions_uninitialized(self):
 | 
			
		||||
        # Arrange
 | 
			
		||||
        Image._initialized = 0
 | 
			
		||||
| 
						 | 
				
			
			@ -512,6 +522,14 @@ class TestImage:
 | 
			
		|||
        i = Image.new("RGB", [1, 1])
 | 
			
		||||
        assert isinstance(i.size, tuple)
 | 
			
		||||
 | 
			
		||||
    @pytest.mark.timeout(0.75)
 | 
			
		||||
    @pytest.mark.skipif(
 | 
			
		||||
        "PILLOW_VALGRIND_TEST" in os.environ, reason="Valgrind is slower"
 | 
			
		||||
    )
 | 
			
		||||
    @pytest.mark.parametrize("size", ((0, 100000000), (100000000, 0)))
 | 
			
		||||
    def test_empty_image(self, size):
 | 
			
		||||
        Image.new("RGB", size)
 | 
			
		||||
 | 
			
		||||
    def test_storage_neg(self):
 | 
			
		||||
        # Storage.c accepted negative values for xsize, ysize.  Was
 | 
			
		||||
        # test_neg_ppm, but the core function for that has been
 | 
			
		||||
| 
						 | 
				
			
			@ -921,12 +939,7 @@ class TestImage:
 | 
			
		|||
        with pytest.warns(DeprecationWarning):
 | 
			
		||||
            assert Image.CONTAINER == 2
 | 
			
		||||
 | 
			
		||||
    def test_constants_deprecation(self):
 | 
			
		||||
        with pytest.warns(DeprecationWarning):
 | 
			
		||||
            assert Image.NEAREST == 0
 | 
			
		||||
        with pytest.warns(DeprecationWarning):
 | 
			
		||||
            assert Image.NONE == 0
 | 
			
		||||
 | 
			
		||||
    def test_constants(self):
 | 
			
		||||
        with pytest.warns(DeprecationWarning):
 | 
			
		||||
            assert Image.LINEAR == Image.Resampling.BILINEAR
 | 
			
		||||
        with pytest.warns(DeprecationWarning):
 | 
			
		||||
| 
						 | 
				
			
			@ -943,7 +956,6 @@ class TestImage:
 | 
			
		|||
            Image.Quantize,
 | 
			
		||||
        ):
 | 
			
		||||
            for name in enum.__members__:
 | 
			
		||||
                with pytest.warns(DeprecationWarning):
 | 
			
		||||
                assert getattr(Image, name) == enum[name]
 | 
			
		||||
 | 
			
		||||
    @pytest.mark.parametrize(
 | 
			
		||||
| 
						 | 
				
			
			@ -994,7 +1006,6 @@ def mock_encode(*args):
 | 
			
		|||
 | 
			
		||||
class TestRegistry:
 | 
			
		||||
    def test_encode_registry(self):
 | 
			
		||||
 | 
			
		||||
        Image.register_encoder("MOCK", mock_encode)
 | 
			
		||||
        assert "MOCK" in Image.ENCODERS
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -132,22 +132,26 @@ class TestImageGetPixel(AccessTest):
 | 
			
		|||
            return 1
 | 
			
		||||
        return tuple(range(1, bands + 1))
 | 
			
		||||
 | 
			
		||||
    def check(self, mode, c=None):
 | 
			
		||||
        if not c:
 | 
			
		||||
            c = self.color(mode)
 | 
			
		||||
    def check(self, mode, expected_color=None):
 | 
			
		||||
        if not expected_color:
 | 
			
		||||
            expected_color = self.color(mode)
 | 
			
		||||
 | 
			
		||||
        # check putpixel
 | 
			
		||||
        im = Image.new(mode, (1, 1), None)
 | 
			
		||||
        im.putpixel((0, 0), c)
 | 
			
		||||
        assert (
 | 
			
		||||
            im.getpixel((0, 0)) == c
 | 
			
		||||
        ), f"put/getpixel roundtrip failed for mode {mode}, color {c}"
 | 
			
		||||
        im.putpixel((0, 0), expected_color)
 | 
			
		||||
        actual_color = im.getpixel((0, 0))
 | 
			
		||||
        assert actual_color == expected_color, (
 | 
			
		||||
            f"put/getpixel roundtrip failed for mode {mode}, "
 | 
			
		||||
            f"expected {expected_color} got {actual_color}"
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        # check putpixel negative index
 | 
			
		||||
        im.putpixel((-1, -1), c)
 | 
			
		||||
        assert (
 | 
			
		||||
            im.getpixel((-1, -1)) == c
 | 
			
		||||
        ), f"put/getpixel roundtrip negative index failed for mode {mode}, color {c}"
 | 
			
		||||
        im.putpixel((-1, -1), expected_color)
 | 
			
		||||
        actual_color = im.getpixel((-1, -1))
 | 
			
		||||
        assert actual_color == expected_color, (
 | 
			
		||||
            f"put/getpixel roundtrip negative index failed for mode {mode}, "
 | 
			
		||||
            f"expected {expected_color} got {actual_color}"
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        # Check 0
 | 
			
		||||
        im = Image.new(mode, (0, 0), None)
 | 
			
		||||
| 
						 | 
				
			
			@ -155,27 +159,32 @@ class TestImageGetPixel(AccessTest):
 | 
			
		|||
 | 
			
		||||
        error = ValueError if self._need_cffi_access else IndexError
 | 
			
		||||
        with pytest.raises(error):
 | 
			
		||||
            im.putpixel((0, 0), c)
 | 
			
		||||
            im.putpixel((0, 0), expected_color)
 | 
			
		||||
        with pytest.raises(error):
 | 
			
		||||
            im.getpixel((0, 0))
 | 
			
		||||
        # Check 0 negative index
 | 
			
		||||
        with pytest.raises(error):
 | 
			
		||||
            im.putpixel((-1, -1), c)
 | 
			
		||||
            im.putpixel((-1, -1), expected_color)
 | 
			
		||||
        with pytest.raises(error):
 | 
			
		||||
            im.getpixel((-1, -1))
 | 
			
		||||
 | 
			
		||||
        # check initial color
 | 
			
		||||
        im = Image.new(mode, (1, 1), c)
 | 
			
		||||
        assert (
 | 
			
		||||
            im.getpixel((0, 0)) == c
 | 
			
		||||
        ), f"initial color failed for mode {mode}, color {c} "
 | 
			
		||||
        im = Image.new(mode, (1, 1), expected_color)
 | 
			
		||||
        actual_color = im.getpixel((0, 0))
 | 
			
		||||
        assert actual_color == expected_color, (
 | 
			
		||||
            f"initial color failed for mode {mode}, "
 | 
			
		||||
            f"expected {expected_color} got {actual_color}"
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        # check initial color negative index
 | 
			
		||||
        assert (
 | 
			
		||||
            im.getpixel((-1, -1)) == c
 | 
			
		||||
        ), f"initial color failed with negative index for mode {mode}, color {c} "
 | 
			
		||||
        actual_color = im.getpixel((-1, -1))
 | 
			
		||||
        assert actual_color == expected_color, (
 | 
			
		||||
            f"initial color failed with negative index for mode {mode}, "
 | 
			
		||||
            f"expected {expected_color} got {actual_color}"
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        # Check 0
 | 
			
		||||
        im = Image.new(mode, (0, 0), c)
 | 
			
		||||
        im = Image.new(mode, (0, 0), expected_color)
 | 
			
		||||
        with pytest.raises(error):
 | 
			
		||||
            im.getpixel((0, 0))
 | 
			
		||||
        # Check 0 negative index
 | 
			
		||||
| 
						 | 
				
			
			@ -205,13 +214,13 @@ class TestImageGetPixel(AccessTest):
 | 
			
		|||
        self.check(mode)
 | 
			
		||||
 | 
			
		||||
    @pytest.mark.parametrize("mode", ("I;16", "I;16B"))
 | 
			
		||||
    def test_signedness(self, mode):
 | 
			
		||||
    @pytest.mark.parametrize(
 | 
			
		||||
        "expected_color", (2**15 - 1, 2**15, 2**15 + 1, 2**16 - 1)
 | 
			
		||||
    )
 | 
			
		||||
    def test_signedness(self, mode, expected_color):
 | 
			
		||||
        # see https://github.com/python-pillow/Pillow/issues/452
 | 
			
		||||
        # pixelaccess is using signed int* instead of uint*
 | 
			
		||||
        self.check(mode, 2**15 - 1)
 | 
			
		||||
        self.check(mode, 2**15)
 | 
			
		||||
        self.check(mode, 2**15 + 1)
 | 
			
		||||
        self.check(mode, 2**16 - 1)
 | 
			
		||||
        self.check(mode, expected_color)
 | 
			
		||||
 | 
			
		||||
    @pytest.mark.parametrize("mode", ("P", "PA"))
 | 
			
		||||
    @pytest.mark.parametrize("color", ((255, 0, 0), (255, 0, 0, 255)))
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -45,7 +45,6 @@ def test_unsupported_conversion():
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
def test_default():
 | 
			
		||||
 | 
			
		||||
    im = hopper("P")
 | 
			
		||||
    assert im.mode == "P"
 | 
			
		||||
    converted_im = im.convert()
 | 
			
		||||
| 
						 | 
				
			
			@ -104,6 +103,13 @@ def test_rgba_p():
 | 
			
		|||
    assert_image_similar(im, comparable, 20)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_rgba():
 | 
			
		||||
    with Image.open("Tests/images/transparent.png") as im:
 | 
			
		||||
        assert im.mode == "RGBA"
 | 
			
		||||
 | 
			
		||||
        assert_image_similar(im.convert("RGBa").convert("RGB"), im.convert("RGB"), 1.5)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_trns_p(tmp_path):
 | 
			
		||||
    im = hopper("P")
 | 
			
		||||
    im.info["transparency"] = 0
 | 
			
		||||
| 
						 | 
				
			
			@ -248,17 +254,6 @@ def test_p2pa_palette():
 | 
			
		|||
    assert im_pa.getpalette() == im.getpalette()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.mark.parametrize("mode", ("RGB", "RGBA", "RGBX"))
 | 
			
		||||
def test_rgb_lab(mode):
 | 
			
		||||
    im = Image.new(mode, (1, 1))
 | 
			
		||||
    converted_im = im.convert("LAB")
 | 
			
		||||
    assert converted_im.getpixel((0, 0)) == (0, 128, 128)
 | 
			
		||||
 | 
			
		||||
    im = Image.new("LAB", (1, 1), (255, 0, 0))
 | 
			
		||||
    converted_im = im.convert(mode)
 | 
			
		||||
    assert converted_im.getpixel((0, 0))[:3] == (0, 255, 255)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_matrix_illegal_conversion():
 | 
			
		||||
    # Arrange
 | 
			
		||||
    im = hopper("CMYK")
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -86,7 +86,6 @@ def test_crop_crash():
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
def test_crop_zero():
 | 
			
		||||
 | 
			
		||||
    im = Image.new("RGB", (0, 0), "white")
 | 
			
		||||
 | 
			
		||||
    cropped = im.crop((0, 0, 0, 0))
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -24,6 +24,7 @@ from .helper import assert_image_equal, hopper
 | 
			
		|||
        ImageFilter.ModeFilter,
 | 
			
		||||
        ImageFilter.GaussianBlur,
 | 
			
		||||
        ImageFilter.GaussianBlur(5),
 | 
			
		||||
        ImageFilter.BoxBlur(0),
 | 
			
		||||
        ImageFilter.BoxBlur(5),
 | 
			
		||||
        ImageFilter.UnsharpMask,
 | 
			
		||||
        ImageFilter.UnsharpMask(10),
 | 
			
		||||
| 
						 | 
				
			
			@ -173,3 +174,14 @@ def test_consistency_5x5(mode):
 | 
			
		|||
                Image.merge(mode, source[: len(mode)]).filter(kernel),
 | 
			
		||||
                Image.merge(mode, reference[: len(mode)]),
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_invalid_box_blur_filter():
 | 
			
		||||
    with pytest.raises(ValueError):
 | 
			
		||||
        ImageFilter.BoxBlur(-2)
 | 
			
		||||
 | 
			
		||||
    im = hopper()
 | 
			
		||||
    box_blur_filter = ImageFilter.BoxBlur(2)
 | 
			
		||||
    box_blur_filter.radius = -2
 | 
			
		||||
    with pytest.raises(ValueError):
 | 
			
		||||
        im.filter(box_blur_filter)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,10 +1,17 @@
 | 
			
		|||
import pytest
 | 
			
		||||
 | 
			
		||||
from PIL import Image
 | 
			
		||||
 | 
			
		||||
from .helper import assert_image_equal, hopper
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_sanity():
 | 
			
		||||
@pytest.mark.parametrize("data_type", ("bytes", "memoryview"))
 | 
			
		||||
def test_sanity(data_type):
 | 
			
		||||
    im1 = hopper()
 | 
			
		||||
    im2 = Image.frombytes(im1.mode, im1.size, im1.tobytes())
 | 
			
		||||
 | 
			
		||||
    data = im1.tobytes()
 | 
			
		||||
    if data_type == "memoryview":
 | 
			
		||||
        data = memoryview(data)
 | 
			
		||||
    im2 = Image.frombytes(im1.mode, im1.size, data)
 | 
			
		||||
 | 
			
		||||
    assert_image_equal(im1, im2)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,7 +4,6 @@ from .helper import hopper
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
def test_sanity():
 | 
			
		||||
 | 
			
		||||
    bbox = hopper().getbbox()
 | 
			
		||||
    assert isinstance(bbox, tuple)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,10 +1,11 @@
 | 
			
		|||
import pytest
 | 
			
		||||
 | 
			
		||||
from PIL import Image, ImageMode
 | 
			
		||||
 | 
			
		||||
from .helper import hopper
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_sanity():
 | 
			
		||||
 | 
			
		||||
    with hopper() as im:
 | 
			
		||||
        im.mode
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -49,23 +50,25 @@ def test_sanity():
 | 
			
		|||
    assert m.typestr == "|u1"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_properties():
 | 
			
		||||
    def check(mode, *result):
 | 
			
		||||
        signature = (
 | 
			
		||||
            Image.getmodebase(mode),
 | 
			
		||||
            Image.getmodetype(mode),
 | 
			
		||||
            Image.getmodebands(mode),
 | 
			
		||||
            Image.getmodebandnames(mode),
 | 
			
		||||
        )
 | 
			
		||||
        assert signature == result
 | 
			
		||||
 | 
			
		||||
    check("1", "L", "L", 1, ("1",))
 | 
			
		||||
    check("L", "L", "L", 1, ("L",))
 | 
			
		||||
    check("P", "P", "L", 1, ("P",))
 | 
			
		||||
    check("I", "L", "I", 1, ("I",))
 | 
			
		||||
    check("F", "L", "F", 1, ("F",))
 | 
			
		||||
    check("RGB", "RGB", "L", 3, ("R", "G", "B"))
 | 
			
		||||
    check("RGBA", "RGB", "L", 4, ("R", "G", "B", "A"))
 | 
			
		||||
    check("RGBX", "RGB", "L", 4, ("R", "G", "B", "X"))
 | 
			
		||||
    check("CMYK", "RGB", "L", 4, ("C", "M", "Y", "K"))
 | 
			
		||||
    check("YCbCr", "RGB", "L", 3, ("Y", "Cb", "Cr"))
 | 
			
		||||
@pytest.mark.parametrize(
 | 
			
		||||
    "mode, expected_base, expected_type, expected_bands, expected_band_names",
 | 
			
		||||
    (
 | 
			
		||||
        ("1", "L", "L", 1, ("1",)),
 | 
			
		||||
        ("L", "L", "L", 1, ("L",)),
 | 
			
		||||
        ("P", "P", "L", 1, ("P",)),
 | 
			
		||||
        ("I", "L", "I", 1, ("I",)),
 | 
			
		||||
        ("F", "L", "F", 1, ("F",)),
 | 
			
		||||
        ("RGB", "RGB", "L", 3, ("R", "G", "B")),
 | 
			
		||||
        ("RGBA", "RGB", "L", 4, ("R", "G", "B", "A")),
 | 
			
		||||
        ("RGBX", "RGB", "L", 4, ("R", "G", "B", "X")),
 | 
			
		||||
        ("CMYK", "RGB", "L", 4, ("C", "M", "Y", "K")),
 | 
			
		||||
        ("YCbCr", "RGB", "L", 3, ("Y", "Cb", "Cr")),
 | 
			
		||||
    ),
 | 
			
		||||
)
 | 
			
		||||
def test_properties(
 | 
			
		||||
    mode, expected_base, expected_type, expected_bands, expected_band_names
 | 
			
		||||
):
 | 
			
		||||
    assert Image.getmodebase(mode) == expected_base
 | 
			
		||||
    assert Image.getmodetype(mode) == expected_type
 | 
			
		||||
    assert Image.getmodebands(mode) == expected_bands
 | 
			
		||||
    assert Image.getmodebandnames(mode) == expected_band_names
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -55,10 +55,11 @@ def test_mode_with_L_with_float():
 | 
			
		|||
    assert im.getpixel((0, 0)) == 2
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_mode_i():
 | 
			
		||||
@pytest.mark.parametrize("mode", ("I", "I;16", "I;16L", "I;16B"))
 | 
			
		||||
def test_mode_i(mode):
 | 
			
		||||
    src = hopper("L")
 | 
			
		||||
    data = list(src.getdata())
 | 
			
		||||
    im = Image.new("I", src.size, 0)
 | 
			
		||||
    im = Image.new(mode, src.size, 0)
 | 
			
		||||
    im.putdata(data, 2, 256)
 | 
			
		||||
 | 
			
		||||
    target = [2 * elt + 256 for elt in data]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -135,7 +135,6 @@ class TestImagingCoreResampleAccuracy:
 | 
			
		|||
 | 
			
		||||
    @pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L"))
 | 
			
		||||
    def test_reduce_bicubic(self, mode):
 | 
			
		||||
        for mode in ["RGBX", "RGB", "La", "L"]:
 | 
			
		||||
        case = self.make_case(mode, (12, 12), 0xE1)
 | 
			
		||||
        case = case.resize((6, 6), Image.Resampling.BICUBIC)
 | 
			
		||||
        # fmt: off
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -52,7 +52,7 @@ def test_resample():
 | 
			
		|||
    # >>> im.save('Tests/images/hopper_45.png')
 | 
			
		||||
 | 
			
		||||
    with Image.open("Tests/images/hopper_45.png") as target:
 | 
			
		||||
        for (resample, epsilon) in (
 | 
			
		||||
        for resample, epsilon in (
 | 
			
		||||
            (Image.Resampling.NEAREST, 10),
 | 
			
		||||
            (Image.Resampling.BILINEAR, 5),
 | 
			
		||||
            (Image.Resampling.BICUBIC, 0),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,7 +4,6 @@ from .helper import assert_image_equal, fromstring, hopper
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
def test_sanity():
 | 
			
		||||
 | 
			
		||||
    with pytest.raises(ValueError):
 | 
			
		||||
        hopper().tobitmap()
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -42,12 +42,12 @@ class TestImageTransform:
 | 
			
		|||
    def test_extent(self):
 | 
			
		||||
        im = hopper("RGB")
 | 
			
		||||
        (w, h) = im.size
 | 
			
		||||
        # fmt: off
 | 
			
		||||
        transformed = im.transform(im.size, Image.Transform.EXTENT,
 | 
			
		||||
                                   (0, 0,
 | 
			
		||||
                                    w//2, h//2),  # ul -> lr
 | 
			
		||||
                                   Image.Resampling.BILINEAR)
 | 
			
		||||
        # fmt: on
 | 
			
		||||
        transformed = im.transform(
 | 
			
		||||
            im.size,
 | 
			
		||||
            Image.Transform.EXTENT,
 | 
			
		||||
            (0, 0, w // 2, h // 2),  # ul -> lr
 | 
			
		||||
            Image.Resampling.BILINEAR,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        scaled = im.resize((w * 2, h * 2), Image.Resampling.BILINEAR).crop((0, 0, w, h))
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -58,13 +58,12 @@ class TestImageTransform:
 | 
			
		|||
        # one simple quad transform, equivalent to scale & crop upper left quad
 | 
			
		||||
        im = hopper("RGB")
 | 
			
		||||
        (w, h) = im.size
 | 
			
		||||
        # fmt: off
 | 
			
		||||
        transformed = im.transform(im.size, Image.Transform.QUAD,
 | 
			
		||||
                                   (0, 0, 0, h//2,
 | 
			
		||||
                                    # ul -> ccw around quad:
 | 
			
		||||
                                    w//2, h//2, w//2, 0),
 | 
			
		||||
                                   Image.Resampling.BILINEAR)
 | 
			
		||||
        # fmt: on
 | 
			
		||||
        transformed = im.transform(
 | 
			
		||||
            im.size,
 | 
			
		||||
            Image.Transform.QUAD,
 | 
			
		||||
            (0, 0, 0, h // 2, w // 2, h // 2, w // 2, 0),  # ul -> ccw around quad
 | 
			
		||||
            Image.Resampling.BILINEAR,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        scaled = im.transform(
 | 
			
		||||
            (w, h),
 | 
			
		||||
| 
						 | 
				
			
			@ -99,16 +98,21 @@ class TestImageTransform:
 | 
			
		|||
        # this should be a checkerboard of halfsized hoppers in ul, lr
 | 
			
		||||
        im = hopper("RGBA")
 | 
			
		||||
        (w, h) = im.size
 | 
			
		||||
        # fmt: off
 | 
			
		||||
        transformed = im.transform(im.size, Image.Transform.MESH,
 | 
			
		||||
                                   [((0, 0, w//2, h//2),  # box
 | 
			
		||||
                                    (0, 0, 0, h,
 | 
			
		||||
                                     w, h, w, 0)),  # ul -> ccw around quad
 | 
			
		||||
                                    ((w//2, h//2, w, h),  # box
 | 
			
		||||
                                    (0, 0, 0, h,
 | 
			
		||||
                                     w, h, w, 0))],  # ul -> ccw around quad
 | 
			
		||||
                                   Image.Resampling.BILINEAR)
 | 
			
		||||
        # fmt: on
 | 
			
		||||
        transformed = im.transform(
 | 
			
		||||
            im.size,
 | 
			
		||||
            Image.Transform.MESH,
 | 
			
		||||
            (
 | 
			
		||||
                (
 | 
			
		||||
                    (0, 0, w // 2, h // 2),  # box
 | 
			
		||||
                    (0, 0, 0, h, w, h, w, 0),  # ul -> ccw around quad
 | 
			
		||||
                ),
 | 
			
		||||
                (
 | 
			
		||||
                    (w // 2, h // 2, w, h),  # box
 | 
			
		||||
                    (0, 0, 0, h, w, h, w, 0),  # ul -> ccw around quad
 | 
			
		||||
                ),
 | 
			
		||||
            ),
 | 
			
		||||
            Image.Resampling.BILINEAR,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        scaled = im.transform(
 | 
			
		||||
            (w // 2, h // 2),
 | 
			
		||||
| 
						 | 
				
			
			@ -174,11 +178,13 @@ class TestImageTransform:
 | 
			
		|||
 | 
			
		||||
        im = op(im, (40, 10))
 | 
			
		||||
 | 
			
		||||
        colors = im.getcolors()
 | 
			
		||||
        assert colors == [
 | 
			
		||||
        colors = sorted(im.getcolors())
 | 
			
		||||
        assert colors == sorted(
 | 
			
		||||
            (
 | 
			
		||||
                (20 * 10, opaque),
 | 
			
		||||
                (20 * 10, transparent),
 | 
			
		||||
        ]
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    @pytest.mark.parametrize("mode", ("RGBA", "LA"))
 | 
			
		||||
    def test_nearest_resize(self, mode):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -50,7 +50,6 @@ def test_add():
 | 
			
		|||
    # Arrange
 | 
			
		||||
    with Image.open("Tests/images/imagedraw_ellipse_RGB.png") as im1:
 | 
			
		||||
        with Image.open("Tests/images/imagedraw_floodfill_RGB.png") as im2:
 | 
			
		||||
 | 
			
		||||
            # Act
 | 
			
		||||
            new = ImageChops.add(im1, im2)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -63,7 +62,6 @@ def test_add_scale_offset():
 | 
			
		|||
    # Arrange
 | 
			
		||||
    with Image.open("Tests/images/imagedraw_ellipse_RGB.png") as im1:
 | 
			
		||||
        with Image.open("Tests/images/imagedraw_floodfill_RGB.png") as im2:
 | 
			
		||||
 | 
			
		||||
            # Act
 | 
			
		||||
            new = ImageChops.add(im1, im2, scale=2.5, offset=100)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -87,7 +85,6 @@ def test_add_modulo():
 | 
			
		|||
    # Arrange
 | 
			
		||||
    with Image.open("Tests/images/imagedraw_ellipse_RGB.png") as im1:
 | 
			
		||||
        with Image.open("Tests/images/imagedraw_floodfill_RGB.png") as im2:
 | 
			
		||||
 | 
			
		||||
            # Act
 | 
			
		||||
            new = ImageChops.add_modulo(im1, im2)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -111,7 +108,6 @@ def test_blend():
 | 
			
		|||
    # Arrange
 | 
			
		||||
    with Image.open("Tests/images/imagedraw_ellipse_RGB.png") as im1:
 | 
			
		||||
        with Image.open("Tests/images/imagedraw_floodfill_RGB.png") as im2:
 | 
			
		||||
 | 
			
		||||
            # Act
 | 
			
		||||
            new = ImageChops.blend(im1, im2, 0.5)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -137,7 +133,6 @@ def test_darker_image():
 | 
			
		|||
    # Arrange
 | 
			
		||||
    with Image.open("Tests/images/imagedraw_chord_RGB.png") as im1:
 | 
			
		||||
        with Image.open("Tests/images/imagedraw_outline_chord_RGB.png") as im2:
 | 
			
		||||
 | 
			
		||||
            # Act
 | 
			
		||||
            new = ImageChops.darker(im1, im2)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -149,7 +144,6 @@ def test_darker_pixel():
 | 
			
		|||
    # Arrange
 | 
			
		||||
    im1 = hopper()
 | 
			
		||||
    with Image.open("Tests/images/imagedraw_chord_RGB.png") as im2:
 | 
			
		||||
 | 
			
		||||
        # Act
 | 
			
		||||
        new = ImageChops.darker(im1, im2)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -161,7 +155,6 @@ def test_difference():
 | 
			
		|||
    # Arrange
 | 
			
		||||
    with Image.open("Tests/images/imagedraw_arc_end_le_start.png") as im1:
 | 
			
		||||
        with Image.open("Tests/images/imagedraw_arc_no_loops.png") as im2:
 | 
			
		||||
 | 
			
		||||
            # Act
 | 
			
		||||
            new = ImageChops.difference(im1, im2)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -173,7 +166,6 @@ def test_difference_pixel():
 | 
			
		|||
    # Arrange
 | 
			
		||||
    im1 = hopper()
 | 
			
		||||
    with Image.open("Tests/images/imagedraw_polygon_kite_RGB.png") as im2:
 | 
			
		||||
 | 
			
		||||
        # Act
 | 
			
		||||
        new = ImageChops.difference(im1, im2)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -195,7 +187,6 @@ def test_duplicate():
 | 
			
		|||
def test_invert():
 | 
			
		||||
    # Arrange
 | 
			
		||||
    with Image.open("Tests/images/imagedraw_floodfill_RGB.png") as im:
 | 
			
		||||
 | 
			
		||||
        # Act
 | 
			
		||||
        new = ImageChops.invert(im)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -209,7 +200,6 @@ def test_lighter_image():
 | 
			
		|||
    # Arrange
 | 
			
		||||
    with Image.open("Tests/images/imagedraw_chord_RGB.png") as im1:
 | 
			
		||||
        with Image.open("Tests/images/imagedraw_outline_chord_RGB.png") as im2:
 | 
			
		||||
 | 
			
		||||
            # Act
 | 
			
		||||
            new = ImageChops.lighter(im1, im2)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -221,7 +211,6 @@ def test_lighter_pixel():
 | 
			
		|||
    # Arrange
 | 
			
		||||
    im1 = hopper()
 | 
			
		||||
    with Image.open("Tests/images/imagedraw_chord_RGB.png") as im2:
 | 
			
		||||
 | 
			
		||||
        # Act
 | 
			
		||||
        new = ImageChops.lighter(im1, im2)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -275,7 +264,6 @@ def test_offset():
 | 
			
		|||
    xoffset = 45
 | 
			
		||||
    yoffset = 20
 | 
			
		||||
    with Image.open("Tests/images/imagedraw_ellipse_RGB.png") as im:
 | 
			
		||||
 | 
			
		||||
        # Act
 | 
			
		||||
        new = ImageChops.offset(im, xoffset, yoffset)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -292,7 +280,6 @@ def test_screen():
 | 
			
		|||
    # Arrange
 | 
			
		||||
    with Image.open("Tests/images/imagedraw_ellipse_RGB.png") as im1:
 | 
			
		||||
        with Image.open("Tests/images/imagedraw_floodfill_RGB.png") as im2:
 | 
			
		||||
 | 
			
		||||
            # Act
 | 
			
		||||
            new = ImageChops.screen(im1, im2)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -305,7 +292,6 @@ def test_subtract():
 | 
			
		|||
    # Arrange
 | 
			
		||||
    with Image.open("Tests/images/imagedraw_chord_RGB.png") as im1:
 | 
			
		||||
        with Image.open("Tests/images/imagedraw_outline_chord_RGB.png") as im2:
 | 
			
		||||
 | 
			
		||||
            # Act
 | 
			
		||||
            new = ImageChops.subtract(im1, im2)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -319,7 +305,6 @@ def test_subtract_scale_offset():
 | 
			
		|||
    # Arrange
 | 
			
		||||
    with Image.open("Tests/images/imagedraw_chord_RGB.png") as im1:
 | 
			
		||||
        with Image.open("Tests/images/imagedraw_outline_chord_RGB.png") as im2:
 | 
			
		||||
 | 
			
		||||
            # Act
 | 
			
		||||
            new = ImageChops.subtract(im1, im2, scale=2.5, offset=100)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -332,7 +317,6 @@ def test_subtract_clip():
 | 
			
		|||
    # Arrange
 | 
			
		||||
    im1 = hopper()
 | 
			
		||||
    with Image.open("Tests/images/imagedraw_chord_RGB.png") as im2:
 | 
			
		||||
 | 
			
		||||
        # Act
 | 
			
		||||
        new = ImageChops.subtract(im1, im2)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -344,7 +328,6 @@ def test_subtract_modulo():
 | 
			
		|||
    # Arrange
 | 
			
		||||
    with Image.open("Tests/images/imagedraw_chord_RGB.png") as im1:
 | 
			
		||||
        with Image.open("Tests/images/imagedraw_outline_chord_RGB.png") as im2:
 | 
			
		||||
 | 
			
		||||
            # Act
 | 
			
		||||
            new = ImageChops.subtract_modulo(im1, im2)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -358,7 +341,6 @@ def test_subtract_modulo_no_clip():
 | 
			
		|||
    # Arrange
 | 
			
		||||
    im1 = hopper()
 | 
			
		||||
    with Image.open("Tests/images/imagedraw_chord_RGB.png") as im2:
 | 
			
		||||
 | 
			
		||||
        # Act
 | 
			
		||||
        new = ImageChops.subtract_modulo(im1, im2)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -370,7 +352,6 @@ def test_soft_light():
 | 
			
		|||
    # Arrange
 | 
			
		||||
    with Image.open("Tests/images/hopper.png") as im1:
 | 
			
		||||
        with Image.open("Tests/images/hopper-XYZ.png") as im2:
 | 
			
		||||
 | 
			
		||||
            # Act
 | 
			
		||||
            new = ImageChops.soft_light(im1, im2)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -383,7 +364,6 @@ def test_hard_light():
 | 
			
		|||
    # Arrange
 | 
			
		||||
    with Image.open("Tests/images/hopper.png") as im1:
 | 
			
		||||
        with Image.open("Tests/images/hopper-XYZ.png") as im2:
 | 
			
		||||
 | 
			
		||||
            # Act
 | 
			
		||||
            new = ImageChops.hard_light(im1, im2)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -396,7 +376,6 @@ def test_overlay():
 | 
			
		|||
    # Arrange
 | 
			
		||||
    with Image.open("Tests/images/hopper.png") as im1:
 | 
			
		||||
        with Image.open("Tests/images/hopper-XYZ.png") as im2:
 | 
			
		||||
 | 
			
		||||
            # Act
 | 
			
		||||
            new = ImageChops.overlay(im1, im2)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -625,3 +625,14 @@ def test_constants_deprecation():
 | 
			
		|||
        for name in enum.__members__:
 | 
			
		||||
            with pytest.warns(DeprecationWarning):
 | 
			
		||||
                assert getattr(ImageCms, prefix + name) == enum[name]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.mark.parametrize("mode", ("RGB", "RGBA", "RGBX"))
 | 
			
		||||
def test_rgb_lab(mode):
 | 
			
		||||
    im = Image.new(mode, (1, 1))
 | 
			
		||||
    converted_im = im.convert("LAB")
 | 
			
		||||
    assert converted_im.getpixel((0, 0)) == (0, 128, 128)
 | 
			
		||||
 | 
			
		||||
    im = Image.new("LAB", (1, 1), (255, 0, 0))
 | 
			
		||||
    converted_im = im.convert(mode)
 | 
			
		||||
    assert converted_im.getpixel((0, 0))[:3] == (0, 255, 255)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -52,7 +52,6 @@ def test_sanity():
 | 
			
		|||
 | 
			
		||||
def test_valueerror():
 | 
			
		||||
    with Image.open("Tests/images/chi.gif") as im:
 | 
			
		||||
 | 
			
		||||
        draw = ImageDraw.Draw(im)
 | 
			
		||||
        draw.line((0, 0), fill=(0, 0, 0))
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -356,7 +355,13 @@ def ellipse_various_sizes_helper(filled):
 | 
			
		|||
    for w in ellipse_sizes:
 | 
			
		||||
        y = 1
 | 
			
		||||
        for h in ellipse_sizes:
 | 
			
		||||
            border = [x, y, x + w - 1, y + h - 1]
 | 
			
		||||
            x1 = x + w
 | 
			
		||||
            if w:
 | 
			
		||||
                x1 -= 1
 | 
			
		||||
            y1 = y + h
 | 
			
		||||
            if h:
 | 
			
		||||
                y1 -= 1
 | 
			
		||||
            border = [x, y, x1, y1]
 | 
			
		||||
            if filled:
 | 
			
		||||
                draw.ellipse(border, fill="white")
 | 
			
		||||
            else:
 | 
			
		||||
| 
						 | 
				
			
			@ -736,6 +741,36 @@ def test_rounded_rectangle(xy):
 | 
			
		|||
    assert_image_equal_tofile(im, "Tests/images/imagedraw_rounded_rectangle.png")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.mark.parametrize("top_left", (True, False))
 | 
			
		||||
@pytest.mark.parametrize("top_right", (True, False))
 | 
			
		||||
@pytest.mark.parametrize("bottom_right", (True, False))
 | 
			
		||||
@pytest.mark.parametrize("bottom_left", (True, False))
 | 
			
		||||
def test_rounded_rectangle_corners(top_left, top_right, bottom_right, bottom_left):
 | 
			
		||||
    corners = (top_left, top_right, bottom_right, bottom_left)
 | 
			
		||||
 | 
			
		||||
    # Arrange
 | 
			
		||||
    im = Image.new("RGB", (200, 200))
 | 
			
		||||
    draw = ImageDraw.Draw(im)
 | 
			
		||||
 | 
			
		||||
    # Act
 | 
			
		||||
    draw.rounded_rectangle(
 | 
			
		||||
        (10, 20, 190, 180), 30, fill="red", outline="green", width=5, corners=corners
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    # Assert
 | 
			
		||||
    suffix = "".join(
 | 
			
		||||
        (
 | 
			
		||||
            ("y" if top_left else "n"),
 | 
			
		||||
            ("y" if top_right else "n"),
 | 
			
		||||
            ("y" if bottom_right else "n"),
 | 
			
		||||
            ("y" if bottom_left else "n"),
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
    assert_image_equal_tofile(
 | 
			
		||||
        im, "Tests/images/imagedraw_rounded_rectangle_corners_" + suffix + ".png"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.mark.parametrize(
 | 
			
		||||
    "xy, radius, type",
 | 
			
		||||
    [
 | 
			
		||||
| 
						 | 
				
			
			@ -903,9 +938,6 @@ def test_square():
 | 
			
		|||
    img, draw = create_base_image_draw((10, 10))
 | 
			
		||||
    draw.rectangle((2, 2, 7, 7), BLACK)
 | 
			
		||||
    assert_image_equal_tofile(img, expected, "square as normal rectangle failed")
 | 
			
		||||
    img, draw = create_base_image_draw((10, 10))
 | 
			
		||||
    draw.rectangle((7, 7, 2, 2), BLACK)
 | 
			
		||||
    assert_image_equal_tofile(img, expected, "square as inverted rectangle failed")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_triangle_right():
 | 
			
		||||
| 
						 | 
				
			
			@ -1470,3 +1502,21 @@ def test_polygon2():
 | 
			
		|||
    draw.polygon([(18, 30), (19, 31), (18, 30), (85, 30), (60, 72)], "red")
 | 
			
		||||
    expected = "Tests/images/imagedraw_outline_polygon_RGB.png"
 | 
			
		||||
    assert_image_similar_tofile(im, expected, 1)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.mark.parametrize("xy", ((1, 1, 0, 1), (1, 1, 1, 0)))
 | 
			
		||||
def test_incorrectly_ordered_coordinates(xy):
 | 
			
		||||
    im = Image.new("RGB", (W, H))
 | 
			
		||||
    draw = ImageDraw.Draw(im)
 | 
			
		||||
    with pytest.raises(ValueError):
 | 
			
		||||
        draw.arc(xy, 10, 260)
 | 
			
		||||
    with pytest.raises(ValueError):
 | 
			
		||||
        draw.chord(xy, 10, 260)
 | 
			
		||||
    with pytest.raises(ValueError):
 | 
			
		||||
        draw.ellipse(xy)
 | 
			
		||||
    with pytest.raises(ValueError):
 | 
			
		||||
        draw.pieslice(xy, 10, 260)
 | 
			
		||||
    with pytest.raises(ValueError):
 | 
			
		||||
        draw.rectangle(xy)
 | 
			
		||||
    with pytest.raises(ValueError):
 | 
			
		||||
        draw.rounded_rectangle(xy)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -30,7 +30,6 @@ SAFEBLOCK = ImageFile.SAFEBLOCK
 | 
			
		|||
class TestImageFile:
 | 
			
		||||
    def test_parser(self):
 | 
			
		||||
        def roundtrip(format):
 | 
			
		||||
 | 
			
		||||
            im = hopper("L").resize((1000, 1000), Image.Resampling.NEAREST)
 | 
			
		||||
            if format in ("MSP", "XBM"):
 | 
			
		||||
                im = im.convert("1")
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||