diff --git a/.appveyor.yml b/.appveyor.yml deleted file mode 100644 index 60132a9a35a..00000000000 --- a/.appveyor.yml +++ /dev/null @@ -1,94 +0,0 @@ -version: '{build}' -clone_folder: c:\pillow -init: -- ECHO %PYTHON% -#- ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) -# Uncomment previous line to get RDP access during the build. - -environment: - EXECUTABLE: python.exe - TEST_OPTIONS: - DEPLOY: YES - matrix: - - PYTHON: C:/Python311 - ARCHITECTURE: x86 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022 - - PYTHON: C:/Python38-x64 - ARCHITECTURE: x64 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 - - -install: -- '%PYTHON%\%EXECUTABLE% --version' -- '%PYTHON%\%EXECUTABLE% -m pip install --upgrade pip' -- curl -fsSL -o pillow-depends.zip https://github.com/python-pillow/pillow-depends/archive/main.zip -- curl -fsSL -o pillow-test-images.zip https://github.com/python-pillow/test-images/archive/main.zip -- 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:\test-images-main\* c:\pillow\tests\images -- 7z x ..\pillow-depends\nasm-2.16.01-win64.zip -oc:\ -- choco install ghostscript --version=10.0.0.20230317 -- path c:\nasm-2.16.01;C:\Program Files\gs\gs10.00.0\bin;%PATH% -- cd c:\pillow\winbuild\ -- ps: | - c:\python38\python.exe c:\pillow\winbuild\build_prepare.py -v --depends=C:\pillow-depends\ - c:\pillow\winbuild\build\build_dep_all.cmd - $host.SetShouldExit(0) -- path C:\pillow\winbuild\build\bin;%PATH% - -build_script: -- cd c:\pillow -- winbuild\build\build_env.cmd -- '%PYTHON%\%EXECUTABLE% -m pip install -v -C raqm=vendor -C fribidi=vendor .' -- '%PYTHON%\%EXECUTABLE% selftest.py --installed' - -test_script: -- cd c:\pillow -- '%PYTHON%\%EXECUTABLE% -m pip install pytest pytest-cov pytest-timeout' -- c:\"Program Files (x86)"\"Windows Kits"\10\Debuggers\x86\gflags.exe /p /enable %PYTHON%\%EXECUTABLE% -- '%PYTHON%\%EXECUTABLE% -c "from PIL import Image"' -- '%PYTHON%\%EXECUTABLE% -m pytest -vx --cov PIL --cov Tests --cov-report term --cov-report xml Tests' -#- '%PYTHON%\%EXECUTABLE% test-installed.py -v -s %TEST_OPTIONS%' TODO TEST_OPTIONS with pytest? - -after_test: -- curl -Os https://uploader.codecov.io/latest/windows/codecov.exe -- .\codecov.exe --file coverage.xml --name %PYTHON% --flags AppVeyor - -matrix: - fast_finish: true - -cache: -- '%LOCALAPPDATA%\pip\Cache' - -artifacts: -- path: pillow\*.egg - name: egg -- path: pillow\*.whl - name: wheel - -before_deploy: - - cd c:\pillow - - '%PYTHON%\%EXECUTABLE% -m pip wheel -v -C raqm=vendor -C fribidi=vendor .' - - ps: Get-ChildItem .\*.whl | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name } - -deploy: - provider: S3 - region: us-west-2 - access_key_id: AKIAIRAXC62ZNTVQJMOQ - secret_access_key: - secure: Hwb6klTqtBeMgxAjRoDltiiqpuH8xbwD4UooDzBSiCWXjuFj1lyl4kHgHwTCCGqi - bucket: pillow-nightly - folder: win/$(APPVEYOR_BUILD_NUMBER)/ - artifact: /.*egg|wheel/ - on: - APPVEYOR_REPO_NAME: python-pillow/Pillow - branch: main - deploy: YES - - -# Uncomment the following lines to get RDP access after the build/test and block for -# up to the timeout limit (~1hr) -# -#on_finish: -#- ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml deleted file mode 100644 index 560d6c7dfea..00000000000 --- a/.github/workflows/cifuzz.yml +++ /dev/null @@ -1,58 +0,0 @@ -name: CIFuzz - -on: - push: - paths: - - ".github/workflows/cifuzz.yml" - - "**.c" - - "**.h" - pull_request: - paths: - - ".github/workflows/cifuzz.yml" - - "**.c" - - "**.h" - workflow_dispatch: - -permissions: - contents: read - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - Fuzzing: - runs-on: ubuntu-latest - steps: - - name: Build Fuzzers - id: build - uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master - with: - oss-fuzz-project-name: 'pillow' - language: python - dry-run: false - - name: Run Fuzzers - id: run - uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master - with: - oss-fuzz-project-name: 'pillow' - fuzz-seconds: 600 - language: python - dry-run: false - - name: Upload New Crash - uses: actions/upload-artifact@v3 - if: failure() && steps.build.outcome == 'success' - with: - name: artifacts - path: ./out/artifacts - - name: Upload Legacy Crash - uses: actions/upload-artifact@v3 - if: steps.run.outcome == 'success' - with: - name: crash - path: ./out/crash* - - name: Fail on legacy crash - if: success() - run: | - [ ! -e out/crash-* ] - echo No legacy crash detected diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml deleted file mode 100644 index 844c7c1ec44..00000000000 --- a/.github/workflows/docs.yml +++ /dev/null @@ -1,55 +0,0 @@ -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 - -env: - FORCE_COLOR: 1 - -jobs: - build: - - runs-on: ubuntu-latest - name: Docs - - steps: - - uses: actions/checkout@v4 - - - 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 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml deleted file mode 100644 index 78b80d26ebe..00000000000 --- a/.github/workflows/lint.yml +++ /dev/null @@ -1,48 +0,0 @@ -name: Lint - -on: [push, pull_request, workflow_dispatch] - -permissions: - contents: read - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - build: - - runs-on: ubuntu-latest - - name: Lint - - steps: - - uses: actions/checkout@v4 - - - name: pre-commit cache - uses: actions/cache@v3 - with: - path: ~/.cache/pre-commit - key: lint-pre-commit-${{ hashFiles('**/.pre-commit-config.yaml') }} - restore-keys: | - lint-pre-commit- - - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: "3.x" - cache: pip - cache-dependency-path: "setup.py" - - - name: Build system information - run: python3 .github/workflows/system-info.py - - - name: Install dependencies - run: | - python3 -m pip install -U pip - python3 -m pip install -U tox - - - name: Lint - run: tox -e lint - env: - PRE_COMMIT_COLOR: always diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml deleted file mode 100644 index 9e2fdc09604..00000000000 --- a/.github/workflows/release-drafter.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: Release drafter - -on: - push: - # branches to consider in the event; optional, defaults to all - branches: - - main - workflow_dispatch: - -permissions: - contents: read - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - update_release_draft: - permissions: - contents: write # for release-drafter/release-drafter to create a github release - pull-requests: write # for release-drafter/release-drafter to add label to PR - if: github.repository == 'python-pillow/Pillow' - runs-on: ubuntu-latest - steps: - # Drafts your next release notes as pull requests are merged into "main" - - uses: release-drafter/release-drafter@v5 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml deleted file mode 100644 index 24b8f85d119..00000000000 --- a/.github/workflows/stale.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: Close stale issues - -on: - schedule: - - cron: "10 0 * * *" - workflow_dispatch: - -permissions: - issues: write - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - stale: - if: github.repository_owner == 'python-pillow' - - runs-on: ubuntu-latest - - steps: - - name: "Check issues" - uses: actions/stale@v8 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - only-labels: "Awaiting OP Action" - close-issue-message: "Closing this issue as no feedback has been received." - days-before-stale: 7 - days-before-issue-close: 0 - days-before-pr-close: -1 - labels-to-remove-when-unstale: "Awaiting OP Action" diff --git a/.github/workflows/test-cygwin.yml b/.github/workflows/test-cygwin.yml deleted file mode 100644 index 949da636069..00000000000 --- a/.github/workflows/test-cygwin.yml +++ /dev/null @@ -1,150 +0,0 @@ -name: Test Cygwin - -on: - push: - paths-ignore: - - ".github/workflows/docs.yml" - - "docs/**" - pull_request: - paths-ignore: - - ".github/workflows/docs.yml" - - "docs/**" - workflow_dispatch: - -permissions: - contents: read - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - build: - runs-on: windows-latest - strategy: - fail-fast: false - matrix: - python-minor-version: [8, 9] - - timeout-minutes: 40 - - name: Python 3.${{ matrix.python-minor-version }} - - steps: - - name: Fix line endings - run: | - git config --global core.autocrlf input - - - name: Checkout Pillow - uses: actions/checkout@v4 - - - name: Install Cygwin - uses: cygwin/cygwin-install-action@v4 - with: - platform: x86_64 - packages: > - 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 - wget - xorg-server-extra - zlib-devel - - - name: Add Lapack to PATH - uses: egor-tensin/cleanup-path@v3 - with: - dirs: 'C:\cygwin\bin;C:\cygwin\lib\lapack' - - - name: Select Python version - run: | - ln -sf c:/cygwin/bin/python3.${{ matrix.python-minor-version }} c:/cygwin/bin/python3 - - - name: Get latest NumPy version - id: latest-numpy - shell: bash.exe -eo pipefail -o igncr "{0}" - run: | - python3 -m pip list --outdated | grep numpy | sed -r 's/ +/ /g' | cut -d ' ' -f 3 | sed 's/^/version=/' >> $GITHUB_OUTPUT - - - name: pip cache - uses: actions/cache@v3 - with: - path: 'C:\cygwin\home\runneradmin\.cache\pip' - key: ${{ runner.os }}-cygwin-pip3.${{ matrix.python-minor-version }}-numpy${{ steps.latest-numpy.outputs.version }}-${{ hashFiles('.ci/install.sh') }} - restore-keys: | - ${{ runner.os }}-cygwin-pip3.${{ matrix.python-minor-version }}-numpy${{ steps.latest-numpy.outputs.version }}- - - - name: Build system information - run: | - dash.exe -c "python3 .github/workflows/system-info.py" - - - name: Install dependencies - run: | - bash.exe .ci/install.sh - - - name: Install latest NumPy - shell: dash.exe -l "{0}" - run: | - python3 -m pip install -U numpy - - - name: Build - shell: bash.exe -eo pipefail -o igncr "{0}" - run: | - .ci/build.sh - - - name: Test - run: | - bash.exe xvfb-run -s '-screen 0 1024x768x24' .ci/test.sh - - - name: Prepare to upload errors - if: failure() - run: | - dash.exe -c "mkdir -p Tests/errors" - - - name: Upload errors - uses: actions/upload-artifact@v3 - if: failure() - with: - name: errors - path: Tests/errors - - - name: After success - run: | - bash.exe .ci/after_success.sh - - - name: Upload coverage - uses: codecov/codecov-action@v3 - with: - file: ./coverage.xml - flags: GHA_Cygwin - name: Cygwin Python 3.${{ matrix.python-minor-version }} - - success: - permissions: - contents: none - needs: build - runs-on: ubuntu-latest - name: Cygwin Test Successful - steps: - - name: Success - run: echo Cygwin Test Successful diff --git a/.github/workflows/test-docker.yml b/.github/workflows/test-docker.yml index a0c7444c63e..b93a8acafa4 100644 --- a/.github/workflows/test-docker.yml +++ b/.github/workflows/test-docker.yml @@ -14,10 +14,6 @@ on: permissions: contents: read -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - jobs: build: @@ -26,35 +22,11 @@ jobs: fail-fast: false matrix: docker: [ - # Run slower jobs first to give them a headstart and reduce waiting time - ubuntu-22.04-jammy-arm64v8, - ubuntu-22.04-jammy-ppc64le, - ubuntu-22.04-jammy-s390x, - # Then run the remainder - alpine, amazon-2-amd64, - amazon-2023-amd64, - arch, centos-7-amd64, centos-stream-8-amd64, - centos-stream-9-amd64, - debian-11-bullseye-amd64, - debian-12-bookworm-x86, - debian-12-bookworm-amd64, - fedora-37-amd64, - fedora-38-amd64, - gentoo, - ubuntu-20.04-focal-amd64, - ubuntu-22.04-jammy-amd64, ] dockerTag: [main] - include: - - docker: "ubuntu-22.04-jammy-arm64v8" - qemu-arch: "aarch64" - - docker: "ubuntu-22.04-jammy-ppc64le" - qemu-arch: "ppc64le" - - docker: "ubuntu-22.04-jammy-s390x" - qemu-arch: "s390x" name: ${{ matrix.docker }} diff --git a/.github/workflows/test-mingw.yml b/.github/workflows/test-mingw.yml deleted file mode 100644 index 08dfb9a2da0..00000000000 --- a/.github/workflows/test-mingw.yml +++ /dev/null @@ -1,81 +0,0 @@ -name: Test MinGW - -on: - push: - paths-ignore: - - ".github/workflows/docs.yml" - - "docs/**" - pull_request: - paths-ignore: - - ".github/workflows/docs.yml" - - "docs/**" - workflow_dispatch: - -permissions: - contents: read - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - build: - runs-on: windows-latest - - defaults: - run: - shell: bash.exe --login -eo pipefail "{0}" - env: - MSYSTEM: MINGW64 - CHERE_INVOKING: 1 - - timeout-minutes: 30 - name: "MinGW" - - steps: - - name: Checkout Pillow - uses: actions/checkout@v4 - - - name: Set up shell - run: echo "C:\msys64\usr\bin\" >> $env:GITHUB_PATH - shell: pwsh - - - name: Install dependencies - run: | - pacman -S --noconfirm \ - mingw-w64-x86_64-freetype \ - mingw-w64-x86_64-gcc \ - mingw-w64-x86_64-ghostscript \ - mingw-w64-x86_64-lcms2 \ - mingw-w64-x86_64-libimagequant \ - mingw-w64-x86_64-libjpeg-turbo \ - mingw-w64-x86_64-libraqm \ - mingw-w64-x86_64-libtiff \ - mingw-w64-x86_64-libwebp \ - mingw-w64-x86_64-openjpeg2 \ - mingw-w64-x86_64-python3-cffi \ - mingw-w64-x86_64-python3-numpy \ - mingw-w64-x86_64-python3-olefile \ - mingw-w64-x86_64-python3-pip \ - mingw-w64-x86_64-python3-setuptools \ - mingw-w64-x86_64-python-pyqt6 - - python3 -m pip install pyroma pytest pytest-cov pytest-timeout - - pushd depends && ./install_extra_test_images.sh && popd - - - name: Build Pillow - run: SETUPTOOLS_USE_DISTUTILS="stdlib" CFLAGS="-coverage" python3 -m pip install . - - - name: Test Pillow - run: | - python3 selftest.py --installed - python3 -c "from PIL import Image" - python3 -m pytest -vx --cov PIL --cov Tests --cov-report term --cov-report xml Tests - - - name: Upload coverage - uses: codecov/codecov-action@v3 - with: - file: ./coverage.xml - flags: GHA_Windows - name: "MSYS2 MinGW" diff --git a/.github/workflows/test-valgrind.yml b/.github/workflows/test-valgrind.yml deleted file mode 100644 index 21968ad5ad0..00000000000 --- a/.github/workflows/test-valgrind.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: Test Valgrind - -# like the docker tests, but running valgrind only on *.c/*.h changes. - -on: - push: - paths: - - ".github/workflows/test-valgrind.yml" - - "**.c" - - "**.h" - pull_request: - paths: - - ".github/workflows/test-valgrind.yml" - - "**.c" - - "**.h" - workflow_dispatch: - -permissions: - contents: read - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - build: - - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - docker: [ - ubuntu-22.04-jammy-amd64-valgrind, - ] - dockerTag: [main] - - name: ${{ matrix.docker }} - - steps: - - uses: actions/checkout@v4 - - - name: Build system information - run: python3 .github/workflows/system-info.py - - - name: Docker pull - run: | - docker pull pythonpillow/${{ matrix.docker }}:${{ matrix.dockerTag }} - - - name: Build and Run Valgrind - run: | - # The Pillow user in the docker container is UID 1000 - sudo chown -R 1000 $GITHUB_WORKSPACE - 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 diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml deleted file mode 100644 index ae3cc6127da..00000000000 --- a/.github/workflows/test-windows.yml +++ /dev/null @@ -1,252 +0,0 @@ -name: Test Windows - -on: - push: - paths-ignore: - - ".github/workflows/docs.yml" - - "docs/**" - pull_request: - paths-ignore: - - ".github/workflows/docs.yml" - - "docs/**" - workflow_dispatch: - -permissions: - contents: read - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - build: - runs-on: windows-latest - strategy: - fail-fast: false - matrix: - python-version: ["pypy3.10", "pypy3.9", "3.8", "3.9", "3.10", "3.11", "3.12-dev"] - - timeout-minutes: 30 - - name: Python ${{ matrix.python-version }} - - steps: - - name: Checkout Pillow - uses: actions/checkout@v4 - - - name: Checkout cached dependencies - uses: actions/checkout@v4 - with: - repository: python-pillow/pillow-depends - path: winbuild\depends - - - name: Checkout extra test images - uses: actions/checkout@v4 - with: - repository: python-pillow/test-images - path: Tests\test-images - - # sets env: pythonLocation - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - cache: pip - cache-dependency-path: ".github/workflows/test-windows.yml" - - - name: Print build system information - run: python3 .github/workflows/system-info.py - - - name: python3 -m pip install pytest pytest-cov pytest-timeout defusedxml - run: python3 -m pip install pytest pytest-cov pytest-timeout defusedxml - - - name: Install dependencies - id: install - run: | - 7z x winbuild\depends\nasm-2.16.01-win64.zip "-o$env:RUNNER_WORKSPACE\" - echo "$env:RUNNER_WORKSPACE\nasm-2.16.01" >> $env:GITHUB_PATH - - choco install ghostscript --version=10.0.0.20230317 - echo "C:\Program Files\gs\gs10.00.0\bin" >> $env:GITHUB_PATH - - # 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" ` - | find """catalog_buildVersion""" ` - | ForEach-Object { $a = $_.split(" ")[1]; echo "vs=$a" >> $env:GITHUB_OUTPUT } - shell: pwsh - - - name: Cache build - id: build-cache - uses: actions/cache@v3 - with: - path: winbuild\build - key: - ${{ hashFiles('winbuild\build_prepare.py') }}-${{ hashFiles('.github\workflows\test-windows.yml') }}-${{ env.pythonLocation }}-${{ steps.install.outputs.vs }} - - - name: Prepare build - if: steps.build-cache.outputs.cache-hit != 'true' - run: | - & python.exe winbuild\build_prepare.py -v - shell: pwsh - - - name: Build dependencies / libjpeg-turbo - if: steps.build-cache.outputs.cache-hit != 'true' - run: "& winbuild\\build\\build_dep_libjpeg.cmd" - - - name: Build dependencies / zlib - if: steps.build-cache.outputs.cache-hit != 'true' - run: "& winbuild\\build\\build_dep_zlib.cmd" - - - name: Build dependencies / xz - if: steps.build-cache.outputs.cache-hit != 'true' - run: "& winbuild\\build\\build_dep_xz.cmd" - - - name: Build dependencies / WebP - if: steps.build-cache.outputs.cache-hit != 'true' - run: "& winbuild\\build\\build_dep_libwebp.cmd" - - - name: Build dependencies / LibTiff - if: steps.build-cache.outputs.cache-hit != 'true' - run: "& winbuild\\build\\build_dep_libtiff.cmd" - - # for FreeType CBDT/SBIX font support - - name: Build dependencies / libpng - if: steps.build-cache.outputs.cache-hit != 'true' - run: "& winbuild\\build\\build_dep_libpng.cmd" - - # for FreeType WOFF2 font support - - name: Build dependencies / brotli - if: steps.build-cache.outputs.cache-hit != 'true' - run: "& winbuild\\build\\build_dep_brotli.cmd" - - - name: Build dependencies / FreeType - if: steps.build-cache.outputs.cache-hit != 'true' - run: "& winbuild\\build\\build_dep_freetype.cmd" - - - name: Build dependencies / LCMS2 - if: steps.build-cache.outputs.cache-hit != 'true' - run: "& winbuild\\build\\build_dep_lcms2.cmd" - - - name: Build dependencies / OpenJPEG - if: steps.build-cache.outputs.cache-hit != 'true' - run: "& winbuild\\build\\build_dep_openjpeg.cmd" - - # GPL licensed - - name: Build dependencies / libimagequant - if: steps.build-cache.outputs.cache-hit != 'true' - run: "& winbuild\\build\\build_dep_libimagequant.cmd" - - # Raqm dependencies - - name: Build dependencies / HarfBuzz - if: steps.build-cache.outputs.cache-hit != 'true' - run: "& winbuild\\build\\build_dep_harfbuzz.cmd" - - # Raqm dependencies - - name: Build dependencies / FriBidi - if: steps.build-cache.outputs.cache-hit != 'true' - run: "& winbuild\\build\\build_dep_fribidi.cmd" - - # trim ~150MB for each job - - name: Optimize build cache - if: steps.build-cache.outputs.cache-hit != 'true' - run: rmdir /S /Q winbuild\build\src - shell: cmd - - - name: Build Pillow - run: | - $FLAGS="-C raqm=vendor -C fribidi=vendor" - if ('${{ github.event_name }}' -ne 'pull_request') { $FLAGS+=" -C imagequant=disable" } - cmd /c "winbuild\build\build_env.cmd && $env:pythonLocation\python.exe -m pip install -v $FLAGS ." - & $env:pythonLocation\python.exe selftest.py --installed - shell: pwsh - - # skip PyPy for speed - - name: Enable heap verification - if: "!contains(matrix.python-version, 'pypy')" - run: | - & reg.exe add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\python.exe" /v "GlobalFlag" /t REG_SZ /d "0x02000000" /f - - - name: Test Pillow - run: | - path %GITHUB_WORKSPACE%\\winbuild\\build\\bin;%PATH% - python.exe -m pytest -vx -W always --cov PIL --cov Tests --cov-report term --cov-report xml Tests - shell: cmd - - - name: Prepare to upload errors - if: failure() - run: | - mkdir -p Tests/errors - shell: bash - - - name: Upload errors - uses: actions/upload-artifact@v3 - if: failure() - with: - name: errors - path: Tests/errors - - - name: After success - run: | - .ci/after_success.sh - shell: pwsh - - - name: Upload coverage - uses: codecov/codecov-action@v3 - with: - file: ./coverage.xml - flags: GHA_Windows - name: ${{ runner.os }} Python ${{ matrix.python-version }} - - - name: Build wheel - id: wheel - if: "github.event_name != 'pull_request'" - run: | - mkdir fribidi - copy winbuild\build\bin\fribidi* fribidi - setlocal EnableDelayedExpansion - for %%f in (winbuild\build\license\*) do ( - set x=%%~nf - rem Skip FriBiDi license, it is not included in the wheel. - set fribidi=!x:~0,7! - if NOT !fribidi!==fribidi ( - rem Skip imagequant license, it is not included in the wheel. - set libimagequant=!x:~0,13! - if NOT !libimagequant!==libimagequant ( - echo. >> LICENSE - echo ===== %%~nf ===== >> LICENSE - echo. >> LICENSE - type %%f >> LICENSE - ) - ) - ) - for /f "tokens=3 delims=/" %%a in ("${{ github.ref }}") do echo dist=dist-%%a >> %GITHUB_OUTPUT% - call winbuild\\build\\build_env.cmd - %pythonLocation%\python.exe -m pip wheel -v -C raqm=vendor -C fribidi=vendor -C imagequant=disable . - shell: cmd - - - name: Upload wheel - uses: actions/upload-artifact@v3 - if: "github.event_name != 'pull_request'" - with: - name: ${{ steps.wheel.outputs.dist }} - path: "*.whl" - - - name: Upload fribidi.dll - if: "github.event_name != 'pull_request' && matrix.python-version == 3.11" - uses: actions/upload-artifact@v3 - with: - name: fribidi - path: fribidi\* - - success: - permissions: - contents: none - needs: build - runs-on: ubuntu-latest - name: Windows Test Successful - steps: - - name: Success - run: echo Windows Test Successful diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index 2f43f4b5599..00000000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,128 +0,0 @@ -name: Test - -on: - push: - paths-ignore: - - ".github/workflows/docs.yml" - - "docs/**" - pull_request: - paths-ignore: - - ".github/workflows/docs.yml" - - "docs/**" - workflow_dispatch: - -permissions: - contents: read - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - build: - - strategy: - fail-fast: false - matrix: - os: [ - "macos-latest", - "ubuntu-latest", - ] - python-version: [ - "pypy3.10", - "pypy3.9", - "3.12-dev", - "3.11", - "3.10", - "3.9", - "3.8", - ] - include: - - python-version: "3.9" - PYTHONOPTIMIZE: 1 - REVERSE: "--reverse" - - python-version: "3.8" - PYTHONOPTIMIZE: 2 - - runs-on: ${{ matrix.os }} - name: ${{ matrix.os }} Python ${{ matrix.python-version }} - - steps: - - uses: actions/checkout@v4 - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - cache: pip - cache-dependency-path: ".ci/*.sh" - - - name: Build system information - run: python3 .github/workflows/system-info.py - - - name: Install Linux dependencies - if: startsWith(matrix.os, 'ubuntu') - run: | - .ci/install.sh - env: - GHA_PYTHON_VERSION: ${{ matrix.python-version }} - - - name: Install macOS dependencies - if: startsWith(matrix.os, 'macOS') - run: | - .github/workflows/macos-install.sh - env: - GHA_PYTHON_VERSION: ${{ matrix.python-version }} - - - name: Build - run: | - .ci/build.sh - - - name: Test - run: | - if [ $REVERSE ]; then - python3 -m pip install pytest-reverse - fi - if [ "${{ matrix.os }}" = "ubuntu-latest" ]; then - xvfb-run -s '-screen 0 1024x768x24' sway& - export WAYLAND_DISPLAY=wayland-1 - .ci/test.sh - else - .ci/test.sh - fi - env: - PYTHONOPTIMIZE: ${{ matrix.PYTHONOPTIMIZE }} - REVERSE: ${{ matrix.REVERSE }} - - - name: Prepare to upload errors - if: failure() - run: | - mkdir -p Tests/errors - - - name: Upload errors - uses: actions/upload-artifact@v3 - if: failure() - with: - name: errors - path: Tests/errors - - - name: After success - run: | - .ci/after_success.sh - - - name: Upload coverage - uses: codecov/codecov-action@v3 - with: - flags: ${{ matrix.os == 'macos-latest' && 'GHA_macOS' || 'GHA_Ubuntu' }} - name: ${{ matrix.os }} Python ${{ matrix.python-version }} - gcov: true - - success: - permissions: - contents: none - needs: build - runs-on: ubuntu-latest - name: Test Successful - steps: - - name: Success - run: echo Test Successful diff --git a/Tests/test_000_aibtiff.py b/Tests/test_000_aibtiff.py new file mode 100644 index 00000000000..ed3529a1e3d --- /dev/null +++ b/Tests/test_000_aibtiff.py @@ -0,0 +1,66 @@ +import base64 +import io +import itertools +import os +import re +import sys +from collections import namedtuple + +import pytest + +from PIL import Image, ImageFilter, ImageOps, TiffImagePlugin, TiffTags, features +from PIL.TiffImagePlugin import SAMPLEFORMAT, STRIPOFFSETS, SUBIFD + +from .helper import ( + assert_image_equal, + assert_image_equal_tofile, + assert_image_similar, + assert_image_similar_tofile, + hopper, + mark_if_feature_version, + skip_unless_feature, +) + + +@skip_unless_feature("libtiff") +class LibTiffTestCase: + def _assert_noerr(self, tmp_path, im): + """Helper tests that assert basic sanity about the g4 tiff reading""" + # 1 bit + assert im.mode == "1" + + # Does the data actually load + im.load() + im.getdata() + + try: + assert im._compression == "group4" + except AttributeError: + print("No _compression") + print(dir(im)) + + # can we write it back out, in a different form. + out = str(tmp_path / "temp.png") + im.save(out) + + out_bytes = io.BytesIO() + im.save(out_bytes, format="tiff", compression="group4") + + +class TestFileLibTiff(LibTiffTestCase): + @pytest.mark.parametrize( + "path, sizes", + ( + ("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) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py deleted file mode 100644 index a7394f1bf5c..00000000000 --- a/Tests/test_file_libtiff.py +++ /dev/null @@ -1,1142 +0,0 @@ -import base64 -import io -import itertools -import os -import re -import sys -from collections import namedtuple - -import pytest - -from PIL import Image, ImageFilter, ImageOps, TiffImagePlugin, TiffTags, features -from PIL.TiffImagePlugin import SAMPLEFORMAT, STRIPOFFSETS, SUBIFD - -from .helper import ( - assert_image_equal, - assert_image_equal_tofile, - assert_image_similar, - assert_image_similar_tofile, - hopper, - mark_if_feature_version, - skip_unless_feature, -) - - -@skip_unless_feature("libtiff") -class LibTiffTestCase: - def _assert_noerr(self, tmp_path, im): - """Helper tests that assert basic sanity about the g4 tiff reading""" - # 1 bit - assert im.mode == "1" - - # Does the data actually load - im.load() - im.getdata() - - try: - assert im._compression == "group4" - except AttributeError: - print("No _compression") - print(dir(im)) - - # can we write it back out, in a different form. - out = str(tmp_path / "temp.png") - im.save(out) - - out_bytes = io.BytesIO() - im.save(out_bytes, format="tiff", compression="group4") - - -class TestFileLibTiff(LibTiffTestCase): - def test_version(self): - assert re.search(r"\d+\.\d+\.\d+$", features.version_codec("libtiff")) - - def test_g4_tiff(self, tmp_path): - """Test the ordinary file path load path""" - - test_file = "Tests/images/hopper_g4_500.tif" - with Image.open(test_file) as im: - assert im.size == (500, 500) - self._assert_noerr(tmp_path, im) - - def test_g4_large(self, tmp_path): - test_file = "Tests/images/pport_g4.tif" - with Image.open(test_file) as im: - self._assert_noerr(tmp_path, im) - - def test_g4_tiff_file(self, tmp_path): - """Testing the string load path""" - - test_file = "Tests/images/hopper_g4_500.tif" - with open(test_file, "rb") as f: - with Image.open(f) as im: - assert im.size == (500, 500) - self._assert_noerr(tmp_path, im) - - def test_g4_tiff_bytesio(self, tmp_path): - """Testing the stringio loading code path""" - test_file = "Tests/images/hopper_g4_500.tif" - s = io.BytesIO() - with open(test_file, "rb") as f: - s.write(f.read()) - s.seek(0) - with Image.open(s) as im: - assert im.size == (500, 500) - self._assert_noerr(tmp_path, im) - - def test_g4_non_disk_file_object(self, tmp_path): - """Testing loading from non-disk non-BytesIO file object""" - test_file = "Tests/images/hopper_g4_500.tif" - s = io.BytesIO() - with open(test_file, "rb") as f: - s.write(f.read()) - s.seek(0) - r = io.BufferedReader(s) - with Image.open(r) as im: - assert im.size == (500, 500) - self._assert_noerr(tmp_path, im) - - def test_g4_eq_png(self): - """Checking that we're actually getting the data that we expect""" - with Image.open("Tests/images/hopper_bw_500.png") as png: - assert_image_equal_tofile(png, "Tests/images/hopper_g4_500.tif") - - # see https://github.com/python-pillow/Pillow/issues/279 - def test_g4_fillorder_eq_png(self): - """Checking that we're actually getting the data that we expect""" - with Image.open("Tests/images/g4-fillorder-test.tif") as g4: - assert_image_equal_tofile(g4, "Tests/images/g4-fillorder-test.png") - - def test_g4_write(self, tmp_path): - """Checking to see that the saved image is the same as what we wrote""" - test_file = "Tests/images/hopper_g4_500.tif" - with Image.open(test_file) as orig: - out = str(tmp_path / "temp.tif") - rot = orig.transpose(Image.Transpose.ROTATE_90) - assert rot.size == (500, 500) - rot.save(out) - - with Image.open(out) as reread: - assert reread.size == (500, 500) - self._assert_noerr(tmp_path, reread) - assert_image_equal(reread, rot) - assert reread.info["compression"] == "group4" - - assert reread.info["compression"] == orig.info["compression"] - - assert orig.tobytes() != reread.tobytes() - - def test_adobe_deflate_tiff(self): - test_file = "Tests/images/tiff_adobe_deflate.tif" - with Image.open(test_file) as im: - assert im.mode == "RGB" - assert im.size == (278, 374) - assert im.tile[0][:3] == ("libtiff", (0, 0, 278, 374), 0) - im.load() - - assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png") - - @pytest.mark.parametrize("legacy_api", (False, True)) - def test_write_metadata(self, legacy_api, tmp_path): - """Test metadata writing through libtiff""" - f = str(tmp_path / "temp.tiff") - with Image.open("Tests/images/hopper_g4.tif") as img: - img.save(f, tiffinfo=img.tag) - - if legacy_api: - original = img.tag.named() - else: - original = img.tag_v2.named() - - # PhotometricInterpretation is set from SAVE_INFO, - # not the original image. - ignored = [ - "StripByteCounts", - "RowsPerStrip", - "PageNumber", - "PhotometricInterpretation", - ] - - with Image.open(f) as loaded: - if legacy_api: - reloaded = loaded.tag.named() - else: - reloaded = loaded.tag_v2.named() - - for tag, value in itertools.chain(reloaded.items(), original.items()): - if tag not in ignored: - val = original[tag] - if tag.endswith("Resolution"): - if legacy_api: - assert val[0][0] / val[0][1] == ( - 4294967295 / 113653537 - ), f"{tag} didn't roundtrip" - else: - assert val == 37.79000115940079, f"{tag} didn't roundtrip" - else: - assert val == value, f"{tag} didn't roundtrip" - - # https://github.com/python-pillow/Pillow/issues/1561 - requested_fields = ["StripByteCounts", "RowsPerStrip", "StripOffsets"] - for field in requested_fields: - assert field in reloaded, f"{field} not in metadata" - - @pytest.mark.valgrind_known_error(reason="Known invalid metadata") - def test_additional_metadata(self, tmp_path): - # these should not crash. Seriously dummy data, most of it doesn't make - # any sense, so we're running up against limits where we're asking - # libtiff to do stupid things. - - # Get the list of the ones that we should be able to write - - core_items = { - tag: info - for tag, info in ((s, TiffTags.lookup(s)) for s in TiffTags.LIBTIFF_CORE) - if info.type is not None - } - - # Exclude ones that have special meaning - # that we're already testing them - with Image.open("Tests/images/hopper_g4.tif") as im: - for tag in im.tag_v2: - try: - del core_items[tag] - except KeyError: - pass - del core_items[320] # colormap is special, tested below - - # Type codes: - # 2: "ascii", - # 3: "short", - # 4: "long", - # 5: "rational", - # 12: "double", - # Type: dummy value - values = { - 2: "test", - 3: 1, - 4: 2**20, - 5: TiffImagePlugin.IFDRational(100, 1), - 12: 1.05, - } - - new_ifd = TiffImagePlugin.ImageFileDirectory_v2() - for tag, info in core_items.items(): - if info.length == 1: - new_ifd[tag] = values[info.type] - if info.length == 0: - new_ifd[tag] = tuple(values[info.type] for _ in range(3)) - else: - new_ifd[tag] = tuple(values[info.type] for _ in range(info.length)) - - # Extra samples really doesn't make sense in this application. - del new_ifd[338] - - out = str(tmp_path / "temp.tif") - TiffImagePlugin.WRITE_LIBTIFF = True - - im.save(out, tiffinfo=new_ifd) - - TiffImagePlugin.WRITE_LIBTIFF = False - - def test_custom_metadata(self, tmp_path): - tc = namedtuple("test_case", "value,type,supported_by_default") - custom = { - 37000 + k: v - for k, v in enumerate( - [ - tc(4, TiffTags.SHORT, True), - tc(123456789, TiffTags.LONG, True), - tc(-4, TiffTags.SIGNED_BYTE, False), - tc(-4, TiffTags.SIGNED_SHORT, False), - tc(-123456789, TiffTags.SIGNED_LONG, False), - tc(TiffImagePlugin.IFDRational(4, 7), TiffTags.RATIONAL, True), - tc(4.25, TiffTags.FLOAT, True), - tc(4.25, TiffTags.DOUBLE, True), - tc("custom tag value", TiffTags.ASCII, True), - tc(b"custom tag value", TiffTags.BYTE, True), - tc((4, 5, 6), TiffTags.SHORT, True), - tc((123456789, 9, 34, 234, 219387, 92432323), TiffTags.LONG, True), - tc((-4, 9, 10), TiffTags.SIGNED_BYTE, False), - tc((-4, 5, 6), TiffTags.SIGNED_SHORT, False), - tc( - (-123456789, 9, 34, 234, 219387, -92432323), - TiffTags.SIGNED_LONG, - False, - ), - tc((4.25, 5.25), TiffTags.FLOAT, True), - tc((4.25, 5.25), TiffTags.DOUBLE, True), - # array of TIFF_BYTE requires bytes instead of tuple for backwards - # compatibility - tc(bytes([4]), TiffTags.BYTE, True), - tc(bytes((4, 9, 10)), TiffTags.BYTE, True), - ] - ) - } - - libtiffs = [False] - if Image.core.libtiff_support_custom_tags: - libtiffs.append(True) - - for libtiff in libtiffs: - TiffImagePlugin.WRITE_LIBTIFF = libtiff - - def check_tags(tiffinfo): - im = hopper() - - out = str(tmp_path / "temp.tif") - im.save(out, tiffinfo=tiffinfo) - - with Image.open(out) as reloaded: - for tag, value in tiffinfo.items(): - reloaded_value = reloaded.tag_v2[tag] - if ( - isinstance(reloaded_value, TiffImagePlugin.IFDRational) - and libtiff - ): - # libtiff does not support real RATIONALS - assert ( - round(abs(float(reloaded_value) - float(value)), 7) == 0 - ) - continue - - assert reloaded_value == value - - # Test with types - ifd = TiffImagePlugin.ImageFileDirectory_v2() - for tag, tagdata in custom.items(): - ifd[tag] = tagdata.value - ifd.tagtype[tag] = tagdata.type - check_tags(ifd) - - # Test without types. This only works for some types, int for example are - # always encoded as LONG and not SIGNED_LONG. - check_tags( - { - tag: tagdata.value - for tag, tagdata in custom.items() - if tagdata.supported_by_default - } - ) - TiffImagePlugin.WRITE_LIBTIFF = False - - def test_subifd(self, tmp_path): - outfile = str(tmp_path / "temp.tif") - with Image.open("Tests/images/g4_orientation_6.tif") as im: - im.tag_v2[SUBIFD] = 10000 - - # Should not segfault - im.save(outfile) - - def test_xmlpacket_tag(self, tmp_path): - TiffImagePlugin.WRITE_LIBTIFF = True - - out = str(tmp_path / "temp.tif") - hopper().save(out, tiffinfo={700: b"xmlpacket tag"}) - TiffImagePlugin.WRITE_LIBTIFF = False - - with Image.open(out) as reloaded: - if 700 in reloaded.tag_v2: - assert reloaded.tag_v2[700] == b"xmlpacket tag" - - def test_int_dpi(self, tmp_path): - # issue #1765 - im = hopper("RGB") - out = str(tmp_path / "temp.tif") - TiffImagePlugin.WRITE_LIBTIFF = True - im.save(out, dpi=(72, 72)) - TiffImagePlugin.WRITE_LIBTIFF = False - with Image.open(out) as reloaded: - assert reloaded.info["dpi"] == (72.0, 72.0) - - def test_g3_compression(self, tmp_path): - with Image.open("Tests/images/hopper_g4_500.tif") as i: - out = str(tmp_path / "temp.tif") - i.save(out, compression="group3") - - with Image.open(out) as reread: - assert reread.info["compression"] == "group3" - assert_image_equal(reread, i) - - def test_little_endian(self, tmp_path): - with Image.open("Tests/images/16bit.deflate.tif") as im: - assert im.getpixel((0, 0)) == 480 - assert im.mode == "I;16" - - b = im.tobytes() - # Bytes are in image native order (little endian) - assert b[0] == ord(b"\xe0") - assert b[1] == ord(b"\x01") - - out = str(tmp_path / "temp.tif") - # out = "temp.le.tif" - im.save(out) - with Image.open(out) as reread: - assert reread.info["compression"] == im.info["compression"] - assert reread.getpixel((0, 0)) == 480 - # UNDONE - libtiff defaults to writing in native endian, so - # on big endian, we'll get back mode = 'I;16B' here. - - def test_big_endian(self, tmp_path): - with Image.open("Tests/images/16bit.MM.deflate.tif") as im: - assert im.getpixel((0, 0)) == 480 - assert im.mode == "I;16B" - - b = im.tobytes() - - # Bytes are in image native order (big endian) - assert b[0] == ord(b"\x01") - assert b[1] == ord(b"\xe0") - - out = str(tmp_path / "temp.tif") - im.save(out) - with Image.open(out) as reread: - assert reread.info["compression"] == im.info["compression"] - assert reread.getpixel((0, 0)) == 480 - - def test_g4_string_info(self, tmp_path): - """Tests String data in info directory""" - test_file = "Tests/images/hopper_g4_500.tif" - with Image.open(test_file) as orig: - out = str(tmp_path / "temp.tif") - - orig.tag[269] = "temp.tif" - orig.save(out) - - with Image.open(out) as reread: - assert "temp.tif" == reread.tag_v2[269] - assert "temp.tif" == reread.tag[269][0] - - def test_12bit_rawmode(self): - """Are we generating the same interpretation - of the image as Imagemagick is?""" - TiffImagePlugin.READ_LIBTIFF = True - with Image.open("Tests/images/12bit.cropped.tif") as im: - im.load() - TiffImagePlugin.READ_LIBTIFF = False - # to make the target -- - # convert 12bit.cropped.tif -depth 16 tmp.tif - # convert tmp.tif -evaluate RightShift 4 12in16bit2.tif - # imagemagick will auto scale so that a 12bit FFF is 16bit FFF0, - # so we need to unshift so that the integer values are the same. - - assert_image_equal_tofile(im, "Tests/images/12in16bit.tif") - - def test_blur(self, tmp_path): - # test case from irc, how to do blur on b/w image - # and save to compressed tif. - out = str(tmp_path / "temp.tif") - with Image.open("Tests/images/pport_g4.tif") as im: - im = im.convert("L") - - im = im.filter(ImageFilter.GaussianBlur(4)) - im.save(out, compression="tiff_adobe_deflate") - - assert_image_equal_tofile(im, out) - - def test_compressions(self, tmp_path): - # Test various tiff compressions and assert similar image content but reduced - # file sizes. - im = hopper("RGB") - out = str(tmp_path / "temp.tif") - im.save(out) - size_raw = os.path.getsize(out) - - for compression in ("packbits", "tiff_lzw"): - im.save(out, compression=compression) - size_compressed = os.path.getsize(out) - assert_image_equal_tofile(im, out) - - im.save(out, compression="jpeg") - size_jpeg = os.path.getsize(out) - with Image.open(out) as im2: - assert_image_similar(im, im2, 30) - - im.save(out, compression="jpeg", quality=30) - size_jpeg_30 = os.path.getsize(out) - assert_image_similar_tofile(im2, out, 30) - - assert size_raw > size_compressed - assert size_compressed > size_jpeg - assert size_jpeg > size_jpeg_30 - - def test_tiff_jpeg_compression(self, tmp_path): - im = hopper("RGB") - out = str(tmp_path / "temp.tif") - im.save(out, compression="tiff_jpeg") - - with Image.open(out) as reloaded: - assert reloaded.info["compression"] == "jpeg" - - def test_tiff_deflate_compression(self, tmp_path): - im = hopper("RGB") - out = str(tmp_path / "temp.tif") - im.save(out, compression="tiff_deflate") - - with Image.open(out) as reloaded: - assert reloaded.info["compression"] == "tiff_adobe_deflate" - - def test_quality(self, tmp_path): - im = hopper("RGB") - out = str(tmp_path / "temp.tif") - - with pytest.raises(ValueError): - im.save(out, compression="tiff_lzw", quality=50) - with pytest.raises(ValueError): - im.save(out, compression="jpeg", quality=-1) - with pytest.raises(ValueError): - im.save(out, compression="jpeg", quality=101) - with pytest.raises(ValueError): - im.save(out, compression="jpeg", quality="good") - im.save(out, compression="jpeg", quality=0) - im.save(out, compression="jpeg", quality=100) - - def test_cmyk_save(self, tmp_path): - im = hopper("CMYK") - out = str(tmp_path / "temp.tif") - - im.save(out, compression="tiff_adobe_deflate") - assert_image_equal_tofile(im, out) - - @pytest.mark.parametrize("im", (hopper("P"), Image.new("P", (1, 1), "#000"))) - def test_palette_save(self, im, tmp_path): - out = str(tmp_path / "temp.tif") - - TiffImagePlugin.WRITE_LIBTIFF = True - im.save(out) - TiffImagePlugin.WRITE_LIBTIFF = False - - with Image.open(out) as reloaded: - # colormap/palette tag - assert len(reloaded.tag_v2[320]) == 768 - - @pytest.mark.parametrize("compression", ("tiff_ccitt", "group3", "group4")) - def test_bw_compression_w_rgb(self, compression, tmp_path): - im = hopper("RGB") - out = str(tmp_path / "temp.tif") - - with pytest.raises(OSError): - im.save(out, compression=compression) - - def test_fp_leak(self): - im = Image.open("Tests/images/hopper_g4_500.tif") - fn = im.fp.fileno() - - os.fstat(fn) - im.load() # this should close it. - with pytest.raises(OSError): - os.fstat(fn) - im = None # this should force even more closed. - with pytest.raises(OSError): - os.fstat(fn) - with pytest.raises(OSError): - os.close(fn) - - def test_multipage(self): - # issue #862 - TiffImagePlugin.READ_LIBTIFF = True - with Image.open("Tests/images/multipage.tiff") as im: - # file is a multipage tiff, 10x10 green, 10x10 red, 20x20 blue - - im.seek(0) - assert im.size == (10, 10) - assert im.convert("RGB").getpixel((0, 0)) == (0, 128, 0) - assert im.tag.next - - im.seek(1) - assert im.size == (10, 10) - assert im.convert("RGB").getpixel((0, 0)) == (255, 0, 0) - assert im.tag.next - - im.seek(2) - assert not im.tag.next - assert im.size == (20, 20) - assert im.convert("RGB").getpixel((0, 0)) == (0, 0, 255) - - TiffImagePlugin.READ_LIBTIFF = False - - def test_multipage_nframes(self): - # issue #862 - TiffImagePlugin.READ_LIBTIFF = True - with Image.open("Tests/images/multipage.tiff") as im: - frames = im.n_frames - assert frames == 3 - for _ in range(frames): - im.seek(0) - # Should not raise ValueError: I/O operation on closed file - im.load() - - TiffImagePlugin.READ_LIBTIFF = False - - def test_multipage_seek_backwards(self): - TiffImagePlugin.READ_LIBTIFF = True - with Image.open("Tests/images/multipage.tiff") as im: - im.seek(1) - im.load() - - im.seek(0) - assert im.convert("RGB").getpixel((0, 0)) == (0, 128, 0) - - TiffImagePlugin.READ_LIBTIFF = False - - def test__next(self): - TiffImagePlugin.READ_LIBTIFF = True - with Image.open("Tests/images/hopper.tif") as im: - assert not im.tag.next - im.load() - assert not im.tag.next - - def test_4bit(self): - # Arrange - test_file = "Tests/images/hopper_gray_4bpp.tif" - original = hopper("L") - - # Act - TiffImagePlugin.READ_LIBTIFF = True - with Image.open(test_file) as im: - TiffImagePlugin.READ_LIBTIFF = False - - # Assert - assert im.size == (128, 128) - assert im.mode == "L" - assert_image_similar(im, original, 7.3) - - def test_gray_semibyte_per_pixel(self): - test_files = ( - ( - 24.8, # epsilon - ( # group - "Tests/images/tiff_gray_2_4_bpp/hopper2.tif", - "Tests/images/tiff_gray_2_4_bpp/hopper2I.tif", - "Tests/images/tiff_gray_2_4_bpp/hopper2R.tif", - "Tests/images/tiff_gray_2_4_bpp/hopper2IR.tif", - ), - ), - ( - 7.3, # epsilon - ( # group - "Tests/images/tiff_gray_2_4_bpp/hopper4.tif", - "Tests/images/tiff_gray_2_4_bpp/hopper4I.tif", - "Tests/images/tiff_gray_2_4_bpp/hopper4R.tif", - "Tests/images/tiff_gray_2_4_bpp/hopper4IR.tif", - ), - ), - ) - original = hopper("L") - for epsilon, group in test_files: - with Image.open(group[0]) as im: - assert im.size == (128, 128) - assert im.mode == "L" - assert_image_similar(im, original, epsilon) - for file in group[1:]: - with Image.open(file) as im2: - assert im2.size == (128, 128) - assert im2.mode == "L" - assert_image_equal(im, im2) - - def test_save_bytesio(self): - # PR 1011 - # Test TIFF saving to io.BytesIO() object. - - TiffImagePlugin.WRITE_LIBTIFF = True - TiffImagePlugin.READ_LIBTIFF = True - - # Generate test image - pilim = hopper() - - def save_bytesio(compression=None): - buffer_io = io.BytesIO() - pilim.save(buffer_io, format="tiff", compression=compression) - buffer_io.seek(0) - - assert_image_similar_tofile(pilim, buffer_io, 0) - - save_bytesio() - save_bytesio("raw") - save_bytesio("packbits") - save_bytesio("tiff_lzw") - - TiffImagePlugin.WRITE_LIBTIFF = False - TiffImagePlugin.READ_LIBTIFF = False - - def test_save_ycbcr(self, tmp_path): - im = hopper("YCbCr") - outfile = str(tmp_path / "temp.tif") - im.save(outfile, compression="jpeg") - - with Image.open(outfile) as reloaded: - assert reloaded.tag_v2[530] == (1, 1) - assert reloaded.tag_v2[532] == (0, 255, 128, 255, 128, 255) - - def test_exif_ifd(self, tmp_path): - outfile = str(tmp_path / "temp.tif") - with Image.open("Tests/images/tiff_adobe_deflate.tif") as im: - assert im.tag_v2[34665] == 125456 - im.save(outfile) - - with Image.open(outfile) as reloaded: - if Image.core.libtiff_support_custom_tags: - assert reloaded.tag_v2[34665] == 125456 - - def test_crashing_metadata(self, tmp_path): - # issue 1597 - with Image.open("Tests/images/rdf.tif") as im: - out = str(tmp_path / "temp.tif") - - TiffImagePlugin.WRITE_LIBTIFF = True - # this shouldn't crash - im.save(out, format="TIFF") - TiffImagePlugin.WRITE_LIBTIFF = False - - def test_page_number_x_0(self, tmp_path): - # Issue 973 - # Test TIFF with tag 297 (Page Number) having value of 0 0. - # The first number is the current page number. - # The second is the total number of pages, zero means not available. - outfile = str(tmp_path / "temp.tif") - # Created by printing a page in Chrome to PDF, then: - # /usr/bin/gs -q -sDEVICE=tiffg3 -sOutputFile=total-pages-zero.tif - # -dNOPAUSE /tmp/test.pdf -c quit - infile = "Tests/images/total-pages-zero.tif" - with Image.open(infile) as im: - # Should not divide by zero - im.save(outfile) - - def test_fd_duplication(self, tmp_path): - # https://github.com/python-pillow/Pillow/issues/1651 - - tmpfile = str(tmp_path / "temp.tif") - with open(tmpfile, "wb") as f: - with open("Tests/images/g4-multi.tiff", "rb") as src: - f.write(src.read()) - - im = Image.open(tmpfile) - im.n_frames - im.close() - # Should not raise PermissionError. - os.remove(tmpfile) - - def test_read_icc(self): - with Image.open("Tests/images/hopper.iccprofile.tif") as img: - icc = img.info.get("icc_profile") - assert icc is not None - TiffImagePlugin.READ_LIBTIFF = True - with Image.open("Tests/images/hopper.iccprofile.tif") as img: - icc_libtiff = img.info.get("icc_profile") - assert icc_libtiff is not None - TiffImagePlugin.READ_LIBTIFF = False - assert icc == icc_libtiff - - def test_write_icc(self, tmp_path): - def check_write(libtiff): - TiffImagePlugin.WRITE_LIBTIFF = libtiff - - with Image.open("Tests/images/hopper.iccprofile.tif") as img: - icc_profile = img.info["icc_profile"] - - out = str(tmp_path / "temp.tif") - img.save(out, icc_profile=icc_profile) - with Image.open(out) as reloaded: - assert icc_profile == reloaded.info["icc_profile"] - - libtiffs = [] - if Image.core.libtiff_support_custom_tags: - libtiffs.append(True) - libtiffs.append(False) - - for libtiff in libtiffs: - check_write(libtiff) - - 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) - - im.seek(1) - assert im._compression == "packbits" - assert im.size == (10, 10) - im.load() - - im.seek(0) - assert im._compression == "tiff_ccitt" - assert im.size == (10, 10) - im.load() - - def test_save_tiff_with_jpegtables(self, tmp_path): - # Arrange - outfile = str(tmp_path / "temp.tif") - - # Created with ImageMagick: convert hopper.jpg hopper_jpg.tif - # Contains JPEGTables (347) tag - infile = "Tests/images/hopper_jpg.tif" - with Image.open(infile) as im: - # Act / Assert - # Should not raise UnicodeDecodeError or anything else - im.save(outfile) - - def test_16bit_RGB_tiff(self): - with Image.open("Tests/images/tiff_16bit_RGB.tiff") as im: - assert im.mode == "RGB" - assert im.size == (100, 40) - assert im.tile, [ - ( - "libtiff", - (0, 0, 100, 40), - 0, - ("RGB;16N", "tiff_adobe_deflate", False, 8), - ) - ] - im.load() - - assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGB_target.png") - - def test_16bit_RGBa_tiff(self): - with Image.open("Tests/images/tiff_16bit_RGBa.tiff") as im: - assert im.mode == "RGBA" - assert im.size == (100, 40) - assert im.tile, [ - ("libtiff", (0, 0, 100, 40), 0, ("RGBa;16N", "tiff_lzw", False, 38236)) - ] - im.load() - - assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGBa_target.png") - - @skip_unless_feature("jpg") - def test_gimp_tiff(self): - # Read TIFF JPEG images from GIMP [@PIL168] - filename = "Tests/images/pil168.tif" - with Image.open(filename) as im: - assert im.mode == "RGB" - assert im.size == (256, 256) - assert im.tile == [ - ("libtiff", (0, 0, 256, 256), 0, ("RGB", "jpeg", False, 5122)) - ] - im.load() - - assert_image_equal_tofile(im, "Tests/images/pil168.png") - - def test_sampleformat(self): - # https://github.com/python-pillow/Pillow/issues/1466 - with Image.open("Tests/images/copyleft.tiff") as im: - assert im.mode == "RGB" - - assert_image_equal_tofile(im, "Tests/images/copyleft.png", mode="RGB") - - def test_sampleformat_write(self, tmp_path): - im = Image.new("F", (1, 1)) - out = str(tmp_path / "temp.tif") - TiffImagePlugin.WRITE_LIBTIFF = True - im.save(out) - TiffImagePlugin.WRITE_LIBTIFF = False - - with Image.open(out) as reloaded: - assert reloaded.mode == "F" - assert reloaded.getexif()[SAMPLEFORMAT] == 3 - - def test_lzma(self, capfd): - try: - with Image.open("Tests/images/hopper_lzma.tif") as im: - assert im.mode == "RGB" - assert im.size == (128, 128) - assert im.format == "TIFF" - im2 = hopper() - assert_image_similar(im, im2, 5) - except OSError: - captured = capfd.readouterr() - if "LZMA compression support is not configured" in captured.err: - pytest.skip("LZMA compression support is not configured") - sys.stdout.write(captured.out) - sys.stderr.write(captured.err) - raise - - def test_webp(self, capfd): - try: - with Image.open("Tests/images/hopper_webp.tif") as im: - assert im.mode == "RGB" - assert im.size == (128, 128) - assert im.format == "TIFF" - assert_image_similar_tofile(im, "Tests/images/hopper_webp.png", 1) - except OSError: - captured = capfd.readouterr() - if "WEBP compression support is not configured" in captured.err: - pytest.skip("WEBP compression support is not configured") - if ( - "Compression scheme 50001 strip decoding is not implemented" - in captured.err - ): - pytest.skip( - "Compression scheme 50001 strip decoding is not implemented" - ) - sys.stdout.write(captured.out) - sys.stderr.write(captured.err) - raise - - def test_lzw(self): - with Image.open("Tests/images/hopper_lzw.tif") as im: - assert im.mode == "RGB" - assert im.size == (128, 128) - assert im.format == "TIFF" - im2 = hopper() - assert_image_similar(im, im2, 5) - - def test_strip_cmyk_jpeg(self): - infile = "Tests/images/tiff_strip_cmyk_jpeg.tif" - with Image.open(infile) as im: - assert_image_similar_tofile(im, "Tests/images/pil_sample_cmyk.jpg", 0.5) - - def test_strip_cmyk_16l_jpeg(self): - infile = "Tests/images/tiff_strip_cmyk_16l_jpeg.tif" - with Image.open(infile) as im: - assert_image_similar_tofile(im, "Tests/images/pil_sample_cmyk.jpg", 0.5) - - @mark_if_feature_version( - pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" - ) - def test_strip_ycbcr_jpeg_2x2_sampling(self): - infile = "Tests/images/tiff_strip_ycbcr_jpeg_2x2_sampling.tif" - with Image.open(infile) as im: - assert_image_similar_tofile(im, "Tests/images/flower.jpg", 1.2) - - @mark_if_feature_version( - pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" - ) - def test_strip_ycbcr_jpeg_1x1_sampling(self): - infile = "Tests/images/tiff_strip_ycbcr_jpeg_1x1_sampling.tif" - with Image.open(infile) as im: - assert_image_similar_tofile(im, "Tests/images/flower2.jpg", 0.01) - - def test_tiled_cmyk_jpeg(self): - infile = "Tests/images/tiff_tiled_cmyk_jpeg.tif" - with Image.open(infile) as im: - assert_image_similar_tofile(im, "Tests/images/pil_sample_cmyk.jpg", 0.5) - - @mark_if_feature_version( - pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" - ) - def test_tiled_ycbcr_jpeg_1x1_sampling(self): - infile = "Tests/images/tiff_tiled_ycbcr_jpeg_1x1_sampling.tif" - with Image.open(infile) as im: - assert_image_similar_tofile(im, "Tests/images/flower2.jpg", 0.01) - - @mark_if_feature_version( - pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" - ) - def test_tiled_ycbcr_jpeg_2x2_sampling(self): - infile = "Tests/images/tiff_tiled_ycbcr_jpeg_2x2_sampling.tif" - with Image.open(infile) as im: - assert_image_similar_tofile(im, "Tests/images/flower.jpg", 1.5) - - def test_strip_planar_rgb(self): - # gdal_translate -co TILED=no -co INTERLEAVE=BAND -co COMPRESS=LZW \ - # tiff_strip_raw.tif tiff_strip_planar_lzw.tiff - infile = "Tests/images/tiff_strip_planar_lzw.tiff" - with Image.open(infile) as im: - assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png") - - def test_tiled_planar_rgb(self): - # gdal_translate -co TILED=yes -co INTERLEAVE=BAND -co COMPRESS=LZW \ - # tiff_tiled_raw.tif tiff_tiled_planar_lzw.tiff - infile = "Tests/images/tiff_tiled_planar_lzw.tiff" - with Image.open(infile) as im: - assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png") - - def test_tiled_planar_16bit_RGB(self): - # gdal_translate -co TILED=yes -co INTERLEAVE=BAND -co COMPRESS=LZW \ - # tiff_16bit_RGB.tiff tiff_tiled_planar_16bit_RGB.tiff - with Image.open("Tests/images/tiff_tiled_planar_16bit_RGB.tiff") as im: - assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGB_target.png") - - def test_strip_planar_16bit_RGB(self): - # gdal_translate -co TILED=no -co INTERLEAVE=BAND -co COMPRESS=LZW \ - # tiff_16bit_RGB.tiff tiff_strip_planar_16bit_RGB.tiff - with Image.open("Tests/images/tiff_strip_planar_16bit_RGB.tiff") as im: - assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGB_target.png") - - def test_tiled_planar_16bit_RGBa(self): - # gdal_translate -co TILED=yes \ - # -co INTERLEAVE=BAND -co COMPRESS=LZW -co ALPHA=PREMULTIPLIED \ - # tiff_16bit_RGBa.tiff tiff_tiled_planar_16bit_RGBa.tiff - with Image.open("Tests/images/tiff_tiled_planar_16bit_RGBa.tiff") as im: - assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGBa_target.png") - - def test_strip_planar_16bit_RGBa(self): - # gdal_translate -co TILED=no \ - # -co INTERLEAVE=BAND -co COMPRESS=LZW -co ALPHA=PREMULTIPLIED \ - # tiff_16bit_RGBa.tiff tiff_strip_planar_16bit_RGBa.tiff - with Image.open("Tests/images/tiff_strip_planar_16bit_RGBa.tiff") as im: - assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGBa_target.png") - - @pytest.mark.parametrize("compression", (None, "jpeg")) - def test_block_tile_tags(self, compression, tmp_path): - im = hopper() - out = str(tmp_path / "temp.tif") - - tags = { - TiffImagePlugin.TILEWIDTH: 256, - TiffImagePlugin.TILELENGTH: 256, - TiffImagePlugin.TILEOFFSETS: 256, - TiffImagePlugin.TILEBYTECOUNTS: 256, - } - im.save(out, exif=tags, compression=compression) - - with Image.open(out) as reloaded: - for tag in tags: - assert tag not in reloaded.getexif() - - def test_old_style_jpeg(self): - with Image.open("Tests/images/old-style-jpeg-compression.tif") as im: - assert_image_equal_tofile(im, "Tests/images/old-style-jpeg-compression.png") - - def test_open_missing_samplesperpixel(self): - with Image.open( - "Tests/images/old-style-jpeg-compression-no-samplesperpixel.tif" - ) 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" - with Image.open(infile) as im: - im.load() - assert im.size == (950, 975) - - def test_orientation(self): - with Image.open("Tests/images/g4_orientation_1.tif") as base_im: - for i in range(2, 9): - with Image.open("Tests/images/g4_orientation_" + str(i) + ".tif") as im: - assert 274 in im.tag_v2 - - im.load() - assert 274 not in im.tag_v2 - - assert_image_similar(base_im, im, 0.7) - - def test_exif_transpose(self): - with Image.open("Tests/images/g4_orientation_1.tif") as base_im: - for i in range(2, 9): - with Image.open("Tests/images/g4_orientation_" + str(i) + ".tif") as im: - im = ImageOps.exif_transpose(im) - - assert_image_similar(base_im, im, 0.7) - - @pytest.mark.valgrind_known_error(reason="Backtrace in Python Core") - def test_sampleformat_not_corrupted(self): - # Assert that a TIFF image with SampleFormat=UINT tag is not corrupted - # when saving to a new file. - # Pillow 6.0 fails with "OSError: cannot identify image file". - tiff = io.BytesIO( - base64.b64decode( - b"SUkqAAgAAAAPAP4ABAABAAAAAAAAAAABBAABAAAAAQAAAAEBBAABAAAAAQAA" - b"AAIBAwADAAAAwgAAAAMBAwABAAAACAAAAAYBAwABAAAAAgAAABEBBAABAAAA" - b"4AAAABUBAwABAAAAAwAAABYBBAABAAAAAQAAABcBBAABAAAACwAAABoBBQAB" - b"AAAAyAAAABsBBQABAAAA0AAAABwBAwABAAAAAQAAACgBAwABAAAAAQAAAFMB" - b"AwADAAAA2AAAAAAAAAAIAAgACAABAAAAAQAAAAEAAAABAAAAAQABAAEAAAB4" - b"nGNgYAAAAAMAAQ==" - ) - ) - out = io.BytesIO() - with Image.open(tiff) as im: - im.save(out, format="tiff") - out.seek(0) - with Image.open(out) as im: - im.load() - - def test_realloc_overflow(self): - TiffImagePlugin.READ_LIBTIFF = True - with Image.open("Tests/images/tiff_overflow_rows_per_strip.tif") as im: - with pytest.raises(OSError) as e: - im.load() - - # Assert that the error code is IMAGING_CODEC_MEMORY - assert str(e.value) == "-9" - TiffImagePlugin.READ_LIBTIFF = False - - @pytest.mark.parametrize("compression", ("tiff_adobe_deflate", "jpeg")) - def test_save_multistrip(self, compression, tmp_path): - im = hopper("RGB").resize((256, 256)) - out = str(tmp_path / "temp.tif") - im.save(out, compression=compression) - - with Image.open(out) as im: - # Assert that there are multiple strips - assert len(im.tag_v2[STRIPOFFSETS]) > 1 - - @pytest.mark.parametrize("argument", (True, False)) - def test_save_single_strip(self, argument, tmp_path): - im = hopper("RGB").resize((256, 256)) - out = str(tmp_path / "temp.tif") - - if not argument: - TiffImagePlugin.STRIP_SIZE = 2**18 - try: - arguments = {"compression": "tiff_adobe_deflate"} - if argument: - arguments["strip_size"] = 2**18 - im.save(out, **arguments) - - with Image.open(out) as im: - assert len(im.tag_v2[STRIPOFFSETS]) == 1 - finally: - TiffImagePlugin.STRIP_SIZE = 65536 - - @pytest.mark.parametrize("compression", ("tiff_adobe_deflate", None)) - def test_save_zero(self, compression, tmp_path): - im = Image.new("RGB", (0, 0)) - 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) diff --git a/Tests/test_file_libtiff_small.py b/Tests/test_file_libtiff_small.py deleted file mode 100644 index 03137c8b603..00000000000 --- a/Tests/test_file_libtiff_small.py +++ /dev/null @@ -1,44 +0,0 @@ -from io import BytesIO - -from PIL import Image - -from .test_file_libtiff import LibTiffTestCase - - -class TestFileLibTiffSmall(LibTiffTestCase): - - """The small lena image was failing on open in the libtiff - decoder because the file pointer was set to the wrong place - by a spurious seek. It wasn't failing with the byteio method. - - It was fixed by forcing an lseek to the beginning of the - file just before reading in libtiff. These tests remain - to ensure that it stays fixed.""" - - def test_g4_hopper_file(self, tmp_path): - """Testing the open file load path""" - - test_file = "Tests/images/hopper_g4.tif" - with open(test_file, "rb") as f: - with Image.open(f) as im: - assert im.size == (128, 128) - self._assert_noerr(tmp_path, im) - - def test_g4_hopper_bytesio(self, tmp_path): - """Testing the bytesio loading code path""" - test_file = "Tests/images/hopper_g4.tif" - s = BytesIO() - with open(test_file, "rb") as f: - s.write(f.read()) - s.seek(0) - with Image.open(s) as im: - assert im.size == (128, 128) - self._assert_noerr(tmp_path, im) - - def test_g4_hopper(self, tmp_path): - """The 128x128 lena image failed for some reason.""" - - test_file = "Tests/images/hopper_g4.tif" - with Image.open(test_file) as im: - assert im.size == (128, 128) - self._assert_noerr(tmp_path, im) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 9bf293d13e8..15890ffff6e 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -3252,6 +3252,8 @@ def _open_core(fp, filename, prefix, formats): # Leave disabled by default, spams the logs with image # opening failures that are entirely expected. # logger.debug("", exc_info=True) + if i == "TIFF": + raise continue except BaseException: if exclusive_fp: